From b9be068538492837d2851f2900e1829bb16361c1 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Thu, 24 Sep 2026 18:48:43 -0700 Subject: [PATCH 01/13] Avoid decoding prerender matcher parameters twice (#99121) ## Summary Avoid decoding route parameters a second time while selecting prerender metadata. This fixes valid percent-containing URLs returning 500, including both build-generated closed routes and runtime-generated open routes. For example, a request for `/open/docs/space%20here/with%2Fslash/fallback%25` is already normalized for manifest lookup before reaching `PrerenderManifestMatcher`. Its final parameter now contains a literal `%`. The matcher previously used `getRouteMatcher`, which tried to decode the captures again and threw even though this call only needed to determine whether the route structure matched. Test the existing, lazily constructed route regular expression directly instead. Matcher precedence, source-page filtering, and the current server parameter encoding behavior stay unchanged. This does not introduce a new manifest or adapter contract. The open fallback is requested twice to cover generation and reuse. The existing fixture uses `dynamicParams = false` and remains excluded from the Cache Components matrix, which does not support that configuration. This is an independent prerequisite for #97393: the failure reproduces without the parameter-matching API and should be fixed for existing apps too. ## Verification - Before the fix, the new closed and open catch-all requests returned 500 with a parameter-decoding error. After the fix, both return 200 and retain the existing encoded server parameter values. - The focused matcher unit suite passes all six tests. - The three encoding regressions pass with production Webpack after restacking. The API routing suite also passes all 36 tests with fresh-native production Turbopack above this prerequisite, including its existing percent-containing URL cases. - The earlier isolated verification passed package TypeScript and changed-file lint. Deployment verification remains for CI. --- .../prerender-manifest-matcher.test.ts | 22 +++++++++ .../helpers/prerender-manifest-matcher.ts | 14 +++--- .../app/closed/[...slug]/page.tsx | 18 +++++++ .../app/open/[...slug]/page.tsx | 16 ++++++ .../prerender-encoding.test.ts | 49 +++++++++++++++++++ 5 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 test/e2e/app-dir/prerender-encoding/app/closed/[...slug]/page.tsx create mode 100644 test/e2e/app-dir/prerender-encoding/app/open/[...slug]/page.tsx diff --git a/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.test.ts b/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.test.ts index a2f8a8931892..3fc2e1ad87e9 100644 --- a/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.test.ts +++ b/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.test.ts @@ -145,6 +145,28 @@ describe('PrerenderManifestMatcher', () => { route: genericRootRoute, }) }) + + it('should match a pathname that was already decoded for manifest lookup', () => { + const route = createMockDynamicRoute({ + fallbackSourceRoute: '/open/[...slug]', + }) + + const manifest = createMockPrerenderManifest({ + '/open/[...slug]': route, + }) + + const matcher = new PrerenderManifestMatcher( + '/open/[...slug]', + manifest + ) + + expect( + matcher.match('/open/docs/space here/with%2Fslash/100%') + ).toEqual({ + source: '/open/[...slug]', + route, + }) + }) }) describe('no match scenarios', () => { diff --git a/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.ts b/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.ts index 959a52250735..b99ee573e130 100644 --- a/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.ts +++ b/packages/next/src/server/route-modules/app-page/helpers/prerender-manifest-matcher.ts @@ -3,10 +3,6 @@ import type { PrerenderManifest, } from '../../../../build' import type { DeepReadonly } from '../../../../shared/lib/deep-readonly' -import { - getRouteMatcher, - type RouteMatchFn, -} from '../../../../shared/lib/router/utils/route-matcher' import { getRouteRegex } from '../../../../shared/lib/router/utils/route-regex' /** @@ -17,7 +13,7 @@ type Matcher = { * The matcher for the dynamic route. This is lazily created when the matcher * is first used. */ - matcher?: RouteMatchFn + matcher?: RegExp /** * The source of the dynamic route. @@ -69,11 +65,13 @@ export class PrerenderManifestMatcher { for (const matcher of this.matchers) { // Lazily create the matcher, this is only done once per matcher. if (!matcher.matcher) { - matcher.matcher = getRouteMatcher(getRouteRegex(matcher.source)) + // The pathname has already been decoded for manifest lookup. We only + // need to check its structure here; extracting captures would decode + // route parameters again and reject literal percent signs. + matcher.matcher = getRouteRegex(matcher.source).re } - const match = matcher.matcher(pathname) - if (match) { + if (matcher.matcher.test(pathname)) { return { source: matcher.source, route: matcher.route, diff --git a/test/e2e/app-dir/prerender-encoding/app/closed/[...slug]/page.tsx b/test/e2e/app-dir/prerender-encoding/app/closed/[...slug]/page.tsx new file mode 100644 index 000000000000..84bc2f1b407d --- /dev/null +++ b/test/e2e/app-dir/prerender-encoding/app/closed/[...slug]/page.tsx @@ -0,0 +1,18 @@ +export const dynamicParams = false + +export function generateStaticParams() { + return [ + { slug: ['docs', 'space here', '100%'] }, + { slug: ['docs', 'space here', 'with/slash', '100%'] }, + ] +} + +export default async function Page({ + params, +}: { + params: Promise<{ slug: string[] }> +}) { + const { slug } = await params + + return
params.slug is {slug.join('/')}
+} diff --git a/test/e2e/app-dir/prerender-encoding/app/open/[...slug]/page.tsx b/test/e2e/app-dir/prerender-encoding/app/open/[...slug]/page.tsx new file mode 100644 index 000000000000..214249c254b6 --- /dev/null +++ b/test/e2e/app-dir/prerender-encoding/app/open/[...slug]/page.tsx @@ -0,0 +1,16 @@ +export function generateStaticParams() { + return [ + { slug: ['docs', 'space here', '100%'] }, + { slug: ['docs', 'space here', 'with/slash', '100%'] }, + ] +} + +export default async function Page({ + params, +}: { + params: Promise<{ slug: string[] }> +}) { + const { slug } = await params + + return
params.slug is {slug.join('/')}
+} diff --git a/test/e2e/app-dir/prerender-encoding/prerender-encoding.test.ts b/test/e2e/app-dir/prerender-encoding/prerender-encoding.test.ts index 57dd4bb74117..967e61293c78 100644 --- a/test/e2e/app-dir/prerender-encoding/prerender-encoding.test.ts +++ b/test/e2e/app-dir/prerender-encoding/prerender-encoding.test.ts @@ -1,3 +1,4 @@ +import { load } from 'cheerio' import { nextTestSetup } from 'e2e-utils' describe('prerender-encoding', () => { @@ -9,4 +10,52 @@ describe('prerender-encoding', () => { const $ = await next.render$('/sticks%20%26%20stones') expect($('div').text()).toBe('params.id is sticks%20%26%20stones') }) + + it('should serve an exact closed catch-all path containing a literal percent', async () => { + const response = await next.fetch('/closed/docs/space%20here/100%25') + expect(response.status).toBe(200) + const $ = load(await response.text()) + expect($('div').text()).toBe('params.slug is docs/space%20here/100%25') + }) + + it('should serve an open catch-all fallback containing a literal percent', async () => { + const pathname = '/open/docs/space%20here/fallback%25' + for (let i = 0; i < 2; i++) { + const response = await next.fetch(pathname) + expect(response.status).toBe(200) + const $ = load(await response.text()) + expect($('div').text()).toBe( + 'params.slug is docs/space%20here/fallback%25' + ) + } + }) + + // Vercel tries the raw URL and the fully decoded path for filesystem lookup. + // Neither matches this output's decoded space/percent and still-encoded slash. + // @gate !deploy + it('should preserve an encoded slash in an exact closed catch-all path', async () => { + const response = await next.fetch( + '/closed/docs/space%20here/with%2Fslash/100%25' + ) + expect(response.status).toBe(200) + const $ = load(await response.text()) + expect($('div').text()).toBe( + 'params.slug is docs/space%20here/with%2Fslash/100%25' + ) + }) + + // Vercel decodes the captured slash before splitting catch-all values, whereas + // next start preserves it within a single value. + // @gate !deploy + it('should preserve an encoded slash in an open catch-all fallback', async () => { + const pathname = '/open/docs/space%20here/with%2Fslash/fallback%25' + for (let i = 0; i < 2; i++) { + const response = await next.fetch(pathname) + expect(response.status).toBe(200) + const $ = load(await response.text()) + expect($('div').text()).toBe( + 'params.slug is docs/space%20here/with%2Fslash/fallback%25' + ) + } + }) }) From cc7e804cd600a19348a5322e8ad60153d4b89096 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Thu, 24 Sep 2026 19:27:03 -0700 Subject: [PATCH 02/13] turbo-tasks-gc: Invalidating a deleted task should be a no-op (#98614) Make invalidating deleted tasks a no-op. Invalidators now represent a 'weak' reference to a task, and invalidations don't assert task existence. We know that the task exists when the invalidator is created but by the time an invalidation occurs it might not exist anymore. So treat the dependency as weak. Take care to not create 'blank' tasks when querying for them, this can confuse assertions that occur later. ## Why is this safe? Currently it is safe because we never reuse task ids, so invalidating a deleted task is perfectly reasonable, much like invalidating a non-active task, there is nothing to do In the future if we start reusing task ids, then this becomes an ABA problem. An invalidator can point at a new task. This is also not too bad, a spurious invalidation is self healing. Also most invalidators are not actually persisted which limits the risk. So we can either decide to build a mechanism to tear down these stale edges or tolerate spurious invalidations. I think we could make invalidator users subscribe to a 'deleted task id bus' which would allow them to drop references, this could be useful and then the few cases where we _persist_ invalidators (by way of `State` objects) would need to devise a new mechanism (or decide to tolerate the spurious invalidations) --------- Co-authored-by: Claude Opus 5 (1M context) Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> --- .../src/backend/operation/invalidate.rs | 25 +- .../src/backend/operation/mod.rs | 321 ++++++++++++------ .../src/backend/storage.rs | 52 +++ .../tests/gc_collection.rs | 82 ++++- .../tests/gc_resurrection.rs | 16 +- .../turbo-tasks-backend/tests/gc_stress.rs | 5 +- 6 files changed, 367 insertions(+), 134 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs index d5d40991fd90..c426892d88ab 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs @@ -60,7 +60,7 @@ impl Operation for InvalidateOperation { } => { let mut queue = AggregationUpdateQueue::new(); for task_id in task_ids { - make_task_dirty( + try_make_task_dirty( task_id, #[cfg(feature = "task_dirty_cause")] cause.clone(), @@ -88,6 +88,7 @@ impl Operation for InvalidateOperation { } } +/// Marks a task dirty. The task must exist. pub fn make_task_dirty( task_id: TaskId, #[cfg(feature = "task_dirty_cause")] cause: TaskDirtyCause, @@ -105,6 +106,28 @@ pub fn make_task_dirty( ); } +/// Marks a task dirty, doing nothing if it no longer exists. +/// +/// Intended for invalidation usecases. +fn try_make_task_dirty( + task_id: TaskId, + #[cfg(feature = "task_dirty_cause")] cause: TaskDirtyCause, + queue: &mut AggregationUpdateQueue, + ctx: &mut impl ExecuteContext<'_>, +) { + let Some(mut task) = ctx.try_get_task(task_id, TaskDataCategory::All) else { + return; + }; + make_task_dirty_internal( + &mut task, + true, + #[cfg(feature = "task_dirty_cause")] + cause, + queue, + ctx, + ); +} + /// Requires the guard to be allocated with [TaskDataCategory::All] pub fn make_task_dirty_internal<'e, E: ExecuteContext<'e>>( task: &mut E::TaskGuardImpl, diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs index 55a80d392014..f6585497ec38 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs @@ -9,6 +9,7 @@ mod update_cell; mod update_collectible; use std::{ fmt::{Debug, Display, Formatter}, + ops::{Deref, DerefMut}, sync::Arc, }; @@ -30,7 +31,7 @@ use crate::{ EventDescription, TaskDataCategory, TurboTasksBackend, cell_data::CellData, snapshot_coordinator::{OperationGuard, SnapshotPhase}, - storage::{SpecificTaskDataCategory, StorageWriteGuard, TrackOutcome}, + storage::{SpecificTaskDataCategory, StorageWriteGuard, TaskEntryGuard, TrackOutcome}, storage_schema::{TaskStorage, TaskStorageAccessors}, }, data::{ActivenessState, CollectibleRef, Dirtyness, InProgressState, TransientTask}, @@ -40,9 +41,44 @@ pub trait Operation: Encode + Decode<()> + Default + TryFrom); } -/// Whether an [`ExecuteContext`] task open may create the task or requires it to already exist. -/// A private impl detail behind the two public methods ([`ExecuteContext::task`] = `MustExist`, -/// [`ExecuteContext::open_or_create_task_storage`] = `MaybeCreate`). +/// The task storage `open_task` is working with, which may or may not still own its map entry. +enum OpenedTask<'a> { + Owned(TaskEntryGuard<'a>), + Restored(StorageWriteGuard<'a>), +} + +impl<'a> OpenedTask<'a> { + fn into_write_guard(self) -> StorageWriteGuard<'a> { + match self { + OpenedTask::Owned(g) => g.into_write_guard(), + OpenedTask::Restored(g) => g, + } + } +} + +impl Deref for OpenedTask<'_> { + type Target = TaskStorage; + fn deref(&self) -> &Self::Target { + match self { + OpenedTask::Owned(g) => g, + OpenedTask::Restored(g) => g, + } + } +} + +impl DerefMut for OpenedTask<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + OpenedTask::Owned(g) => g, + OpenedTask::Restored(g) => g, + } + } +} + +/// Whether an [`ExecuteContext`] task open may create the task, requires it to already exist, or +/// tolerates its absence. A private impl detail behind the three public methods +/// ([`ExecuteContext::task`] = `MustExist`, [`ExecuteContext::open_or_create_task_storage`] = +/// `MaybeCreate`, [`ExecuteContext::try_get_task`] = `AllowMissing`). #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum TaskAccess { /// Open the task, creating it if it does not exist: `access_mut` inserts a blank entry, then @@ -52,8 +88,10 @@ enum TaskAccess { /// task that exists in neither memory nor persistent storage is a bug — a stale reference to an /// already-collected or never-created task — and this refuses to fabricate a blank for it. /// - /// This is very much expression a 'foreign key constraint' on the database. + /// This is very much expressing a 'foreign key constraint' on the database. MustExist, + /// Open a task that may legitimately be gone or `deleted`. + AllowMissing, } // TODO: consider removing this trait (and `TaskGuard`) in favor of the concrete types. Each has @@ -73,6 +111,15 @@ pub trait ExecuteContext<'e>: Sized { /// The check applies only to persistent tasks; a `MustExist` open of a transient id falls /// through to create. See `ExecuteContextImpl::open_task`. fn task(&mut self, task_id: TaskId, category: TaskDataCategory) -> Self::TaskGuardImpl; + /// Opens a task that may legitimately be gone, returning `None` if it is. + /// + /// Gone covers both a task that exists nowhere and one that is soft-deleted: the caller cannot + /// tell those apart, since only the timing of the next eviction separates them. + fn try_get_task( + &mut self, + task_id: TaskId, + category: TaskDataCategory, + ) -> Option; /// Opens a task, materializing an in-memory storage entry for it if one is not resident yet /// (inserting a blank, then restoring `category` from disk if present). Use only where the /// task's storage may not be resident: the first connect of a freshly-minted child (threads can @@ -299,26 +346,24 @@ impl<'e> ExecuteContextImpl<'e> { task_id: TaskId, category: TaskDataCategory, access: TaskAccess, - ) -> TaskGuardImpl<'e> { + ) -> Option> { self.task_lock_counter.acquire(); - // A resident entry always corresponds to a task that exists (only a `MaybeCreate` open ever - // inserts a blank, and only for a task being created). A `MustExist` open therefore only - // needs to prove existence when the entry looks like a fresh blank: nothing restored, not a - // new task. (A fully-evicted resident task also matches this shape, but it is on disk, so - // the `found_on_disk` check below clears it — the panic fires only when the task is in - // neither memory nor disk.) - let mut task = self.backend.storage.access_mut(task_id); - // The `MustExist` non-fabrication check applies only to **persistent** tasks: they have - // disk backing and are the subject of the stale-reference/GC concern. A transient task has - // no disk copy and is materialized lazily in memory (a strongly-consistent read can open a - // transient root through the aggregation graph before its storage entry exists), so a - // `MustExist` open of a transient id is a no-op that falls through to create. - let maybe_fabricated = access == TaskAccess::MustExist - && !task_id.is_transient() - && !task.flags.is_restored(TaskDataCategory::Meta) - && !task.flags.is_restored(TaskDataCategory::Data) - && !task.flags.new_task(); + let mut task = OpenedTask::Owned(self.backend.storage.access_entry_mut(task_id)); + // Treat deleted tasks under Allowmissing as missing + if access == TaskAccess::AllowMissing && task.flags.deleted() { + self.task_lock_counter.release(); + return None; + } + + // IF the caller cares about existence (either to panic or return None), check if this is an + // effectively blank task + let needs_existence_check = + matches!(access, TaskAccess::MustExist | TaskAccess::AllowMissing) + && !task_id.is_transient() + && !task.flags.is_restored(TaskDataCategory::Meta) + && !task.flags.is_restored(TaskDataCategory::Data) + && !task.flags.new_task(); if !task.flags.is_restored(category) { if task_id.is_transient() { task.flags.set_restored(TaskDataCategory::All); @@ -343,103 +388,144 @@ impl<'e> ExecuteContextImpl<'e> { task.flags.set_meta_restoring(true); } - if do_data || do_meta || data_restoring || meta_restoring { - let waiting_for_restore = data_restoring || meta_restoring; - if waiting_for_restore { - // The caller holds the task id outside the graph while waiting, so pin it - // against GC until the restored guard reaches the use boundary. Eviction is - // still allowed; the wait loop restores the category again if needed. - task.update_and_get_transient_ref_count(1); - } - // Drop lock while doing I/O (our I/O can overlap with the other thread). - drop(task); - - // Perform I/O for categories we claimed. - let storage_data = do_data - .then(|| self.restore_task_data(task_id, SpecificTaskDataCategory::Data)); - let storage_meta = do_meta - .then(|| self.restore_task_data(task_id, SpecificTaskDataCategory::Meta)); - - // Whether our own I/O found the task on disk (in any restored category). - // Another thread restoring it concurrently (`*_restoring`) - // also proves existence: it only sets the restoring bit - // after finding the task. - let found_on_disk = restored_from_disk(&storage_data) - || restored_from_disk(&storage_meta) - || data_restoring - || meta_restoring; - - // Wait for categories claimed by another thread (after our I/O). - // Reuse the returned write guard to avoid a second lock acquisition. - task = if let Some(cat) = wait_category(data_restoring, meta_restoring) { - self.wait_for_restore_or_panic(task_id, cat) - } else { - self.backend.storage.access_mut(task_id) - }; - if waiting_for_restore { - // This caller owns the pin and releases it only after acquiring the task - // guard it is about to use. - task.update_and_get_transient_ref_count(-1); - } + // `!is_restored(category)` above already implies this: for a single category + // it is the same predicate, and for `All` both are the disjunction + // `!data_restored || !meta_restored`. Asserted so that a future change to + // `is_restored` or `TaskDataCategory` surfaces here instead of silently + // skipping the restore. + debug_assert!( + needs_data || needs_meta, + "task({task_id}, {category:?}): not restored, yet neither category needs \ + restoring" + ); - // Apply results and clear restoring bits. - if let Some(result) = storage_data - && let Err(e) = - apply_restore_result(&mut task, result, SpecificTaskDataCategory::Data) - { - drop(task); - self.backend.storage.restored.notify(usize::MAX); - panic!("Failed to restore data for task {task_id}: {e:?}"); - } - if let Some(result) = storage_meta - && let Err(e) = - apply_restore_result(&mut task, result, SpecificTaskDataCategory::Meta) - { - drop(task); - self.backend.storage.restored.notify(usize::MAX); - panic!("Failed to restore meta for task {task_id}: {e:?}"); - } + let waiting_for_restore = data_restoring || meta_restoring; + if waiting_for_restore { + // The caller holds the task id outside the graph while waiting, so pin it + // against GC until the restored guard reaches the use boundary. Eviction is + // still allowed; the wait loop restores the category again if needed. + task.update_and_get_transient_ref_count(1); + } + // Drop lock while doing I/O (our I/O can overlap with the other thread). + drop(task); - if do_data || do_meta { - // Keep the guard through return. Once the restoring bit is clear, eviction - // may otherwise drop the category before this caller can use it. - self.backend.storage.restored.notify(usize::MAX); - } + // Perform I/O for categories we claimed. + let storage_data = do_data + .then(|| self.restore_task_data(task_id, SpecificTaskDataCategory::Data)); + let storage_meta = do_meta + .then(|| self.restore_task_data(task_id, SpecificTaskDataCategory::Meta)); - // The caller asserted this task exists (`MustExist`), but it looked like a - // fresh blank and restore found nothing on disk (and no one - // else was restoring it): it exists nowhere. Fail loudly - // rather than hand back a fabricated task, which - // would silently corrupt the graph. (The leftover blank entry is inert; the - // panic tears the process down.) - // - // This also fires if the on-disk cache is corrupt or truncated. That is the - // intended behavior: there is no recovery path for reading a cell on a task - // that is missing from disk, and the panic is self-healing — the cache is - // discarded and rebuilt on the next run. - assert!( - !(maybe_fabricated && !found_on_disk), - "task({task_id}, MustExist): task exists in neither memory nor persistent \ - storage — a stale reference to an already-collected or never-created task" - ); + // Whether our own I/O found the task on disk (in any restored category). + // + // A category another thread claimed (`*_restoring`) counts as found. The bit is + // set before that thread's I/O, so on its own it proves only that someone is + // looking — but this thread waits for that restore below, and a peer that comes up + // empty clears the category's `restored` bit before returning, so the blank cannot + // be mistaken for a real task. + let found_on_disk = restored_from_disk(&storage_data) + || restored_from_disk(&storage_meta) + || data_restoring + || meta_restoring; + + // Wait for categories claimed by another thread (after our I/O). + // Reuse the returned write guard to avoid a second lock acquisition. + // Reuse the guard the waiter already holds; only our own-I/O path can come + // up empty and need to discard the entry. + task = if let Some(cat) = wait_category(data_restoring, meta_restoring) { + OpenedTask::Restored(self.wait_for_restore_or_panic(task_id, cat)) } else { - // Nothing to restore (no categories claimed, none in progress) yet the entry - // looked like a fresh blank for a task asserted to exist: it does not exist. - assert!( - !maybe_fabricated, + OpenedTask::Owned(self.backend.storage.access_entry_mut(task_id)) + }; + if waiting_for_restore { + // This caller owns the pin and releases it only after acquiring the task + // guard it is about to use. + task.update_and_get_transient_ref_count(-1); + } + + // Apply results and clear restoring bits. + if let Some(result) = storage_data + && let Err(e) = + apply_restore_result(&mut task, result, SpecificTaskDataCategory::Data) + { + drop(task); + self.backend.storage.restored.notify(usize::MAX); + panic!("Failed to restore data for task {task_id}: {e:?}"); + } + if let Some(result) = storage_meta + && let Err(e) = + apply_restore_result(&mut task, result, SpecificTaskDataCategory::Meta) + { + drop(task); + self.backend.storage.restored.notify(usize::MAX); + panic!("Failed to restore meta for task {task_id}: {e:?}"); + } + + if do_data || do_meta { + // Keep the guard through return. Once the restoring bit is clear, eviction + // may otherwise drop the category before this caller can use it. + self.backend.storage.restored.notify(usize::MAX); + } + + // It looked like a fresh blank and restore found nothing on disk (and no one + // else was restoring it): it exists nowhere. An `AllowMissing` open reports + // that; a `MustExist` open fails loudly rather than hand + // back a fabricated task, which would silently corrupt the + // graph. (The leftover blank entry is inert; the + // panic tears the process down.) + if needs_existence_check && !found_on_disk { + if access == TaskAccess::AllowMissing { + // If the count is non-zero that means another thread is mid-restore or + // otherwise connecting to it. If they are waiting with AllowMissing they + // will perform the discard and if not then they want the blank and that is + // also fine. + if task.gc_transient_ref_count() == 0 { + match task { + OpenedTask::Owned(g) => g.discard(), + OpenedTask::Restored(_) => { + unreachable!( + "a task restored by another thread exists and is never \ + discarded" + ) + } + }; + } else { + // A waiter pins the entry, so it cannot be removed here. Reset the + // categories we read back to "never looked" instead so the other reader + // re-reads and also observes absence + // + // Only the categories this thread claimed are ours to clear. One a + // peer restored is the peer's to report, and it reaches this same + // code to clear its own. + debug_assert!( + !(do_data && task.flags.data_restoring()) + && !(do_meta && task.flags.meta_restoring()), + "task({task_id}): apply_restore_result should have cleared the \ + restoring bits for the categories we claimed" + ); + if do_data { + task.flags.set_data_restored(false); + } + if do_meta { + task.flags.set_meta_restored(false); + } + } + self.task_lock_counter.release(); + return None; + } + panic!( "task({task_id}, MustExist): task exists in neither memory nor persistent \ storage — a stale reference to an already-collected or never-created task" ); } } } - TaskGuardImpl { - task, + Some(TaskGuardImpl { + task: task.into_write_guard(), task_id, #[cfg(debug_assertions)] category, task_lock_counter: self.task_lock_counter.clone(), - } + }) } /// Restores one category for a task from persistent storage. `None` means the task was **not @@ -974,7 +1060,7 @@ fn wait_category(wait_data: bool, wait_meta: bool) -> Option { /// the restored flag. On error, returns the error so the caller can drop the task lock, /// notify waiters, and panic. fn apply_restore_result( - task: &mut StorageWriteGuard<'_>, + task: &mut (impl DerefMut + ?Sized), result: Result>, category: SpecificTaskDataCategory, ) -> Result<()> { @@ -1018,6 +1104,15 @@ impl<'e> ExecuteContext<'e> for ExecuteContextImpl<'e> { fn task(&mut self, task_id: TaskId, category: TaskDataCategory) -> Self::TaskGuardImpl { self.open_task(task_id, category, TaskAccess::MustExist) + .expect("a MustExist open either yields a task or panics") + } + + fn try_get_task( + &mut self, + task_id: TaskId, + category: TaskDataCategory, + ) -> Option { + self.open_task(task_id, category, TaskAccess::AllowMissing) } fn open_or_create_task_storage( @@ -1026,6 +1121,7 @@ impl<'e> ExecuteContext<'e> for ExecuteContextImpl<'e> { category: TaskDataCategory, ) -> Self::TaskGuardImpl { self.open_task(task_id, category, TaskAccess::MaybeCreate) + .expect("a MaybeCreate open always yields a task") } fn prepare_tasks( @@ -1080,11 +1176,11 @@ impl<'e> ExecuteContext<'e> for ExecuteContextImpl<'e> { // in memory and has no disk copy): a task that looks like a freshly-inserted blank (nothing // restored, not a new task) and that restore does not find on disk exists nowhere — a stale // reference. See `TaskAccess::MustExist`. - let maybe_fabricated1 = !task_id1.is_transient() + let needs_existence_check1 = !task_id1.is_transient() && !task1.flags.is_restored(TaskDataCategory::Meta) && !task1.flags.is_restored(TaskDataCategory::Data) && !task1.flags.new_task(); - let maybe_fabricated2 = !task_id2.is_transient() + let needs_existence_check2 = !task_id2.is_transient() && !task2.flags.is_restored(TaskDataCategory::Meta) && !task2.flags.is_restored(TaskDataCategory::Data) && !task2.flags.new_task(); @@ -1232,15 +1328,15 @@ impl<'e> ExecuteContext<'e> for ExecuteContextImpl<'e> { // A `MustExist` pair open must not fabricate: a task that looked like a fresh blank and // was not found on disk exists nowhere (a stale reference). See // `TaskAccess::MustExist`. Only reachable in the restore branch — a task - // already resident/restored (the else path) has `maybe_fabricated == + // already resident/restored (the else path) has `needs_existence_check == // false`. assert!( - !(maybe_fabricated1 && !found_on_disk1), + !(needs_existence_check1 && !found_on_disk1), "task_pair({task_id1}, .., MustExist): task exists in neither memory nor \ persistent storage — a stale reference to a never-created task" ); assert!( - !(maybe_fabricated2 && !found_on_disk2), + !(needs_existence_check2 && !found_on_disk2), "task_pair(.., {task_id2}, MustExist): task exists in neither memory nor \ persistent storage — a stale reference to a never-created task" ); @@ -1490,7 +1586,6 @@ pub trait TaskGuard: Debug + TaskStorageAccessors { /// /// How much this proves depends on the guard's category — with only `Meta` open it is a sound /// pre-filter that cannot see dependency edges, and with `All` open it is authoritative. See - /// [`TaskStorage::gc_maybe_collectible`] for the full contract. fn is_gc_collectible(&self) -> bool { // Transient-ness is a property of the id, not the storage; transient tasks are never // collected. diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs b/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs index 370ac1984b59..7ac1f75791b0 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs @@ -522,6 +522,20 @@ impl Storage { } } + /// Like [`Self::access_mut`], but keeps the map entry so the caller can still remove it. + pub fn access_entry_mut(&self, key: TaskId) -> TaskEntryGuard<'_> { + let entry = match self.map.entry(key) { + dashmap::mapref::entry::Entry::Occupied(e) => e, + dashmap::mapref::entry::Entry::Vacant(e) => { + e.insert_entry(Box::new(TaskStorage::new())) + } + }; + TaskEntryGuard { + storage: self, + entry, + } + } + /// Read-only access to an already resident task. Returns `None` if the task isnt in memory /// resident. The closure runs while a shard read lock is held, so it must be cheap and must /// not re-enter the map. @@ -831,6 +845,44 @@ impl Storage { } } +/// A write guard that still owns its map entry, so the task can be removed under the lock that is +/// already held. +/// +/// Use [`Storage::access_entry_mut`] to obtain one. Convert it with [`Self::into_write_guard`] once +/// removal is no longer a possibility, or call [`Self::discard`] to drop the entry outright. +pub struct TaskEntryGuard<'a> { + storage: &'a Storage, + entry: dashmap::mapref::entry::OccupiedEntry<'a, TaskId, Box>, +} + +impl<'a> TaskEntryGuard<'a> { + /// Removes this task's entry. + pub fn discard(self) { + self.entry.remove(); + } + + /// Gives up the ability to remove the entry, yielding an ordinary write guard. + pub fn into_write_guard(self) -> StorageWriteGuard<'a> { + StorageWriteGuard { + storage: self.storage, + inner: self.entry.into_ref().into(), + } + } +} + +impl Deref for TaskEntryGuard<'_> { + type Target = TaskStorage; + fn deref(&self) -> &Self::Target { + self.entry.get() + } +} + +impl DerefMut for TaskEntryGuard<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.entry.get_mut() + } +} + pub struct StorageWriteGuard<'a> { storage: &'a Storage, inner: RefMut<'a, TaskId, Box>, diff --git a/turbopack/crates/turbo-tasks-backend/tests/gc_collection.rs b/turbopack/crates/turbo-tasks-backend/tests/gc_collection.rs index 593196d5858d..924801f9234b 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/gc_collection.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/gc_collection.rs @@ -11,7 +11,7 @@ use anyhow::Result; use turbo_tasks::{ResolvedVc, TaskId, Vc, prevent_gc}; use crate::{ - gc_fixture::{Selector, create_selector}, + gc_fixture::{Constant, Selector, create_constant, create_selector}, util::create_tt, }; @@ -47,6 +47,29 @@ async fn branch_b() -> Result> { Ok(Vc::cell(2 + *leaf(20).await?)) } +/// Reads `observed` so the task registers an invalidator on it, then goes out of the live graph +/// when the selector flips. Whether that invalidator outlives the task is the point of +/// `mutating_a_state_read_by_a_collected_task_does_not_panic`. +#[turbo_tasks::function] +async fn state_reader(observed: ResolvedVc) -> Result> { + Ok(Vc::cell(*observed.await?.get())) +} + +/// Reads `state_reader` only while the selector is false, so flipping it disconnects the reader. +#[turbo_tasks::function(operation, root)] +async fn select_state_reader( + selector: ResolvedVc, + observed: ResolvedVc, +) -> Result> { + let use_b = *selector.await?.get(); + let value = if use_b { + *branch_b().await? + } else { + *state_reader(*observed).await? + }; + Ok(Vc::cell(value)) +} + /// A task that pins itself against GC while executing. Once pinned it must survive collection even /// after it is disconnected. #[turbo_tasks::function] @@ -114,7 +137,6 @@ async fn gc_collects_disconnected_subtree() { ); // Flipping back must recompute branch_a fresh, since it was collected. - let tt3 = tt.clone(); let result = turbo_tasks::run_once(tt.clone(), async move { let selector_op = create_selector(true); let selector_vc = selector_op.resolve().strongly_consistent().await?; @@ -123,7 +145,6 @@ async fn gc_collects_disconnected_subtree() { assert_eq!(*output.read_strongly_consistent().await?, 22); selector.set(false); assert_eq!(*output.read_strongly_consistent().await?, 11); - let _ = &tt3; anyhow::Ok(()) }) .await; @@ -266,3 +287,58 @@ async fn unpin_after_stop_does_not_panic() { tt.unpin_task_for_gc(leaf_id); } + +/// A `State` keeps an `Invalidator` for every task that read it, and those entries are plain task +/// ids with nothing keeping the task alive. Collecting a reader therefore leaves a dangling +/// invalidator behind, this test ensures that that doesn't cause a panic. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn mutating_a_state_read_by_a_collected_task_does_not_panic() { + let (tt, _persistence_dir) = + create_tt("mutating_a_state_read_by_a_collected_task_does_not_panic"); + let tt2 = tt.clone(); + + let result = turbo_tasks::run_once(tt.clone(), async move { + let selector_op = create_selector(false); + let selector_vc = selector_op.resolve().strongly_consistent().await?; + let selector = selector_op.read_strongly_consistent().await?; + let observed_vc = create_constant().resolve().strongly_consistent().await?; + + // `state_reader` reads `observed`, registering an invalidator on that State. + let output = select_state_reader(selector_vc, observed_vc); + output.read_strongly_consistent().await?; + + // Flip so `state_reader` leaves the live graph; its invalidator stays on `observed`. + selector.set(true); + output.read_strongly_consistent().await?; + + anyhow::Ok(()) + }) + .await; + result.unwrap(); + + let collected = tt2.backend().gc_for_testing(&tt2); + assert!( + collected > 0, + "the disconnected state reader should have been collected" + ); + + // Required, and not just for realism: the evict is what selects the code path under test. + // GC only soft-deletes, leaving the task resident, and a weak open of a resident-but-deleted + // task returns early on the `deleted` flag. Evicting drops it from memory (and tombstones it on + // disk), so the open below instead reaches the exists-nowhere case in + // `ExecuteContextImpl::open_task` -- nothing restored, nothing found on disk -- which is the + // one that would panic under `MustExist`. Drop this line and the test still passes, but it + // stops covering that path. + tt2.backend().snapshot_and_evict_for_testing(&tt2); + + // Mutating the State now walks its invalidator list, which still names the collected reader. + let result = turbo_tasks::run_once(tt.clone(), async move { + let observed = create_constant().read_strongly_consistent().await?; + observed.set(1); + anyhow::Ok(()) + }) + .await; + result.unwrap(); + + tt.stop_and_wait().await; +} diff --git a/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs b/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs index 3b4f5ba10256..9bcd9661c548 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs @@ -230,7 +230,6 @@ async fn gc_resurrect_on_reconnect() { // Reconnect the subtree (selector back to false) BEFORE any snapshot. Reading `reader` again // connects it, which must resurrect it (and its leaves, as it re-reads them). - let tt3 = tt.clone(); let result = turbo_tasks::run_once(tt.clone(), async move { let selector_op = create_selector(false); let selector_vc = selector_op.resolve().strongly_consistent().await?; @@ -244,7 +243,6 @@ async fn gc_resurrect_on_reconnect() { expected, "resurrected reader must recompute the correct value" ); - let _ = &tt3; anyhow::Ok(()) }) .await; @@ -252,7 +250,6 @@ async fn gc_resurrect_on_reconnect() { // A snapshot+evict now must NOT have tombstoned/hard-deleted the resurrected subtree. tt2.backend().snapshot_and_evict_for_testing(&tt2); - let tt4 = tt.clone(); let result = turbo_tasks::run_once(tt.clone(), async move { let selector_op = create_selector(false); let selector_vc = selector_op.resolve().strongly_consistent().await?; @@ -260,7 +257,6 @@ async fn gc_resurrect_on_reconnect() { let constant_vc = constant_op.resolve().strongly_consistent().await?; let output = select_reader(selector_vc, constant_vc); assert_eq!(*output.read_strongly_consistent().await?, expected); - let _ = &tt4; anyhow::Ok(()) }) .await; @@ -319,7 +315,6 @@ async fn select_imm_reader(selector: ResolvedVc) -> Result> { #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn gc_resurrect_immutable_recomputes() { let (tt, _persistence_dir) = create_tt("gc_resurrect_immutable_recomputes"); - let tt2 = tt.clone(); let expected: u32 = (0..IMM_FANOUT).fold(0u32, |a, b| a.wrapping_add(b * 3)); let result = turbo_tasks::run_once(tt.clone(), async move { @@ -349,7 +344,7 @@ async fn gc_resurrect_immutable_recomputes() { // Collect the disconnected subtree. The entries stay resident (no snapshot yet), so the tasks // are soft-deleted rather than gone. - let collected = tt2.backend().gc_for_testing(&tt2); + let collected = tt.backend().gc_for_testing(&tt); assert_eq!( collected, IMM_FANOUT as usize + 1, @@ -360,10 +355,8 @@ async fn gc_resurrect_immutable_recomputes() { // This must recompute it rather than serve a stale value. Done before the reconnect below, // while the subtree is still collected — afterwards the leaves are live again and a read would // legitimately hit a fresh cell, proving nothing. - let tt_direct = tt.clone(); let result = turbo_tasks::run_once(tt.clone(), async move { assert_eq!(*read_imm_leaf(0).read_strongly_consistent().await?, 0); - let _ = &tt_direct; anyhow::Ok(()) }) .await; @@ -377,7 +370,6 @@ async fn gc_resurrect_immutable_recomputes() { // Reconnect BEFORE any snapshot. These tasks were never persisted, so there is nothing on disk // to restore — the only way back to a correct value is re-execution. - let tt3 = tt.clone(); let result = turbo_tasks::run_once(tt.clone(), async move { let selector_op = create_selector(false); let selector_vc = selector_op.resolve().strongly_consistent().await?; @@ -389,7 +381,7 @@ async fn gc_resurrect_immutable_recomputes() { expected, "a resurrected immutable task must recompute the correct value" ); - let _ = &tt3; + anyhow::Ok(()) }) .await; @@ -404,14 +396,12 @@ async fn gc_resurrect_immutable_recomputes() { // A snapshot + evict must not have tombstoned the resurrected subtree, and the restored data // must survive the round trip. - tt2.backend().snapshot_and_evict_for_testing(&tt2); - let tt4 = tt.clone(); + tt.backend().snapshot_and_evict_for_testing(&tt); let result = turbo_tasks::run_once(tt.clone(), async move { let selector_op = create_selector(false); let selector_vc = selector_op.resolve().strongly_consistent().await?; let output = select_imm_reader(selector_vc); assert_eq!(*output.read_strongly_consistent().await?, expected); - let _ = &tt4; anyhow::Ok(()) }) .await; diff --git a/turbopack/crates/turbo-tasks-backend/tests/gc_stress.rs b/turbopack/crates/turbo-tasks-backend/tests/gc_stress.rs index e656e664016a..cad455120736 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/gc_stress.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/gc_stress.rs @@ -35,7 +35,6 @@ async fn gc_re_rooting_stays_flat() { tt: &Arc>, gen_value: u32, ) -> (usize, usize, bool) { - let tt_inner = tt.clone(); turbo_tasks::run_once(tt.clone(), async move { // `create_generation` is a cached root operation, so this returns the same task each // round. @@ -47,7 +46,6 @@ async fn gc_re_rooting_stays_flat() { } let output = wide_root(generation_vc, WIDTH); output.read_strongly_consistent().await?; - let _ = &tt_inner; anyhow::Ok(()) }) .await @@ -119,14 +117,13 @@ async fn gc_re_rooting_stays_flat() { ); // The live graph must still compute correctly after all the churn. - let tt3 = tt.clone(); let result = turbo_tasks::run_once(tt.clone(), async move { let generation_op = create_generation(); let generation_vc = generation_op.resolve().strongly_consistent().await?; let output = wide_root(generation_vc, WIDTH); let expected: u32 = expected_value(ROUNDS, WIDTH); assert_eq!(*output.read_strongly_consistent().await?, expected); - let _ = &tt3; + anyhow::Ok(()) }) .await; From 645cae034c329871228950538b03c5001b8cdb8b Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Thu, 24 Sep 2026 19:27:03 -0700 Subject: [PATCH 03/13] turbo-tasks-backend: simplify the collectibility predicate and collect transient tasks (#98615) Fix refcounting for transient tasks - ensure transient tasks refcount each other correctly, and are eligible for collection - ensure root tasks are born with a transient_ref_count of 1 Also drop some ultimately harmful conditions from the gc predicate - don't consider `cell_dependent`. cell dependencies form cycles and are subsumed by ancestors - to read a cell you have to read it from a child (covered by parent_count) or be passed it from a parent. For the parent to have passed it to you it must have a child dependency on the producer or be passed it by its parent (recursively). So the task that passed you the vc must be holding ownership over both tasks. - therefore a stale cell_dependency can only exist within a task that is effectively dead, however because we do not reliably clean up all tasks in a session (due to leaking root tasks), we cannot fully exclude the existence of stale cell_dependency edges. We just know they must only exist within unreachable tasks. --------- Co-authored-by: Claude Opus 5 (1M context) --- .../turbo-tasks-backend/src/backend/mod.rs | 8 +- .../backend/operation/cleanup_old_edges.rs | 42 +++++----- .../src/backend/operation/connect_child.rs | 8 ++ .../src/backend/operation/connect_children.rs | 16 ++-- .../src/backend/operation/mod.rs | 9 +-- .../src/backend/storage.rs | 81 +++++++------------ .../src/backend/storage_schema.rs | 38 +++++---- .../tests/gc_collection.rs | 11 ++- .../tests/gc_resurrection.rs | 8 +- .../turbo-tasks-backend/tests/gc_stress.rs | 2 +- 10 files changed, 113 insertions(+), 110 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 99e7ff326a13..73d41ac4717a 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -435,12 +435,10 @@ impl TurboTasksBackend { } } - /// The number of persistent (non-transient) tasks resident in the map. Test-only hook; see - /// [`Storage::resident_persistent_task_count_for_testing`] for why the metric excludes - /// transient tasks. + /// The number oftasks resident in the map. #[doc(hidden)] - pub fn resident_persistent_task_count_for_testing(&self) -> usize { - self.storage.resident_persistent_task_count_for_testing() + pub fn resident_task_count_for_testing(&self) -> usize { + self.storage.resident_task_count_for_testing() } /// The persistent `parent_count` of a resident task (0 if absent or not resident). Test-only diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs index 7f217c78f0dd..3d67476cee36 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs @@ -160,27 +160,33 @@ impl CleanupOldEdgesOperation { }); let mut task = ctx.task(task_id, TaskDataCategory::All); - let mut removed_persistent_children = - SmallVec::<[TaskId; 4]>::new(); + // Mirror `ConnectChildrenOperation`'s split exactly: an edge + // counted as durable is released from `parent_count`, everything + // else from `transient_ref_count`. Getting this wrong either + // strands a task forever or underflows the count. + let parent_is_transient = task_id.is_transient(); + let mut removed_durable = SmallVec::<[TaskId; 4]>::new(); + let mut removed_transient = SmallVec::<[TaskId; 4]>::new(); for child_id in children.iter() { - if task.remove_children(child_id) && !child_id.is_transient() { - removed_persistent_children.push(*child_id); + if task.remove_children(child_id) { + if parent_is_transient || child_id.is_transient() { + removed_transient.push(*child_id); + } else { + removed_durable.push(*child_id); + } } } - // Each removed persistent child loses a parent. - if !removed_persistent_children.is_empty() { - let job = if task_id.is_transient() { - AggregationUpdateJob::AdjustTransientRefCount { - task_ids: removed_persistent_children, - delta: -1, - } - } else { - AggregationUpdateJob::AdjustParentCount { - task_ids: removed_persistent_children, - delta: -1, - } - }; - queue.push(job); + if !removed_durable.is_empty() { + queue.push(AggregationUpdateJob::AdjustParentCount { + task_ids: removed_durable, + delta: -1, + }); + } + if !removed_transient.is_empty() { + queue.push(AggregationUpdateJob::AdjustTransientRefCount { + task_ids: removed_transient, + delta: -1, + }); } if is_aggregating_node(get_aggregation_number(&task)) { drop(task); diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_child.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_child.rs index ca04e1c9304f..f959b239f051 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_child.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_child.rs @@ -87,6 +87,14 @@ impl ConnectChildOperation { release_construction_ref: bool, mut ctx: impl ExecuteContext<'_>, ) { + if parent_task_id.is_none() { + // All parentless tasks receive a transient ref when connected: their lifetime cannot be + // constrained by turbo-tasks and needs to be managed by the caller. If the caller + // doesn't manage it, the GC root TTL handles it in a later session. + let mut child_task = + ctx.open_or_create_task_storage(child_task_id, TaskDataCategory::Meta); + child_task.update_and_get_transient_ref_count(1); + } if let Some(parent_task_id) = parent_task_id { let mut parent_task = ctx.task(parent_task_id, TaskDataCategory::Meta); let Some(InProgressState::InProgress(InProgressStateInner { new_children, .. })) = diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_children.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_children.rs index 7198a2dd4435..f7873f4af42c 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_children.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/connect_children.rs @@ -71,12 +71,16 @@ pub fn connect_children( "connect_children parent_count + dirty", |mut child, ctx| { // Bump before `make_task_dirty_internal`, which consumes the guard. - if !child.id().is_transient() { - if parent_is_transient { - child.update_and_get_transient_ref_count(1); - } else { - child.update_and_get_parent_count(1); - } + // + // `parent_count` is the *durable* count, so it may only track an edge that will + // itself be persisted: a persistent parent holding a persistent child. Every + // other combination is session-only and belongs in `transient_ref_count` -- + // including a transient child, whose incoming edges can never outlive the + // session no matter what kind of parent holds them. + if parent_is_transient || child.id().is_transient() { + child.update_and_get_transient_ref_count(1); + } else { + child.update_and_get_parent_count(1); } if !child.has_output() { make_task_dirty_internal( diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs index f6585497ec38..8f07f54d3ecc 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs @@ -1582,15 +1582,10 @@ pub trait TaskGuard: Debug + TaskStorageAccessors { new_value } - /// Whether a GC pass may collect this task: it is non-transient and nothing references it. - /// - /// How much this proves depends on the guard's category — with only `Meta` open it is a sound - /// pre-filter that cannot see dependency edges, and with `All` open it is authoritative. See + /// Whether a GC pass may collect this task: nothing references it. fn is_gc_collectible(&self) -> bool { - // Transient-ness is a property of the id, not the storage; transient tasks are never - // collected. self.check_access(SpecificTaskDataCategory::Meta); - !self.id().is_transient() && self.typed().gc_maybe_collectible() + self.typed().gc_maybe_collectible() } fn invalidate_serialization(&mut self); diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs b/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs index 7ac1f75791b0..356b8ced33e4 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs @@ -544,21 +544,10 @@ impl Storage { Some(f(task.value())) } - /// The number of **persistent** (non-transient) tasks resident in the map. Use this to assert - /// GC returns to a flat baseline across re-rooting: GC never collects transient tasks (e.g. - /// `run_once`/Once roots), so their count is not expected to settle. + /// The number of tasks resident in the map. #[doc(hidden)] - pub fn resident_persistent_task_count_for_testing(&self) -> usize { - let mut persistent = 0; - for shard in self.map.shards() { - let shard = shard.read(); - for (task_id, _) in shard.iter() { - if !task_id.is_transient() { - persistent += 1; - } - } - } - persistent + pub fn resident_task_count_for_testing(&self) -> usize { + self.map.len() } /// The number of shards in the resident map. GC seeds one `ScanShard` job per index; the slice @@ -568,32 +557,17 @@ impl Storage { self.map.shards().len() } - /// Iterates the non-transient tasks of a **single** shard of the resident map by index, under - /// that shard's read lock. - fn for_each_resident_persistent_in_shard( - &self, - index: usize, - mut f: impl FnMut(TaskId, &TaskStorage), - ) { + /// Scans a **single** shard by index, invoking `on_candidate` for each resident task whose + /// storage passes [`TaskStorage::gc_maybe_collectible`]. + pub fn gc_scan_shard(&self, index: usize, mut on_candidate: impl FnMut(TaskId)) { let shard = self.map.shards()[index].read(); for (task_id, task) in shard.iter() { - if task_id.is_transient() { - continue; + if task.gc_maybe_collectible() { + on_candidate(*task_id); } - f(*task_id, task); } } - /// Scans a **single** shard by index, invoking `on_candidate` for each resident, non-transient - /// task whose storage passes the cheap [`TaskStorage::gc_maybe_collectible`] pre-filter. - pub fn gc_scan_shard(&self, index: usize, mut on_candidate: impl FnMut(TaskId)) { - self.for_each_resident_persistent_in_shard(index, |task_id, storage| { - if storage.gc_maybe_collectible() { - on_candidate(task_id); - } - }); - } - /// Return the set of all known live roots. pub fn gc_scan_roots(&self) -> impl Iterator { // Roots that failed the "held by a transient pin" expectation, with the referencing tasks @@ -605,20 +579,21 @@ impl Storage { let per_shard: Vec> = parallel::map_collect(&(0..self.shard_count()).collect::>(), |&index| { let mut roots = Vec::new(); - self.for_each_resident_persistent_in_shard(index, |task_id, storage| { - if storage.gc_is_root() { - // The `is_root` criteria is conservative, in debug assert that we aren't - // marking things as roots for surprising reasons + let shard = self.map.shards()[index].read(); + for (task_id, task) in shard.iter() { + if !task_id.is_transient() && task.gc_is_root() { + // The `is_root` criteria is conservative, in debug assert that we + // aren't marking things as roots for surprising reasons #[cfg(debug_assertions)] - if !storage.gc_is_held_by_transient_pin() { + if !task.gc_is_held_by_transient_pin() { unexpected .lock() .unwrap() - .push((task_id, storage.gc_root_holders())); + .push((*task_id, task.gc_root_holders())); } - roots.push(task_id); + roots.push(*task_id); } - }); + } roots }); @@ -743,20 +718,22 @@ impl Storage { } }; shard.retain(|(task_id, task)| { - if task_id.is_transient() { + // Transient tasks can not be evicted at all, unless they are fully + // delete by the GC. + if task_id.is_transient() && !task.flags.deleted() { evicted.unevictable_reasons[UnevictableReason::Transient.index()] += 1; return true; } - // GC'd tasks were tombstoned during the snapshot so we can drop them fully now. + // All GC'd tasks were tombstoned during the snapshot (or are not persisted) so we + // can drop them fully now. if task.flags.deleted() { - let task_type = task - .get_persistent_task_type() - .expect("GC deleted tasks must have a task type"); - remove_from_task_cache( - &mut evicted, - &mut deferred_task_cache_removals, - task_type, - ); + if let Some(task_type) = task.get_persistent_task_type() { + remove_from_task_cache( + &mut evicted, + &mut deferred_task_cache_removals, + task_type, + ); + } evicted.full += 1; return false; } diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs b/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs index 36582f84e8ba..cf317e758ee5 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs @@ -868,14 +868,26 @@ impl TaskStorage { } /// Whether a GC pass may collect this task: nothing references it, via parents, transient - /// pins, aggregation edges, or dependency edges. + /// pins, or aggregation edges. /// - /// Precision depends on what the caller restored. Meta alone cannot see the three Data-category - /// dependent sets, so the answer is a sound *pre-filter*: a `false` is definitive, a `true` may - /// still have dependents. With Meta + Data it is the full predicate. That one-directional - /// conservatism lets the cheap Meta-only shard scan and the authoritative under-guard recheck - /// (which opens `TaskDataCategory::All`, and is what actually gates collection) share this - /// single predicate. + /// Only reads `Meta`, so the shard scan and the under-guard recheck get the same answer -- it + /// is exact in both, not a pre-filter. + /// + /// The `Data`-category dependent sets are deliberately not consulted: + /// + /// - `cell_dependents` / `cell_dependents_hashed` are redundant with ancestry. A cell dependent + /// is either a child, whose child edge already orders the teardown, or a sibling reached by + /// passing a `ResolvedVc` laterally, which needs a common ancestor that collects both in the + /// same pass. Counting them deadlocked the caller/callee cycle `NftJsonAsset::content` -> + /// `all_assets_from_entries_filtered`, whose tasks could then never be collected. + /// - `output_dependent` is redundant with `parent_count`. It records a read of a task's + /// *output*, which is the `OperationVc` representation, and those reads go through + /// `connect()` -- so the reader is already a child. (A `ResolvedVc` read, the one that + /// travels laterally as an argument, lands in `cell_dependents` instead.) + /// + /// Removing the cell sets exposed a race in the GC cascade -- rebalancing running while other + /// workers were still collecting -- which `gc_collect` now avoids by deferring all rebalance + /// work until the parallel phase is quiescent. pub fn gc_maybe_collectible(&self) -> bool { // None of the predicates below are correct without this. self.flags.is_restored(TaskDataCategory::Meta) @@ -890,18 +902,12 @@ impl TaskStorage { // It is rare for an upper to be present when the ref counts are 0, but it // happens transiently during a concurrent GC pass as uppers move around in the cascade. && self.upper().is_empty() - // `collectibles_dependents` is Meta, so it is always checkable here. + // It would be rare for a collectibles dependent to be the only thing holding a task, + // but the invalidation that disconnected the task may not have bubbled all the way up + // yet. && self .collectibles_dependents() .is_none_or(|d| d.is_empty()) - // The remaining dependent sets are Data; skipped (leaving this a pre-filter) when Data - // is not restored. - && (!self.flags.is_restored(TaskDataCategory::Data) - || (self.output_dependent().is_empty() - && self.cell_dependents().is_none_or(|d| d.is_empty()) - && self - .cell_dependents_hashed() - .is_none_or(|d| d.is_empty()))) } /// Whether this task is a GC **root**: parent-less, but pinned for some reason diff --git a/turbopack/crates/turbo-tasks-backend/tests/gc_collection.rs b/turbopack/crates/turbo-tasks-backend/tests/gc_collection.rs index 924801f9234b..bcfaf36709ac 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/gc_collection.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/gc_collection.rs @@ -259,7 +259,16 @@ async fn dispose_root_task_releases_anchored_subgraph() { turbo_tasks::run_once(tt.clone(), async move { anyhow::Ok(()) }) .await .unwrap(); - assert_eq!(tt.backend().gc_for_testing(&tt), 1); + // Two: the leaf, and the disposed root_task` itself. The `root_task` is a transient task, and + // transient tasks are collectible, so releasing the last reference to the subgraph reclaims + // both. + assert_eq!( + tt.backend() + .snapshot_and_evict_for_testing(&tt) + .gc_stats() + .collected, + 2 + ); // Disposal after the backend has stopped (the whole task map is dropped by `stop`), as a // `RootTask` finalized during Node worker teardown would be. diff --git a/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs b/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs index 9bcd9661c548..755111b51473 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs @@ -115,11 +115,11 @@ async fn gc_rebalances_aggregation_and_cascades_in_one_pass() { result.unwrap(); // Baseline resident count with the reader subtree disconnected but not yet collected. - let baseline = tt2.backend().resident_persistent_task_count_for_testing(); + let baseline = tt2.backend().resident_task_count_for_testing(); let collected = tt2.backend().gc_for_testing(&tt2); tt2.backend().snapshot_and_evict_for_testing(&tt2); - let after = tt2.backend().resident_persistent_task_count_for_testing(); + let after = tt2.backend().resident_task_count_for_testing(); assert_eq!( collected, @@ -172,11 +172,11 @@ async fn gc_diamond_forward_dep_no_resurrection() { .await; result.unwrap(); - let baseline = tt2.backend().resident_persistent_task_count_for_testing(); + let baseline = tt2.backend().resident_task_count_for_testing(); let collected = tt2.backend().gc_for_testing(&tt2); tt2.backend().snapshot_and_evict_for_testing(&tt2); - let after = tt2.backend().resident_persistent_task_count_for_testing(); + let after = tt2.backend().resident_task_count_for_testing(); assert_eq!( collected, diff --git a/turbopack/crates/turbo-tasks-backend/tests/gc_stress.rs b/turbopack/crates/turbo-tasks-backend/tests/gc_stress.rs index cad455120736..9c3339fad654 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/gc_stress.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/gc_stress.rs @@ -66,7 +66,7 @@ async fn gc_re_rooting_stays_flat() { // independently of GC — including them would mask the real signal. ( collected, - tt.backend().resident_persistent_task_count_for_testing(), + tt.backend().resident_task_count_for_testing(), interrupted, ) } From c947937bf4ab13c1138a25efa2b7922cc49e1403 Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Thu, 24 Sep 2026 19:27:03 -0700 Subject: [PATCH 04/13] [turbopack] only remove followers if they are actually removed as children (#98843) ## What When removing edges, only enqueue `lost follower` jobs when we actually remove children. ## Why GC can race with other kinds of task completion which also remove outgoing edges. If task completion drops a child from a task that also becomes collectible. Then it is possible for there to be an enqueued CleanupOldEdges job at the time GC runs. Then we will have two jobs that remove children, only one will succeed but both will enqueue `InnerOfUppersLostFollowersJob` for all the chidren. This can lead to panics since one of those jobs will fail to remove the follower The fix is simple, only remove tasks as followers when you actually removed them as children. --- .../src/backend/operation/cleanup_old_edges.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs index 3d67476cee36..592e3046ad75 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs @@ -167,15 +167,18 @@ impl CleanupOldEdgesOperation { let parent_is_transient = task_id.is_transient(); let mut removed_durable = SmallVec::<[TaskId; 4]>::new(); let mut removed_transient = SmallVec::<[TaskId; 4]>::new(); - for child_id in children.iter() { + children.retain(|child_id| { if task.remove_children(child_id) { if parent_is_transient || child_id.is_transient() { removed_transient.push(*child_id); } else { removed_durable.push(*child_id); } + true + } else { + false } - } + }); if !removed_durable.is_empty() { queue.push(AggregationUpdateJob::AdjustParentCount { task_ids: removed_durable, From cb95ca373be557309a962d8a6e64a75c6732bc5d Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Thu, 24 Sep 2026 19:27:04 -0700 Subject: [PATCH 05/13] Fix root predicates (#98942) Fix the definition of GC roots to account for all transient references There was a categorical mistake in how `uppers` and `collectible_dependents` were interpreted. Both of these refer to a task above us and this is it is a reference but the reference might be comming from transient or persistent tasks. This is what kept confusing the 'root' definition So the new definition is simply * You are collectible if there are no references (persistent or transient) * You are a root if there are only transient references, which includes transient uppers and collectible_dependents The old definition expected roots to have no uppers at all, which isn't correct. A `run_once` task becomes an aggregation root and so the nested persistent tasks reference it as an upper. Those tasks should become gc roots since they are on the transient <--> persistent boundary and the only thing retaining them is references from transient tasks. --- .../turbo-tasks-backend/src/backend/mod.rs | 5 +- .../src/backend/operation/mod.rs | 2 +- .../src/backend/storage.rs | 59 +------- .../src/backend/storage_schema.rs | 128 +++++++----------- .../tests/gc_resurrection.rs | 10 +- 5 files changed, 61 insertions(+), 143 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 73d41ac4717a..27e2ee3e4ac2 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -1138,7 +1138,7 @@ impl TurboTasksBackend { parent_span: Option, reason: SnapshotReason, turbo_tasks: &TurboTasks, - ) -> Result<(Instant, bool, Option<(GcStats, GcPassResult)>), anyhow::Error> { + ) -> Result<(Instant, bool, Option<(GcStats, GcPassResult)>)> { let snapshot_span = tracing::trace_span!(parent: parent_span.clone(), "snapshot", reason = reason.as_str()) .entered(); @@ -1158,7 +1158,6 @@ impl TurboTasksBackend { let mut snapshot_phase = self.snapshot_coord.begin_snapshot(); let (gc_elapsed, gc_roots_to_persist, gc_outcome) = if self.gc_enabled { let gc_span = tracing::info_span!( - parent: parent_span.clone(), "gc", stats = tracing::field::Empty, interrupted = tracing::field::Empty @@ -1415,7 +1414,7 @@ impl TurboTasksBackend { }; } else { debug_assert!( - !inner.gc_maybe_collectible(), + !inner.gc_collectible(), "tasks scheduled for persistent must not be collectible, this implies a \ missed task during GC" ); diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs index 8f07f54d3ecc..e05634e2ac56 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs @@ -1585,7 +1585,7 @@ pub trait TaskGuard: Debug + TaskStorageAccessors { /// Whether a GC pass may collect this task: nothing references it. fn is_gc_collectible(&self) -> bool { self.check_access(SpecificTaskDataCategory::Meta); - self.typed().gc_maybe_collectible() + self.typed().gc_collectible() } fn invalidate_serialization(&mut self); diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs b/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs index 356b8ced33e4..921f2e801846 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/storage.rs @@ -16,8 +16,6 @@ use tracing::span::Id; use turbo_bincode::TurboBincodeBuffer; use turbo_tasks::{FxDashMap, TaskId, backend::CachedTaskTypeArc, event::Event, parallel}; -#[cfg(debug_assertions)] -use crate::backend::storage_schema::GcRootHolders; use crate::{ backend::storage_schema::{ DropPartialOutcome, KeyEvictability, TaskStorage, UnevictableReason, ValueEvictability, @@ -558,11 +556,11 @@ impl Storage { } /// Scans a **single** shard by index, invoking `on_candidate` for each resident task whose - /// storage passes [`TaskStorage::gc_maybe_collectible`]. + /// storage passes [`TaskStorage::gc_collectible`]. pub fn gc_scan_shard(&self, index: usize, mut on_candidate: impl FnMut(TaskId)) { let shard = self.map.shards()[index].read(); for (task_id, task) in shard.iter() { - if task.gc_maybe_collectible() { + if task.gc_collectible() { on_candidate(*task_id); } } @@ -570,69 +568,18 @@ impl Storage { /// Return the set of all known live roots. pub fn gc_scan_roots(&self) -> impl Iterator { - // Roots that failed the "held by a transient pin" expectation, with the referencing tasks - // that kept them un-collectible. Collected during the scan and reported *after* it: naming - // a dependent means reading its storage, and the shard locks are held inside the closure. - #[cfg(debug_assertions)] - let unexpected = std::sync::Mutex::new(Vec::<(TaskId, GcRootHolders)>::new()); - let per_shard: Vec> = parallel::map_collect(&(0..self.shard_count()).collect::>(), |&index| { let mut roots = Vec::new(); let shard = self.map.shards()[index].read(); for (task_id, task) in shard.iter() { if !task_id.is_transient() && task.gc_is_root() { - // The `is_root` criteria is conservative, in debug assert that we - // aren't marking things as roots for surprising reasons - #[cfg(debug_assertions)] - if !task.gc_is_held_by_transient_pin() { - unexpected - .lock() - .unwrap() - .push((*task_id, task.gc_root_holders())); - } roots.push(*task_id); } } roots }); - #[cfg(debug_assertions)] - { - use std::fmt::Write as _; - let unexpected = unexpected.into_inner().unwrap(); - if !unexpected.is_empty() { - let mut report = String::new(); - fn describe_task(storage: &Storage, task_id: TaskId) -> String { - storage - .access_mut(task_id) - .get_persistent_task_type() - // `NativeFunction`'s `Debug` is the public view of its name fields. - .map(|t| format!("{:?}", t.native_fn)) - .unwrap_or_else(|| "".to_string()) - } - for (task_id, holders) in &unexpected { - let _ = writeln!( - report, - " {} ({task_id:?}) is held by:", - describe_task(self, *task_id) - ); - for (kind, holder) in holders.iter() { - let _ = writeln!( - report, - " via {kind}: {} ({holder:?})", - describe_task(self, *holder) - ); - } - } - panic!( - "{} GC root(s) held by a non-transient pin.\nBeing held by another kind of \ - reference implies a bug in GC or the aggregation graph.\n{report}", - unexpected.len() - ); - } - } - per_shard.into_iter().flatten() } @@ -1244,7 +1191,7 @@ mod tests { let task = storage.access_mut(task_id); assert_eq!(task.gc_transient_ref_count(), 1); - assert!(!task.gc_maybe_collectible()); + assert!(!task.gc_collectible()); } /// A process fn that returns a non-empty SnapshotItem so the iterator doesn't diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs b/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs index cf317e758ee5..cfea1bffc6f9 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs @@ -867,7 +867,7 @@ impl TaskStorage { new_value } - /// Whether a GC pass may collect this task: nothing references it, via parents, transient + /// Whether a GC pass can collect this task: nothing references it, via parents, transient /// pins, or aggregation edges. /// /// Only reads `Meta`, so the shard scan and the under-guard recheck get the same answer -- it @@ -888,95 +888,67 @@ impl TaskStorage { /// Removing the cell sets exposed a race in the GC cascade -- rebalancing running while other /// workers were still collecting -- which `gc_collect` now avoids by deferring all rebalance /// work until the parallel phase is quiescent. - pub fn gc_maybe_collectible(&self) -> bool { + pub fn gc_collectible(&self) -> bool { + self.gc_unreferenced(ReferenceScope::All) + } + + /// Whether nothing in `scope` refers to this task. + fn gc_unreferenced(&self, scope: ReferenceScope) -> bool { // None of the predicates below are correct without this. - self.flags.is_restored(TaskDataCategory::Meta) + if !self.flags.is_restored(TaskDataCategory::Meta) // Already collected this session (soft-deleted, awaiting tombstone + hard-delete): // don't re-select it, or a second pass would collect it again while it is still // resident. - && !self.flags.deleted() - && self.gc_parent_count() == 0 - && self.gc_transient_ref_count() == 0 - && self.get_activeness().is_none() - && self.get_in_progress().is_none() - // It is rare for an upper to be present when the ref counts are 0, but it - // happens transiently during a concurrent GC pass as uppers move around in the cascade. - && self.upper().is_empty() - // It would be rare for a collectibles dependent to be the only thing holding a task, - // but the invalidation that disconnected the task may not have bubbled all the way up - // yet. - && self - .collectibles_dependents() - .is_none_or(|d| d.is_empty()) - } - - /// Whether this task is a GC **root**: parent-less, but pinned for some reason - /// - /// NOTE: this is a conservative classification. The typical reason is that there is a - /// [`TaskStorage::transient_ref`] live, but this will return true if there is merely an - /// `upper`. - pub fn gc_is_root(&self) -> bool { - self.flags.is_restored(TaskDataCategory::Meta) - && !self.flags.deleted() - && self.gc_parent_count() == 0 - && !self.gc_maybe_collectible() - } - - /// Whether this task is held by a pin that eviction cannot drop, which is what a task - /// classified by [`TaskStorage::gc_is_root`] is expected to be held by. - #[cfg(debug_assertions)] - pub fn gc_is_held_by_transient_pin(&self) -> bool { - self.gc_transient_ref_count() > 0 - || self.get_in_progress().is_some() - || self.get_activeness().is_some() - } - - /// The concrete references keeping this task un-collectible, as `(edge kind, holder task)` - /// pairs. - #[cfg(debug_assertions)] - pub fn gc_root_holders(&self) -> GcRootHolders { - let mut holders = GcRootHolders::default(); - for (&upper, _) in self.upper().iter() { - holders.push("upper", upper); - } - if let Some(deps) = self.collectibles_dependents() { - for &(_, task) in deps.iter() { - holders.push("collectibles_dependent", task); - } - } - for &task in self.output_dependent().iter() { - holders.push("output_dependent", task); + || self.flags.deleted() + || self.gc_parent_count() != 0 + { + return false; } - if let Some(deps) = self.cell_dependents() { - // In a `cell_dependents` entry `CellRef.task` is the DEPENDENT's id, not this task's. - for cell_ref in deps.iter() { - holders.push("cell_dependent", cell_ref.task); + match scope { + ReferenceScope::All => { + // It is rare for an upper to be present when the ref counts are 0, but it happens + // transiently during a concurrent GC pass as uppers move around in the cascade. + self.upper().is_empty() + // It would be rare for a collectibles dependent to be the only thing holding a + // task, but the invalidation that disconnected the task may not have bubbled + // all the way up yet. + && self.collectibles_dependents().is_none_or(|d| d.is_empty()) + && self.gc_transient_ref_count() == 0 + && self.get_in_progress().is_none() + && self.get_activeness().is_none() } - } - if let Some(deps) = self.cell_dependents_hashed() { - for (cell_ref, _) in deps.iter() { - holders.push("cell_dependent_hashed", cell_ref.task); + // Same two edge sets, minus the entries that die with the session. The pins above are + // skipped entirely: they are `category = "transient"` and never reach disk. + ReferenceScope::Persistent => { + self.upper().iter().all(|(u, _)| u.is_transient()) + && self + .collectibles_dependents() + .is_none_or(|d| d.iter().all(|(_, t)| t.is_transient())) } } - holders } -} -/// `(edge kind, holder task)` pairs explaining why a task is not collectible. -/// See [`TaskStorage::gc_root_holders`]. -#[cfg(debug_assertions)] -#[derive(Default, Debug)] -pub struct GcRootHolders(Vec<(&'static str, TaskId)>); - -#[cfg(debug_assertions)] -impl GcRootHolders { - fn push(&mut self, kind: &'static str, task: TaskId) { - self.0.push((kind, task)); + /// Whether this task is a GC **root**: nothing *persistent* refers to it, so only this session + /// is keeping it alive -- a `transient_ref` pin, an in-progress execution, activeness, or an + /// `upper` / collectibles edge from a transient task. + pub fn gc_is_root(&self) -> bool { + self.gc_unreferenced(ReferenceScope::Persistent) && !self.gc_collectible() } +} - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } +/// Which references [`TaskStorage::gc_unreferenced`] counts. +/// +/// A task can be held by references that outlive the session and by references that do not -- the +/// transient entries of `upper` / `collectibles_dependents`, and the `transient_ref_count`, +/// `in_progress` and `activeness` pins. The two GC predicates care about different subsets: +/// collectibility about everything currently holding the task, rootness about only what would +/// survive a restart. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum ReferenceScope { + /// Every referrer, transient ones included. + All, + /// Only referrers that outlive the session. + Persistent, } /// Counts for aggregation tree and collectibles fields. diff --git a/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs b/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs index 755111b51473..1531d36bbb47 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/gc_resurrection.rs @@ -87,7 +87,7 @@ async fn select_diamond( /// The **aggregation-graph rebalance** in GC: when the `reader` subtree is disconnected cleanly and /// collected, GC must remove `reader` from each `sd_leaf`'s `upper` set so the leaves — now /// parentless *and* upper-less — cascade-collect in the same pass. Without the rebalance a leaf -/// keeps a dangling `upper` edge to the deleted `reader`, fails `gc_maybe_collectible`, and leaks +/// keeps a dangling `upper` edge to the deleted `reader`, fails `gc_collectible`, and leaks /// until eviction hides it. #[tokio::test(flavor = "multi_thread", worker_threads = 8)] async fn gc_rebalances_aggregation_and_cascades_in_one_pass() { @@ -132,10 +132,10 @@ async fn gc_rebalances_aggregation_and_cascades_in_one_pass() { "resident count must drop by exactly the collected subtree" ); - // Only the three top-level `(operation, root)` tasks may be tracked as roots. A leaf that - // reached the post-drain scan still holding a dangling `upper` edge to the deleted `reader` - // would land in the map as `MostRecent`, which never ages out. If this fires it is a finding - // about the aggregation graph, not a reason to narrow `gc_is_root`. + // Only the three top-level `(operation, root)` tasks may be tracked as roots -- they are the + // ones held by a transient pin. A leaf still holding a dangling `upper` edge to the deleted + // `reader` is not a root (it fails `gc_unreferenced`), so it silently leaks rather than being + // tracked; the resident-count assertions above are what catch that. let roots = tt2.backend().persisted_gc_roots_for_testing(); assert_eq!(roots.len(), 3, "unexpected roots tracked: {roots:?}"); From 03383c3d469826f295459fc6f39dc80ae64844cf Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 25 Sep 2026 00:25:26 -0400 Subject: [PATCH 06/13] Remove the page/layout distinction from RouteTree (#98970) Part of a series of internal refactors to improve param tracking on the client. A route tree node used to come in two variants, a layout and a page, distinguished by an isPage flag that every constructor had to keep consistent with the VaryPath it built. The variants only differed in whether the vary path carried a search params entry, and the code that needed to know either checked the flag or read the vary path's shape positionally. Collapse them into one RouteTree type with one VaryPath type. The few places that still care whether a node is a page derive it from the segment. When something needs to read the search params, they can be accessed from the RouteTree or VaryPath like we do for other params. This is a pure refactor of the existing code. It reads more naturally with a single tree type and a single vary path type, which the rest of this stack builds on. --- .../next/src/client/components/render-tree.ts | 14 +- .../components/segment-cache/bfcache.ts | 14 +- .../components/segment-cache/cache-map.ts | 14 +- .../client/components/segment-cache/cache.ts | 158 ++++------ .../segment-cache/decode-server-response.ts | 48 +-- .../segment-cache/optimistic-routes.ts | 70 ++--- .../components/segment-cache/vary-path.ts | 278 +++++++++--------- .../next/src/server/app-render/vary-params.ts | 41 +-- .../lib/segment-cache/vary-params-decoding.ts | 20 +- .../segment-cache-question-mark-param.test.ts | 85 ++++++ 10 files changed, 372 insertions(+), 370 deletions(-) create mode 100644 test/e2e/app-dir/segment-cache/search-params/segment-cache-question-mark-param.test.ts diff --git a/packages/next/src/client/components/render-tree.ts b/packages/next/src/client/components/render-tree.ts index 47e34ef6f87a..3a95312ca0fa 100644 --- a/packages/next/src/client/components/render-tree.ts +++ b/packages/next/src/client/components/render-tree.ts @@ -45,7 +45,7 @@ import type { NormalizedSearch } from './segment-cache/cache-key' import type { CacheMap } from './segment-cache/cache-map' import { getRenderedSearchFromVaryPath, - type PageVaryPath, + type VaryPath, } from './segment-cache/vary-path' import { readFromBFCache, @@ -219,7 +219,7 @@ export function startPPRNavigation( oldCacheNode: CacheNode | null, oldRouterState: FlightRouterState, newRouteTree: RouteTree, - newMetadataVaryPath: PageVaryPath | null, + newMetadataVaryPath: VaryPath | null, freshness: FreshnessPolicy, seedHead: HeadData | null, seedDynamicStaleAt: number, @@ -264,7 +264,7 @@ function updateCacheNodeOnNavigation( oldCacheNode: CacheNode | void, oldRouterState: FlightRouterState, newRouteTree: RouteTree, - newMetadataVaryPath: PageVaryPath | null, + newMetadataVaryPath: VaryPath | null, freshness: FreshnessPolicy, seedHead: HeadData | null, seedDynamicStaleAt: number, @@ -656,7 +656,7 @@ function accumulateScrollRef( function createCacheNodeOnNavigation( navigatedAt: number, newRouteTree: RouteTree, - newMetadataVaryPath: PageVaryPath | null, + newMetadataVaryPath: VaryPath | null, freshness: FreshnessPolicy, seedHead: HeadData | null, seedDynamicStaleAt: number, @@ -780,7 +780,7 @@ function createCacheNodeOnNavigation( function createSegmentFromRouteTree( newRouteTree: RouteTree ): Segment { - if (newRouteTree.isPage) { + if (newRouteTree.segment === PAGE_SEGMENT_KEY) { // In a dynamic server response, the server embeds the search params into // the segment key, but in a static one it's omitted. The client handles // this inconsistency by adding the search params back right at the end. @@ -953,7 +953,7 @@ function createCacheNodeForSegment( now: number, tree: RouteTree, seedRsc: React.ReactNode | null, - metadataVaryPath: PageVaryPath | null, + metadataVaryPath: VaryPath | null, seedHead: HeadData | null, freshness: FreshnessPolicy, dynamicStaleAt: number, @@ -978,7 +978,7 @@ function createCacheNodeForSegment( // also be able to use that data without spawning a new request. (This is // referred to as the "seed" data.) - const isPage = tree.isPage + const isPage = tree.segment === PAGE_SEGMENT_KEY // During certain kinds of navigations, we may be able to render from // the BFCache. diff --git a/packages/next/src/client/components/segment-cache/bfcache.ts b/packages/next/src/client/components/segment-cache/bfcache.ts index d2def0b16b46..daaa95e46082 100644 --- a/packages/next/src/client/components/segment-cache/bfcache.ts +++ b/packages/next/src/client/components/segment-cache/bfcache.ts @@ -1,5 +1,5 @@ import { DYNAMIC_STALETIME_MS } from '../router-reducer/reducers/navigate-reducer' -import type { SegmentVaryPath } from './vary-path' +import type { VaryPath } from './vary-path' /** * Sentinel value indicating that no per-page dynamic stale time was provided. @@ -69,7 +69,7 @@ export function invalidateBfCache(): void { export function writeToBFCache( now: number, - varyPath: SegmentVaryPath, + varyPath: VaryPath, rsc: React.ReactNode, prefetchRsc: React.ReactNode, head: React.ReactNode, @@ -115,7 +115,7 @@ export function writeToBFCache( export function writeHeadToBFCache( now: number, - varyPath: SegmentVaryPath, + varyPath: VaryPath, head: React.ReactNode, prefetchHead: React.ReactNode, dynamicStaleAt: number, @@ -141,7 +141,7 @@ export function writeHeadToBFCache( * by the default DYNAMIC_STALETIME_MS. */ export function updateBFCacheEntryStaleAt( - varyPath: SegmentVaryPath, + varyPath: VaryPath, newStaleAt: number ): void { if (typeof window === 'undefined') { @@ -162,9 +162,7 @@ export function updateBFCacheEntryStaleAt( } } -export function readFromBFCache( - varyPath: SegmentVaryPath -): BFCacheEntry | null { +export function readFromBFCache(varyPath: VaryPath): BFCacheEntry | null { if (typeof window === 'undefined') { return null } @@ -184,7 +182,7 @@ export function readFromBFCache( export function readFromBFCacheDuringRegularNavigation( now: number, - varyPath: SegmentVaryPath + varyPath: VaryPath ): BFCacheEntry | null { if (typeof window === 'undefined') { return null diff --git a/packages/next/src/client/components/segment-cache/cache-map.ts b/packages/next/src/client/components/segment-cache/cache-map.ts index eebc1581aa04..0787cde68ac8 100644 --- a/packages/next/src/client/components/segment-cache/cache-map.ts +++ b/packages/next/src/client/components/segment-cache/cache-map.ts @@ -1,4 +1,4 @@ -import type { VaryPath } from './vary-path' +import type { VaryPathNode } from './vary-path' import { lruPut, updateLruSize, deleteFromLru } from './lru' /** @@ -160,7 +160,7 @@ export function createCacheMap(): CacheMap { function getOrInitialize( cacheMap: CacheMap, - keys: VaryPath, + keys: VaryPathNode, isRevalidation: boolean ): MapEntry { // Go through each level of keys until we find the entry that matches, or @@ -170,7 +170,7 @@ function getOrInitialize( // Unlike getWithFallback, it will not access fallback entries unless it's // explicitly part of the keypath. let entry = cacheMap - let remainingKeys: VaryPath | null = keys + let remainingKeys: VaryPathNode | null = keys let key: unknown | null = null while (true) { const previousKey = key @@ -230,7 +230,7 @@ export function getFromCacheMap( now: number, currentCacheVersion: number, rootEntry: CacheMap, - keys: VaryPath, + keys: VaryPathNode, isRevalidation: boolean, // When true, terminal entries whose status is not Fulfilled are skipped, so // the lookup falls through to a less-specific Fallback entry. Use this @@ -301,7 +301,7 @@ function getEntryWithFallbackImpl( now: number, currentCacheVersion: number, entry: MapEntry, - keys: VaryPath | null, + keys: VaryPathNode | null, isRevalidation: boolean, previousKey: unknown | null, onlyMatchFulfilled: boolean @@ -317,7 +317,7 @@ function getEntryWithFallbackImpl( // are treated as non-matches, so the recursion will continue searching for // a Fallback match. See getFromCacheMap for the rationale. let key - let remainingKeys: VaryPath | null + let remainingKeys: VaryPathNode | null if (keys !== null) { key = keys.value remainingKeys = keys.parent @@ -373,7 +373,7 @@ function getEntryWithFallbackImpl( export function setInCacheMap( cacheMap: CacheMap, - keys: VaryPath, + keys: VaryPathNode, value: V, isRevalidation: boolean ): void { diff --git a/packages/next/src/client/components/segment-cache/cache.ts b/packages/next/src/client/components/segment-cache/cache.ts index 2cd2bad5f876..6eb1ec265e4d 100644 --- a/packages/next/src/client/components/segment-cache/cache.ts +++ b/packages/next/src/client/components/segment-cache/cache.ts @@ -4,7 +4,10 @@ import { PrefetchHint, StaticAttemptHints, } from '../../../shared/lib/app-router-types' -import type { VaryParams } from '../../../shared/lib/segment-cache/vary-params-decoding' +import { + SEARCH_PARAMS_VARY_ID, + type VaryParams, +} from '../../../shared/lib/segment-cache/vary-params-decoding' import { readFulfilledValue } from '../../../shared/lib/rsc-transport' import { NEXT_DID_POSTPONE_HEADER, @@ -33,18 +36,15 @@ import { } from './scheduler' import { type RouteVaryPath, - type SegmentVaryPath, - type PartialSegmentVaryPath, + type VaryPath, + type PartialVaryPath, getRouteVaryPath, getFulfilledRouteVaryPath, getFulfilledSegmentVaryPath, getSegmentVaryPathForRequest, getShellSegmentVaryPath, - clonePageVaryPathWithNewSearchParams, - type PageVaryPath, - type LayoutVaryPath, - getPartialPageVaryPath, - getPartialLayoutVaryPath, + cloneVaryPathWithNewSearchParams, + getPartialVaryPath, getRenderedSearchFromVaryPath, } from './vary-path' import { createHrefFromUrl } from '../router-reducer/create-href-from-url' @@ -199,17 +199,18 @@ export type RSCSegmentData = { staleTimeSeconds: number | null } -type RouteTreeShared = { +export type RouteTree = { requestKey: SegmentRequestKey // TODO: Remove the `segment` field, now that it can be reconstructed // from `param`. segment: FlightRouterStateSegment + varyPath: VaryPath // The vary path used for shell-scoped keying of this segment: the // segment's vary path with every non-root param replaced with Fallback // (see getShellSegmentVaryPath), so one shell-tier entry serves all param // values below the root. Precomputed once during tree construction so we // don't have to recompute it on every shell request. - shellVaryPath: SegmentVaryPath + shellVaryPath: VaryPath refreshState: RefreshState | null // Render output for this segment, when the tree was created from a server // response that rendered it. The type parameter encodes a lifecycle @@ -236,18 +237,6 @@ export type RefreshState = { renderedSearch: NormalizedSearch } -type LayoutRouteTree = RouteTreeShared & { - isPage: false - varyPath: LayoutVaryPath -} - -type PageRouteTree = RouteTreeShared & { - isPage: true - varyPath: PageVaryPath -} - -export type RouteTree = LayoutRouteTree | PageRouteTree - type RouteCacheEntryShared = { // This is false only if we're certain the route cannot be intercepted. It's // true in all other cases, including on initialization when we haven't yet @@ -612,7 +601,7 @@ export function readSegmentCacheEntryForNavigation( // The map the navigation is bound to: a locked navigation's driving-task // map, or the shared map otherwise. map: CacheMap, - varyPath: SegmentVaryPath, + varyPath: VaryPath, restrictToShell: boolean = false ): SegmentCacheEntry | null { const isRevalidation = false @@ -653,7 +642,7 @@ export function readSegmentCacheEntryForNavigation( function readRevalidatingSegmentCacheEntry( now: number, map: CacheMap, - varyPath: SegmentVaryPath + varyPath: VaryPath ): SegmentCacheEntry | null { const isRevalidation = true return getFromCacheMap( @@ -882,37 +871,20 @@ function deprecated_createOptimisticRouteTree( } } - // We only need to clone the vary path if the route is a page. - if (tree.isPage) { - // The shell vary path Fallbacks search params, so it's unaffected by the - // new rendered search and can be reused as-is. - return { - requestKey: tree.requestKey, - segment: tree.segment, - shellVaryPath: tree.shellVaryPath, - refreshState: tree.refreshState, - // Optimistic trees are structure-only. (The input tree comes from the - // route cache, which never carries render output.) - data: null, - varyPath: clonePageVaryPathWithNewSearchParams( - tree.varyPath, - newRenderedSearch - ), - isPage: true, - slots: clonedSlots, - - prefetchHints: tree.prefetchHints, - } - } - + // The shell vary path Fallbacks search params, so it's unaffected by the + // new rendered search and can be reused as-is. return { requestKey: tree.requestKey, segment: tree.segment, shellVaryPath: tree.shellVaryPath, refreshState: tree.refreshState, + // Optimistic trees are structure-only. (The input tree comes from the + // route cache, which never carries render output.) data: null, - varyPath: tree.varyPath, - isPage: false, + varyPath: cloneVaryPathWithNewSearchParams( + tree.varyPath, + newRenderedSearch + ), slots: clonedSlots, prefetchHints: tree.prefetchHints, } @@ -1082,7 +1054,7 @@ export function upsertSegmentEntry( // testing-lock scope boundary still writes into the map its entries // live in. map: CacheMap, - varyPath: SegmentVaryPath, + varyPath: VaryPath, candidateEntry: SegmentCacheEntry, // The fully concrete vary path a read for this segment position resolves // against (all concrete param values, i.e. `tree.varyPath`) — the most @@ -1091,7 +1063,7 @@ export function upsertSegmentEntry( // Used to detect and evict stale entries at more specific keypaths that // would otherwise shadow the candidate. Pass null when there's no request // context; the shadow check is skipped. - lookupVaryPath: SegmentVaryPath | null + lookupVaryPath: VaryPath | null ): SegmentCacheEntry | null { // We have a new entry that has not yet been inserted into the cache. Before // we do so, we need to confirm whether it takes precedence over the existing @@ -1208,7 +1180,7 @@ export function upsertSegmentEntry( function evictShadowingSegmentEntries( now: number, map: CacheMap, - lookupVaryPath: SegmentVaryPath, + lookupVaryPath: VaryPath, candidateEntry: SegmentCacheEntry ): void { // There can in principle be multiple shadowing entries at successively less @@ -1428,7 +1400,7 @@ function pingBlockedTasks(entry: { } export function createMetadataRouteTree( - metadataVaryPath: PageVaryPath, + metadataVaryPath: VaryPath, // The route root's prefetch hints. The head has no node of its own on the // wire, so route-level hints are read from the root on its behalf — the // same convention as pingStaticHead in scheduler.ts. @@ -1445,10 +1417,6 @@ export function createMetadataRouteTree( refreshState: null, data: null, varyPath: metadataVaryPath, - // The metadata isn't really a "page" (though it isn't really a "segment" - // either) but for the purposes of how this field is used, it behaves like - // one. If this logic ever gets more complex we can change this to an enum. - isPage: true, slots: null, // Only the static-attempt bits apply to the head: it's a route-level // fact ("static per-segment responses may exist for this route"), and @@ -1466,7 +1434,7 @@ export function createMetadataRouteTree( * the subtrees that carry data. Called when a tree is stored in the route * cache: route cache entries live indefinitely, so retaining render output * there would pin RSC payloads in memory outside the segment cache's eviction - * control. See the lifecycle note on RouteTreeShared. + * control. See the lifecycle note on RouteTree. */ function stripDataFromRouteTree( tree: RouteTree @@ -1499,19 +1467,6 @@ function stripDataFromRouteTree( string, RouteTree > | null - if (tree.isPage) { - return { - requestKey: tree.requestKey, - segment: tree.segment, - shellVaryPath: tree.shellVaryPath, - refreshState: tree.refreshState, - data: null, - varyPath: tree.varyPath, - isPage: true, - slots: strippedSlots, - prefetchHints: tree.prefetchHints, - } - } return { requestKey: tree.requestKey, segment: tree.segment, @@ -1519,7 +1474,6 @@ function stripDataFromRouteTree( refreshState: tree.refreshState, data: null, varyPath: tree.varyPath, - isPage: false, slots: strippedSlots, prefetchHints: tree.prefetchHints, } @@ -1529,7 +1483,7 @@ export function fulfillRouteCacheEntry( now: number, entry: PendingRouteCacheEntry, tree: RouteTree, - metadataVaryPath: PageVaryPath, + metadataVaryPath: VaryPath, couldBeIntercepted: boolean, canonicalUrl: string, supportsPerSegmentPrefetching: boolean @@ -1573,7 +1527,7 @@ export function writeRouteIntoCache( search: NormalizedSearch, nextUrl: string | null, tree: RouteTree, - metadataVaryPath: PageVaryPath, + metadataVaryPath: VaryPath, couldBeIntercepted: boolean, canonicalUrl: string, supportsPerSegmentPrefetching: boolean @@ -1665,7 +1619,7 @@ function rejectSegmentCacheEntry( } export type RouteTreeAccumulator = { - metadataVaryPath: PageVaryPath | null + metadataVaryPath: VaryPath | null // Whether the decoded tree's segment identities diverged from the base // tree it was overlaid onto. See NavigationSeed.treeDivergedFromBase. treeDivergedFromBase: boolean @@ -1698,9 +1652,7 @@ export function convertReusedFlightRouterStateToRouteTree( // Unlike a FlightRouterState, the RouteTree type contains backreferences to // the parent segments. Append the vary path to the parent's vary path. - const parentPartialVaryPath = parentRouteTree.isPage - ? getPartialPageVaryPath(parentRouteTree.varyPath) - : getPartialLayoutVaryPath(parentRouteTree.varyPath) + const parentPartialVaryPath = getPartialVaryPath(parentRouteTree.varyPath) const segment = flightRouterState[0] // And the request key. const parentRequestKey = parentRouteTree.requestKey @@ -1722,7 +1674,7 @@ export function convertReusedFlightRouterStateToRouteTree( export function convertFlightRouterStateToRouteTree( flightRouterState: FlightRouterState, requestKey: SegmentRequestKey, - parentPartialVaryPath: PartialSegmentVaryPath | null, + parentPartialVaryPath: PartialVaryPath | null, parentRenderedSearch: NormalizedSearch, acc: RouteTreeAccumulator ): RouteTree { @@ -1757,9 +1709,7 @@ export function convertFlightRouterStateToRouteTree( acc ) tree.refreshState = refreshState - const partialVaryPath = tree.isPage - ? getPartialPageVaryPath(tree.varyPath) - : getPartialLayoutVaryPath(tree.varyPath) + const partialVaryPath = getPartialVaryPath(tree.varyPath) let slots: Map> | null = null @@ -2369,10 +2319,8 @@ async function fetchAndWritePerSegmentPrefetchResponse( // no tree position, so the decode could only derive a vary path for it // from a page node in the payload's own tree, which a standalone head // response (a bare root identity) doesn't have. - // (createMetadataRouteTree stores a PageVaryPath in `varyPath`, so the - // cast is sound.) const now = Date.now() - const metadataVaryPath = route.metadata.varyPath as PageVaryPath + const metadataVaryPath = route.metadata.varyPath writeResponsePayloadsIntoCache( now, fetchStrategy, @@ -2776,7 +2724,7 @@ function writeResponsePayloadsIntoCache( // (Per-segment payloads encode partiality per node and ignore the // response-level value entirely.) isFullResponsePartial: boolean, - metadataVaryPath: PageVaryPath | null, + metadataVaryPath: VaryPath | null, // The pending entries this response fulfills. Null when the caller owns // none (the embedded runtime prefetch stream), in which case every write // is a detached upsert. @@ -3099,7 +3047,7 @@ function writeServerResponseIntoCache( // Where to key the head. Null derives it from the decoded tree's first // page node; per-segment payloads pass the route's own metadata vary path // instead, since a standalone head response's tree has no page node. - metadataVaryPath: PageVaryPath | null, + metadataVaryPath: VaryPath | null, spawnedEntries: Map | null, // The strategy tier describing the CONTENT of the payload being written, // when it differs from `fetchStrategy` (which drives matching and @@ -3384,7 +3332,7 @@ function writeSegmentDataIntoCache( rsc: React.ReactNode, isPartial: boolean, staleAt: number, - segmentVaryParams: Set | null, + segmentVaryParams: VaryParams | null, tree: RouteTree, spawnedEntries: Map | null, // The strategy tier describing the CONTENT of the payload this write came @@ -3496,26 +3444,30 @@ function writeSegmentDataIntoCache( // is good for any value of them (its request path below IS the shell // vary path). const payloadStrategy = contentFetchStrategy ?? fetchStrategy - let fulfilledVaryPath: SegmentVaryPath | null = null + let fulfilledVaryPath: VaryPath | null = null if ( process.env.__NEXT_VARY_PARAMS && payloadStrategy !== FetchStrategy.Full && segmentVaryParams !== null ) { let varyParams = segmentVaryParams - if (payloadStrategy === FetchStrategy.RuntimeShell && varyParams.has('?')) { + if ( + payloadStrategy === FetchStrategy.RuntimeShell && + varyParams.has(SEARCH_PARAMS_VARY_ID) + ) { // SPECIAL CASE: for a RuntimeShell payload, the search params entry - // ('?') is dropped from the server's vary evidence before deriving the - // key, so the search component of the resulting path is marked as the - // fallback. This exists ONLY because of a known compromise in how the - // server reports search params: accessing `searchParams` records a - // dependency on '?' at access time, even when the render suspends on - // that access and cuts the content at the param fallback. A shell - // render's page and head segments therefore report '?' while the - // emitted bytes contain no search-dependent content. Trusting that - // report would key shell-grade content at a concrete search value, - // where shell-restricted reads (which generalize every non-root - // param — see getShellSegmentVaryPath) can never find it. A + // is dropped from the server's vary evidence before deriving the + // key, so the search component of the resulting path is marked as + // the fallback. This exists ONLY because of a known compromise in + // how the server reports search params: accessing `searchParams` + // records a dependency on them at access time, even when the render + // suspends on that access and cuts the content at the param + // fallback. A shell render's page and head segments therefore report + // the search params while the emitted bytes contain no + // search-dependent content. + // Trusting that report would key shell-grade content at a concrete + // search value, where shell-restricted reads (which generalize every + // non-root param — see getShellSegmentVaryPath) can never find it. A // RuntimeShell payload's search-dependent content is reduced to // fallbacks by construction, so its key must not vary on search // regardless of the over-reported evidence. Every other component of @@ -3530,7 +3482,7 @@ function writeSegmentDataIntoCache( // the emitted stage. A shell payload's evidence would then be // accurate, and this branch could be deleted. varyParams = new Set(varyParams) - varyParams.delete('?') + varyParams.delete(SEARCH_PARAMS_VARY_ID) } fulfilledVaryPath = getFulfilledSegmentVaryPath(tree.varyPath, varyParams) } @@ -3563,7 +3515,7 @@ function writeSegmentDataIntoCache( const isOwned = ownedEntry !== undefined && ownedEntry.status === EntryStatus.Pending let fulfilledEntry: FulfilledSegmentCacheEntry - let insertVaryPath: SegmentVaryPath | null + let insertVaryPath: VaryPath | null if (isOwned) { // We own this entry — fulfill it directly. fulfilledEntry = fulfillSegmentCacheEntry( diff --git a/packages/next/src/client/components/segment-cache/decode-server-response.ts b/packages/next/src/client/components/segment-cache/decode-server-response.ts index d54978986345..07ca19cf619e 100644 --- a/packages/next/src/client/components/segment-cache/decode-server-response.ts +++ b/packages/next/src/client/components/segment-cache/decode-server-response.ts @@ -45,18 +45,12 @@ import { } from '../../route-params' import type { NormalizedSearch } from './cache-key' import { splitPathnameIntoParts } from './cache-key' -import type { - PageVaryPath, - PartialSegmentVaryPath, - SegmentVaryPath, -} from './vary-path' +import type { PartialVaryPath, VaryPath } from './vary-path' import { appendLayoutVaryPath, - finalizeLayoutVaryPath, finalizeMetadataVaryPath, - finalizePageVaryPath, - getPartialLayoutVaryPath, - getPartialPageVaryPath, + finalizeVaryPath, + getPartialVaryPath, getShellSegmentVaryPath, } from './vary-path' import { @@ -72,7 +66,7 @@ import { computeDynamicStaleAt } from './bfcache' export type NavigationSeed = { renderedSearch: string routeTree: RouteTree - metadataVaryPath: PageVaryPath | null + metadataVaryPath: VaryPath | null head: HeadData | null isHeadPartial: boolean /** @@ -233,7 +227,7 @@ export function createNavigationSeed( /** * Creates a RouteTree node for a segment, with its identity and cache-key - * information (vary paths, page-ness, the normalized segment value) + * information (vary paths, the normalized segment value) * initialized, and the remaining fields set to their defaults. The caller * finishes initializing those in place after recursing into the children. * Shared by the FlightRouterState converter and the transport decoder so the @@ -244,16 +238,14 @@ export function createRouteTreeNode( originalSegment: FlightRouterStateSegment, isRootParam: boolean, requestKey: SegmentRequestKey, - parentPartialVaryPath: PartialSegmentVaryPath | null, + parentPartialVaryPath: PartialVaryPath | null, renderedSearch: NormalizedSearch, acc: RouteTreeAccumulator ): RouteTree { let segment: FlightRouterStateSegment - let partialVaryPath: PartialSegmentVaryPath | null - let isPage: boolean - let varyPath: SegmentVaryPath + let partialVaryPath: PartialVaryPath | null + let varyPath: VaryPath if (Array.isArray(originalSegment)) { - isPage = false const paramCacheKey = originalSegment[1] const paramName = originalSegment[0] partialVaryPath = appendLayoutVaryPath( @@ -262,7 +254,7 @@ export function createRouteTreeNode( paramName, isRootParam ) - varyPath = finalizeLayoutVaryPath(requestKey, partialVaryPath) + varyPath = finalizeVaryPath(requestKey, null, partialVaryPath) segment = originalSegment } else { // This segment does not have a param. Inherit the partial vary path of @@ -270,7 +262,6 @@ export function createRouteTreeNode( partialVaryPath = parentPartialVaryPath if (requestKey.endsWith(PAGE_SEGMENT_KEY)) { // This is a page segment. - isPage = true // The navigation implementation expects the search params to be included // in the segment. However, in the case of a static response, the search @@ -282,11 +273,7 @@ export function createRouteTreeNode( // TODO: We should move search params out of FlightRouterState and handle // them entirely on the client, similar to our plan for dynamic params. segment = PAGE_SEGMENT_KEY - varyPath = finalizePageVaryPath( - requestKey, - renderedSearch, - partialVaryPath - ) + varyPath = finalizeVaryPath(requestKey, renderedSearch, partialVaryPath) // The metadata "segment" is not part the route tree, but it has the same // conceptual params as a page segment. Write the vary path into the // accumulator object. If there are multiple parallel pages, we use the @@ -302,9 +289,8 @@ export function createRouteTreeNode( } } else { // This is a layout segment. - isPage = false segment = originalSegment - varyPath = finalizeLayoutVaryPath(requestKey, partialVaryPath) + varyPath = finalizeVaryPath(requestKey, null, partialVaryPath) } } return { @@ -313,11 +299,7 @@ export function createRouteTreeNode( shellVaryPath: getShellSegmentVaryPath(varyPath), refreshState: null, data: null, - // TODO: Cheating the type system here a bit because TypeScript can't tell - // that the type of isPage and varyPath are consistent. If isPage were - // wrong it would break the behavior and we'd catch it quickly. - varyPath: varyPath as any, - isPage: isPage as boolean as any, + varyPath, slots: null, prefetchHints: 0, } @@ -446,7 +428,7 @@ function decodeTransportNode( rootVaryParams: VaryParamsIterable | null, isResponsePartial: boolean, requestKey: SegmentRequestKey, - parentPartialVaryPath: PartialSegmentVaryPath | null, + parentPartialVaryPath: PartialVaryPath | null, parentRenderedSearch: NormalizedSearch, pathnameParts: Array | null, // The URL position this node's children read from. @@ -517,9 +499,7 @@ function decodeTransportNode( acc ) tree.refreshState = refreshState - const partialVaryPath = tree.isPage - ? getPartialPageVaryPath(tree.varyPath) - : getPartialLayoutVaryPath(tree.varyPath) + const partialVaryPath = getPartialVaryPath(tree.varyPath) let slots: Map> | null = null const transportChildren = node.c diff --git a/packages/next/src/client/components/segment-cache/optimistic-routes.ts b/packages/next/src/client/components/segment-cache/optimistic-routes.ts index 7dd849c8ee08..4a5cc077d6e6 100644 --- a/packages/next/src/client/components/segment-cache/optimistic-routes.ts +++ b/packages/next/src/client/components/segment-cache/optimistic-routes.ts @@ -45,6 +45,7 @@ import type { DynamicParamTypesShort } from '../../../shared/lib/app-router-types' import { PrefetchHint } from '../../../shared/lib/app-router-types' +import { PAGE_SEGMENT_KEY } from '../../../shared/lib/segment' import type { RouteTree, RSCSegmentData, @@ -67,12 +68,11 @@ import type { NormalizedPathname, NormalizedSearch } from './cache-key' import { splitPathnameIntoParts } from './cache-key' import { appendLayoutVaryPath, - finalizeLayoutVaryPath, - finalizePageVaryPath, finalizeMetadataVaryPath, + finalizeVaryPath, getShellSegmentVaryPath, - type PartialSegmentVaryPath, - type PageVaryPath, + type PartialVaryPath, + type VaryPath, } from './vary-path' /** @@ -239,7 +239,7 @@ export function discoverKnownRoute( nextUrl: string | null, pendingEntry: PendingRouteCacheEntry | null, routeTree: RouteTree, - metadataVaryPath: PageVaryPath, + metadataVaryPath: VaryPath, couldBeIntercepted: boolean, canonicalUrl: string, supportsPerSegmentPrefetching: boolean, @@ -317,7 +317,7 @@ function handleMismatchDueToRewrite( search: NormalizedSearch, nextUrl: string | null, fullTree: RouteTree, - metadataVaryPath: PageVaryPath, + metadataVaryPath: VaryPath, couldBeIntercepted: boolean, canonicalUrl: string, supportsPerSegmentPrefetching: boolean @@ -389,7 +389,7 @@ function discoverKnownRoutePart( search: NormalizedSearch, nextUrl: string | null, fullTree: RouteTree, - metadataVaryPath: PageVaryPath, + metadataVaryPath: VaryPath, couldBeIntercepted: boolean, canonicalUrl: string, supportsPerSegmentPrefetching: boolean, @@ -1010,7 +1010,7 @@ function matchKnownRoutePart( * (parallel routes may have multiple pages, but metadata uses the first). */ type ReifyAccumulator = { - metadataVaryPath: PageVaryPath | null + metadataVaryPath: VaryPath | null } /** @@ -1028,7 +1028,7 @@ function reifyRouteTree( pattern: RouteTree, resolvedParams: ResolvedParams, search: NormalizedSearch, - parentPartialVaryPath: PartialSegmentVaryPath | null, + parentPartialVaryPath: PartialVaryPath | null, acc: ReifyAccumulator ): RouteTree { const originalSegment = pattern.segment @@ -1039,7 +1039,7 @@ function reifyRouteTree( (pattern.prefetchHints & PrefetchHint.IsRootLayoutOrAbove) !== 0 let newSegment = originalSegment - let partialVaryPath: PartialSegmentVaryPath | null + let partialVaryPath: PartialVaryPath | null if (typeof originalSegment !== 'string') { // Dynamic segment: compute new cache key and append to partial vary path @@ -1087,13 +1087,10 @@ function reifyRouteTree( } } - if (pattern.isPage) { + let newVaryPath: VaryPath + if (originalSegment === PAGE_SEGMENT_KEY) { // Page segment: finalize with search params - const newVaryPath = finalizePageVaryPath( - pattern.requestKey, - search, - partialVaryPath - ) + newVaryPath = finalizeVaryPath(pattern.requestKey, search, partialVaryPath) // Collect metadata vary path (first page wins, same as original algorithm) if (acc.metadataVaryPath === null) { acc.metadataVaryPath = finalizeMetadataVaryPath( @@ -1102,36 +1099,21 @@ function reifyRouteTree( partialVaryPath ) } - return { - requestKey: pattern.requestKey, - segment: newSegment, - shellVaryPath: getShellSegmentVaryPath(newVaryPath), - refreshState: pattern.refreshState, - // Route cache patterns never carry seed data (see - // stripDataFromRouteTree), so neither do trees reified from them. - data: null, - varyPath: newVaryPath, - isPage: true, - slots: newSlots, - prefetchHints: pattern.prefetchHints, - } } else { // Layout segment: finalize without search params - const newVaryPath = finalizeLayoutVaryPath( - pattern.requestKey, - partialVaryPath - ) - return { - requestKey: pattern.requestKey, - segment: newSegment, - shellVaryPath: getShellSegmentVaryPath(newVaryPath), - refreshState: pattern.refreshState, - data: null, - varyPath: newVaryPath, - isPage: false, - slots: newSlots, - prefetchHints: pattern.prefetchHints, - } + newVaryPath = finalizeVaryPath(pattern.requestKey, null, partialVaryPath) + } + return { + requestKey: pattern.requestKey, + segment: newSegment, + shellVaryPath: getShellSegmentVaryPath(newVaryPath), + refreshState: pattern.refreshState, + // Route cache patterns never carry seed data (see + // stripDataFromRouteTree), so neither do trees reified from them. + data: null, + varyPath: newVaryPath, + slots: newSlots, + prefetchHints: pattern.prefetchHints, } } diff --git a/packages/next/src/client/components/segment-cache/vary-path.ts b/packages/next/src/client/components/segment-cache/vary-path.ts index 958970068b7d..1c352237d0ad 100644 --- a/packages/next/src/client/components/segment-cache/vary-path.ts +++ b/packages/next/src/client/components/segment-cache/vary-path.ts @@ -6,7 +6,14 @@ import type { } from './cache-key' import type { RouteTree, RSCSegmentData } from './cache' import { Fallback, type FallbackType } from './cache-map' -import { HEAD_REQUEST_KEY } from '../../../shared/lib/segment-cache/segment-value-encoding' +import { + HEAD_REQUEST_KEY, + type SegmentRequestKey, +} from '../../../shared/lib/segment-cache/segment-value-encoding' +import { + SEARCH_PARAMS_VARY_ID, + type VaryParamId, +} from '../../../shared/lib/segment-cache/vary-params-decoding' type Opaque = T & { __brand: K } @@ -25,17 +32,17 @@ type Opaque = T & { __brand: K } * A route's vary path is simpler: it's comprised of the pathname, search * string, and Next-URL header. */ -export type VaryPath = { +export type VaryPathNode = { /** * Identifies which param this vary path node corresponds to. Used by * getFulfilledSegmentVaryPath to determine which params to replace with * Fallback based on the varyParams set from the server. * * - For path params: the param name (e.g., 'slug') - * - For search params: '?' + * - For search params: SEARCH_PARAMS_VARY_ID * - For non-param nodes (request keys, etc.): null */ - id: string | null + id: VaryParamId | null value: string | null | FallbackType /** * Whether this node corresponds to a root param — a path param at or above @@ -44,11 +51,11 @@ export type VaryPath = { * Fallback. See getShellSegmentVaryPath. Only ever true on path param nodes; * false for structural and search param nodes. * - * Always a boolean (never undefined) so that every VaryPath node shares a + * Always a boolean (never undefined) so that every VaryPathNode shares a * single hidden class, keeping the cache hot paths monomorphic. */ isRootParam: boolean - parent: VaryPath | null + parent: VaryPathNode | null } // Because it's so important for vary paths to line up across cache accesses, @@ -62,7 +69,7 @@ export type RouteVaryPath = Opaque< value: NormalizedPathname isRootParam: false parent: { - id: '?' + id: typeof SEARCH_PARAMS_VARY_ID value: NormalizedSearch isRootParam: false parent: { @@ -76,38 +83,25 @@ export type RouteVaryPath = Opaque< 'RouteVaryPath' > -// requestKey -> pathParams -export type LayoutVaryPath = Opaque< +// requestKey -> [searchParams] -> pathParams +// +// The first entry is the request key (id: null). It is followed by the search +// params entry (id: SEARCH_PARAMS_VARY_ID) when the segment varies on search +// params, and then by the path params (id: param name), one entry per param, +// nearest first. +export type VaryPath = Opaque< { id: null - value: string + value: SegmentRequestKey isRootParam: false - parent: PartialSegmentVaryPath | null + parent: VaryPathNode | null }, - 'LayoutVaryPath' + 'VaryPath' > -// requestKey -> searchParams -> pathParams -export type PageVaryPath = Opaque< - { - id: null - value: string - isRootParam: false - parent: { - id: '?' - value: NormalizedSearch | FallbackType - isRootParam: false - parent: PartialSegmentVaryPath | null - } - }, - 'PageVaryPath' -> - -export type SegmentVaryPath = LayoutVaryPath | PageVaryPath - // Intermediate type used when building a vary path during a recursive traversal // of the route tree. -export type PartialSegmentVaryPath = Opaque +export type PartialVaryPath = Opaque export function getRouteVaryPath( pathname: NormalizedPathname, @@ -115,12 +109,12 @@ export function getRouteVaryPath( nextUrl: NormalizedNextUrl | null ): RouteVaryPath { // requestKey -> searchParams -> nextUrl - const varyPath: VaryPath = { + const varyPath: VaryPathNode = { id: null, value: pathname, isRootParam: false, parent: { - id: '?', + id: SEARCH_PARAMS_VARY_ID, value: search, isRootParam: false, parent: { @@ -143,12 +137,12 @@ export function getFulfilledRouteVaryPath( // This is called when a route's data is fulfilled. The cache entry will be // re-keyed based on which inputs the response varies by. // requestKey -> searchParams -> nextUrl - const varyPath: VaryPath = { + const varyPath: VaryPathNode = { id: null, value: pathname, isRootParam: false, parent: { - id: '?', + id: SEARCH_PARAMS_VARY_ID, value: search, isRootParam: false, parent: { @@ -163,73 +157,64 @@ export function getFulfilledRouteVaryPath( } export function appendLayoutVaryPath( - parentPath: PartialSegmentVaryPath | null, + parentPath: PartialVaryPath | null, cacheKey: string, paramName: string, isRootParam: boolean -): PartialSegmentVaryPath { - const varyPathPart: VaryPath = { +): PartialVaryPath { + const varyPathPart: VaryPathNode = { id: paramName, value: cacheKey, isRootParam, parent: parentPath, } - return varyPathPart as PartialSegmentVaryPath + return varyPathPart as PartialVaryPath } -export function finalizeLayoutVaryPath( - requestKey: string, - varyPath: PartialSegmentVaryPath | null -): LayoutVaryPath { - const layoutVaryPath: VaryPath = { - id: null, - value: requestKey, - isRootParam: false, - parent: varyPath, +export function finalizeVaryPath( + requestKey: SegmentRequestKey, + // Non-null when the segment varies on search params: the search entry is + // spliced in between the request key and the path params. Fallback keys an + // entry that is reusable across all search strings. + searchParams: NormalizedSearch | FallbackType | null, + partialVaryPath: PartialVaryPath | null +): VaryPath { + // requestKey -> [searchParams] -> pathParams + let parent: VaryPathNode | null = partialVaryPath + if (searchParams !== null) { + parent = { + id: SEARCH_PARAMS_VARY_ID, + value: searchParams, + isRootParam: false, + parent: partialVaryPath, + } } - return layoutVaryPath as LayoutVaryPath -} - -export function getPartialLayoutVaryPath( - finalizedVaryPath: LayoutVaryPath -): PartialSegmentVaryPath | null { - // This is the inverse of finalizeLayoutVaryPath. - return finalizedVaryPath.parent -} - -export function finalizePageVaryPath( - requestKey: string, - renderedSearch: NormalizedSearch, - varyPath: PartialSegmentVaryPath | null -): PageVaryPath { - // Unlike layouts, a page segment's vary path also includes the search string. - // requestKey -> searchParams -> pathParams - const pageVaryPath: VaryPath = { + const varyPath: VaryPathNode = { id: null, value: requestKey, isRootParam: false, - parent: { - id: '?', - value: renderedSearch, - isRootParam: false, - parent: varyPath, - }, + parent, } - return pageVaryPath as PageVaryPath + return varyPath as VaryPath } -export function getPartialPageVaryPath( - finalizedVaryPath: PageVaryPath -): PartialSegmentVaryPath | null { - // This is the inverse of finalizePageVaryPath. - return finalizedVaryPath.parent.parent +export function getPartialVaryPath( + finalizedVaryPath: VaryPath +): PartialVaryPath | null { + // This is the inverse of finalizeVaryPath: strip the request key, and the + // search params entry if there is one. + const parent = finalizedVaryPath.parent + if (parent !== null && parent.id === SEARCH_PARAMS_VARY_ID) { + return parent.parent as PartialVaryPath | null + } + return parent as PartialVaryPath | null } export function finalizeMetadataVaryPath( - pageRequestKey: string, + pageRequestKey: SegmentRequestKey, renderedSearch: NormalizedSearch, - varyPath: PartialSegmentVaryPath | null -): PageVaryPath { + varyPath: PartialVaryPath | null +): VaryPath { // The metadata "segment" is not a real segment because it doesn't exist in // the normal structure of the route tree, but in terms of caching, it // behaves like a page segment because it varies by all the same params as @@ -255,27 +240,21 @@ export function finalizeMetadataVaryPath( // This is fine because the only difference between request keys for // different parallel pages are things like route groups and parallel // route slots. As long as it's always the same one, it doesn't matter. - const pageVaryPath: VaryPath = { - id: null, - // Append the actual metadata request key to the page request key. Note - // that we're not using a separate vary path part; it's unnecessary because - // these are not conceptually separate inputs. - value: pageRequestKey + HEAD_REQUEST_KEY, - isRootParam: false, - parent: { - id: '?', - value: renderedSearch, - isRootParam: false, - parent: varyPath, - }, - } - return pageVaryPath as PageVaryPath + // + // Append the actual metadata request key to the page request key. Note + // that we're not using a separate vary path part; it's unnecessary because + // these are not conceptually separate inputs. + return finalizeVaryPath( + (pageRequestKey + HEAD_REQUEST_KEY) as SegmentRequestKey, + renderedSearch, + varyPath + ) } export function getSegmentVaryPathForRequest( fetchStrategy: FetchStrategy, tree: RouteTree -): SegmentVaryPath { +): VaryPath { // This is used for storing pending requests in the cache. We want to choose // the most generic vary path based on the strategy used to fetch it, i.e. // static/PPR versus runtime prefetching, so that it can be reused as much @@ -313,10 +292,13 @@ export function getSegmentVaryPathForRequest( return tree.shellVaryPath } - // Only page segments (and the special "metadata" segment, which is treated - // like a page segment for the purposes of caching) may contain search - // params. There's no reason to include them in the vary path otherwise. - if (tree.isPage) { + // The vary path includes a search params entry only when the segment varies + // on search params. + const searchParamsVaryPath = originalVaryPath.parent + if ( + searchParamsVaryPath !== null && + searchParamsVaryPath.id === SEARCH_PARAMS_VARY_ID + ) { // Only a runtime prefetch will include search params in the vary path. // Static prefetches never include search params, so they can be reused // across all possible search param values. @@ -325,67 +307,71 @@ export function getSegmentVaryPathForRequest( fetchStrategy === FetchStrategy.PPRRuntime if (!doesVaryOnSearchParams) { - // The response from the the server will not vary on search params. Clone - // the end of the original vary path to replace the search params - // with Fallback. + // The response from the the server will not vary on search params. + // Rebuild the vary path with the search params replaced by Fallback. // // requestKey -> searchParams -> pathParams // ^ This part gets replaced with Fallback - const searchParamsVaryPath = (originalVaryPath as PageVaryPath).parent - const pathParamsVaryPath = searchParamsVaryPath.parent - const patchedVaryPath: VaryPath = { - id: null, - value: originalVaryPath.value, - isRootParam: false, - parent: { - id: '?', - value: Fallback, - isRootParam: false, - parent: pathParamsVaryPath, - }, - } - return patchedVaryPath as SegmentVaryPath + return finalizeVaryPath( + originalVaryPath.value, + Fallback, + getPartialVaryPath(originalVaryPath) + ) } } // The request does vary on search params. We don't need to modify anything. - return originalVaryPath as SegmentVaryPath + return originalVaryPath } -export function clonePageVaryPathWithNewSearchParams( - originalVaryPath: PageVaryPath, +export function cloneVaryPathWithNewSearchParams( + originalVaryPath: VaryPath, newSearch: NormalizedSearch -): PageVaryPath { +): VaryPath { // requestKey -> searchParams -> pathParams // ^ This part gets replaced with newSearch const searchParamsVaryPath = originalVaryPath.parent - const clonedVaryPath: VaryPath = { - id: null, - value: originalVaryPath.value, - isRootParam: false, - parent: { - id: '?', - value: newSearch, - isRootParam: false, - parent: searchParamsVaryPath.parent, - }, + if ( + searchParamsVaryPath === null || + searchParamsVaryPath.id !== SEARCH_PARAMS_VARY_ID + ) { + // No search params entry; nothing to replace. + return originalVaryPath } - return clonedVaryPath as PageVaryPath + return finalizeVaryPath( + originalVaryPath.value, + newSearch, + getPartialVaryPath(originalVaryPath) + ) } +/** + * Returns the rendered value of the vary path's search params entry when the + * vary path has one with a concrete value, null otherwise. Only a segment that + * varies on search params carries the entry; on every other vary path, and on + * one whose search params entry is Fallback, this is null. + */ export function getRenderedSearchFromVaryPath( - varyPath: PageVaryPath + varyPath: VaryPath ): NormalizedSearch | null { - const searchParams = varyPath.parent.value - return typeof searchParams === 'string' - ? (searchParams as NormalizedSearch) - : null + let node: VaryPathNode | null = varyPath + while (node !== null) { + if (node.id === SEARCH_PARAMS_VARY_ID) { + const search = node.value + if (typeof search === 'string') { + return search as NormalizedSearch + } + return null + } + node = node.parent + } + return null } export function getFulfilledSegmentVaryPath( - original: VaryPath, - varyParams: Set -): SegmentVaryPath { + original: VaryPathNode, + varyParams: Set +): VaryPath { // Re-keys a segment's vary path based on which params the segment actually // depends on. Params that are NOT in the varyParams set are replaced with // Fallback, allowing the cache entry to be reused across different values of @@ -394,7 +380,7 @@ export function getFulfilledSegmentVaryPath( // This is called when a segment is fulfilled with data from the server. The // varyParams set comes from the server and indicates which params were // accessed during rendering. - const clone: VaryPath = { + const clone: VaryPathNode = { id: original.id, // If the id is null, this node is not a param (e.g., it's a request key). // If the id is in the varyParams set, keep the original value. @@ -409,10 +395,10 @@ export function getFulfilledSegmentVaryPath( ? null : getFulfilledSegmentVaryPath(original.parent, varyParams), } - return clone as SegmentVaryPath + return clone as VaryPath } -export function getShellSegmentVaryPath(original: VaryPath): SegmentVaryPath { +export function getShellSegmentVaryPath(original: VaryPathNode): VaryPath { // Re-keys a segment's vary path to identify the "App Shell" entry for this // segment position — a reusable loading state that can be served for any // concrete navigation to this segment. The shell is rendered with params @@ -421,7 +407,7 @@ export function getShellSegmentVaryPath(original: VaryPath): SegmentVaryPath { // them. Accordingly, we keep the concrete value of structural nodes (request // keys, etc.) and root param nodes, and replace every other param node (non- // root path params and search params) with Fallback. - const clone: VaryPath = { + const clone: VaryPathNode = { id: original.id, value: original.id === null || original.isRootParam === true @@ -433,5 +419,5 @@ export function getShellSegmentVaryPath(original: VaryPath): SegmentVaryPath { ? null : getShellSegmentVaryPath(original.parent), } - return clone as SegmentVaryPath + return clone as VaryPath } diff --git a/packages/next/src/server/app-render/vary-params.ts b/packages/next/src/server/app-render/vary-params.ts index 165136f29ac2..3b697e9a3a7f 100644 --- a/packages/next/src/server/app-render/vary-params.ts +++ b/packages/next/src/server/app-render/vary-params.ts @@ -4,11 +4,15 @@ import { getVaryParamsAccumulator, workUnitAsyncStorage, } from './work-unit-async-storage.external' +import { + SEARCH_PARAMS_VARY_ID, + type VaryParamId, +} from '../../shared/lib/segment-cache/vary-params-decoding' /** * Accumulates vary params for a single segment (or for metadata/rootParams). * - * A VaryParamsAccumulator is an `AsyncIterable` that can be serialized + * A VaryParamsAccumulator is an `AsyncIterable` that can be serialized * by React Flight. As params are accessed during render, each newly-seen param * name is `add`ed, which yields it into the Flight stream immediately. After * rendering, call `close()` (via `finishAccumulatingVaryParams`) to end the @@ -29,28 +33,29 @@ import { * only instance referenced by more than one segment, and it only ever yields * "done", so concurrent iteration of it is safe. */ -export class VaryParamsAccumulator implements AsyncIterable { - private _resolve: ((result: IteratorResult) => void) | null = null +export class VaryParamsAccumulator implements AsyncIterable { + private _resolve: ((result: IteratorResult) => void) | null = + null private _done = false - private _buffer: string[] = [] + private _buffer: VaryParamId[] = [] // The set of param names already yielded. Doubles as the dedupe guard so the // same name is never emitted twice. - private _seen: Set = new Set() + private _seen: Set = new Set() /** * Records that a param was accessed. Yields the name into the stream the * first time it's seen; subsequent accesses of the same name are no-ops. */ - add(paramName: string): void { - if (this._done || this._seen.has(paramName)) { + add(id: VaryParamId): void { + if (this._done || this._seen.has(id)) { return } - this._seen.add(paramName) + this._seen.add(id) if (this._resolve !== null) { - this._resolve({ value: paramName, done: false }) + this._resolve({ value: id, done: false }) this._resolve = null } else { - this._buffer.push(paramName) + this._buffer.push(id) } } @@ -67,7 +72,7 @@ export class VaryParamsAccumulator implements AsyncIterable { } } - [Symbol.asyncIterator](): AsyncIterator { + [Symbol.asyncIterator](): AsyncIterator { return { next: () => { if (this._buffer.length > 0) { @@ -76,7 +81,7 @@ export class VaryParamsAccumulator implements AsyncIterable { if (this._done) { return Promise.resolve({ value: undefined, done: true }) } - return new Promise>((resolve) => { + return new Promise>((resolve) => { this._resolve = resolve }) }, @@ -182,9 +187,9 @@ export function getRootParamsVaryParamsAccumulator(): VaryParamsAccumulator | nu */ export function accumulateVaryParam( accumulator: VaryParamsAccumulator, - paramName: string + id: VaryParamId ): void { - accumulator.add(paramName) + accumulator.add(id) } /** @@ -261,23 +266,23 @@ export function createVaryingSearchParams( // checks, or enumeration — must register as varying. A Proxy is required // (rather than per-property getters) so that enumeration of an empty // searchParams object still triggers a vary. All accesses bucket into the - // single sentinel '?'; the segment is keyed by the whole query string. + // single search params id; the segment is keyed by the whole query string. // TODO: Split into per-param tracking if the cache key evolves. return new Proxy(originalSearchParamsObject, { get(target, prop, receiver) { if (typeof prop === 'string') { - accumulateVaryParam(accumulator, '?') + accumulateVaryParam(accumulator, SEARCH_PARAMS_VARY_ID) } return Reflect.get(target, prop, receiver) }, has(target, prop) { if (typeof prop === 'string') { - accumulateVaryParam(accumulator, '?') + accumulateVaryParam(accumulator, SEARCH_PARAMS_VARY_ID) } return Reflect.has(target, prop) }, ownKeys(target) { - accumulateVaryParam(accumulator, '?') + accumulateVaryParam(accumulator, SEARCH_PARAMS_VARY_ID) return Reflect.ownKeys(target) }, }) diff --git a/packages/next/src/shared/lib/segment-cache/vary-params-decoding.ts b/packages/next/src/shared/lib/segment-cache/vary-params-decoding.ts index 6179e65e2067..84db2d9430d1 100644 --- a/packages/next/src/shared/lib/segment-cache/vary-params-decoding.ts +++ b/packages/next/src/shared/lib/segment-cache/vary-params-decoding.ts @@ -6,11 +6,25 @@ import { readFulfilledValue } from '../rsc-transport' -export type VaryParams = Set +/** + * The vary path id for search params. Path params are identified by their + * name. Search params don't have a fixed set of names, so any access to them + * is reported under this one id, and the segment is keyed by the whole search + * string (see createVaryingSearchParams in app-render/vary-params.ts). + * + * It's a number so it can't collide with a param name. `app/[?]/page.tsx` is a + * valid route. + */ +export const SEARCH_PARAMS_VARY_ID = 0 + +// Path param names, and SEARCH_PARAMS_VARY_ID for the search params. +export type VaryParamId = string | number + +export type VaryParams = Set /** * Vary params are serialized into the Flight stream as an - * `AsyncIterable` that yields each accessed param name exactly once + * `AsyncIterable` that yields each accessed param id exactly once * (the server dedupes before emitting). Because each access is flushed into the * stream as it happens, there's no step at the end of the render that has to * run for the client to read anything. If a prerender is aborted by sync I/O, @@ -23,7 +37,7 @@ export type VaryParams = Set * the render — folding them into every segment would otherwise require a merge * once the whole render is complete. */ -export type VaryParamsIterable = AsyncIterable +export type VaryParamsIterable = AsyncIterable /** * Synchronously drains a vary params `AsyncIterable`, adding each yielded name diff --git a/test/e2e/app-dir/segment-cache/search-params/segment-cache-question-mark-param.test.ts b/test/e2e/app-dir/segment-cache/search-params/segment-cache-question-mark-param.test.ts new file mode 100644 index 000000000000..239830ccf14a --- /dev/null +++ b/test/e2e/app-dir/segment-cache/search-params/segment-cache-question-mark-param.test.ts @@ -0,0 +1,85 @@ +import { nextTestSetup, FileRef } from 'e2e-utils' +import { join } from 'path' +import { retry } from '../../../../lib/next-test-utils' + +// `[?]` is a valid param name, so a vary path node whose id is `?` must not +// be taken for the search params entry. The `[?]` route is written at +// runtime rather than checked in: git refuses a path containing `?` on +// Windows, and webpack's `next build` rejects the emitted filename as a +// query string, so the fixture is turbopack-only. +// @force-gate turbopack && !deploy +describe('segment cache (param named "?")', () => { + const { next } = nextTestSetup({ + files: { + 'app/layout.tsx': new FileRef(join(__dirname, 'app/layout.tsx')), + 'next.config.js': new FileRef(join(__dirname, 'next.config.js')), + 'app/[?]/layout.tsx': ` + export default function QuestionMarkLayout({ + children, + }: { + children: React.ReactNode + }) { + return ( +
+

Layout under [?]

+ {children} +
+ ) + } + `, + 'app/[?]/page.tsx': ` + import Link from 'next/link' + import { Suspense } from 'react' + + async function Param({ params }: { params: Promise<{ '?': string }> }) { + const { '?': value } = await params + return
Param: {value}
+ } + + export default async function QuestionMarkPage({ + params, + }: { + params: Promise<{ '?': string }> + }) { + return ( + <> + + + + + param-a + + + param-b + + + ) + } + `, + }, + }) + + // The layout matters: the page's vary path derives from the layout's, which + // ends at the `?` param node. + it('keys segments under a param literally named "?" by its value', async () => { + const browser = await next.browser('/param-a') + expect(await browser.elementById('question-mark-param').text()).toBe( + 'Param: param-a' + ) + + await browser.elementById('link-param-b').click() + await retry(async () => { + expect(await browser.elementById('question-mark-param').text()).toBe( + 'Param: param-b' + ) + expect(await browser.url()).toMatch(/\/param-b$/) + }) + + await browser.elementById('link-param-a').click() + await retry(async () => { + expect(await browser.elementById('question-mark-param').text()).toBe( + 'Param: param-a' + ) + }) + }) +}) From 9937a54acd3532891fac44efe159ab34107e7a2e Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 25 Sep 2026 00:25:27 -0400 Subject: [PATCH 07/13] Use RouteTree for CacheNode tree (#98971) Refactors the CacheNode tree to use the RouteTree data type. CacheNode used to be a tree-shaped object, with a similar structure to RouteTree. CacheNode is now a data container instead, embedded inside a RouteTree structure. So the usage sites change from CacheNode to RouteTree. As a result, we can now diff against the render tree directly during prefetches and navigations, instead of diffing against FlightRouterState. This gets us closer to migrating everything away from FlightRouterState to our shared data types. --- .../src/client/components/app-router-state.ts | 48 ++- .../next/src/client/components/app-router.tsx | 21 +- .../components/bfcache-state-manager.ts | 18 +- .../src/client/components/layout-router.tsx | 60 ++- packages/next/src/client/components/links.ts | 16 +- .../next/src/client/components/navigation.ts | 2 +- .../next/src/client/components/prefetch.ts | 12 +- .../next/src/client/components/render-tree.ts | 343 ++++++++---------- .../create-initial-router-state.ts | 6 +- .../is-navigating-to-new-root-layout.ts | 17 +- .../reducers/find-head-in-cache.ts | 17 +- .../reducers/refresh-reducer.ts | 5 +- .../reducers/restore-reducer.ts | 1 - .../reducers/server-action-reducer.ts | 3 +- .../reducers/server-patch-reducer.ts | 1 - .../router-reducer/router-reducer-types.ts | 5 +- .../components/segment-cache/bfcache.ts | 36 +- .../client/components/segment-cache/cache.ts | 83 ++--- .../segment-cache/decode-server-response.ts | 7 +- .../components/segment-cache/scheduler.ts | 120 +++--- .../components/segment-cache/vary-path.ts | 6 +- .../lib/app-router-context.shared-runtime.ts | 4 +- .../next/src/shared/lib/app-router-types.ts | 8 +- .../browser-logs/browser-logs.test.ts | 6 +- .../app/retained-search/@side/default.tsx | 3 + .../app/retained-search/@side/one/page.tsx | 16 + .../app/retained-search/layout.tsx | 25 ++ .../app/retained-search/one/page.tsx | 3 + .../app/retained-search/three/page.tsx | 3 + .../app/retained-search/two/page.tsx | 3 + .../parallel-routes-revalidation.test.ts | 39 ++ 31 files changed, 460 insertions(+), 477 deletions(-) create mode 100644 test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/@side/default.tsx create mode 100644 test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/@side/one/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/layout.tsx create mode 100644 test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/one/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/three/page.tsx create mode 100644 test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/two/page.tsx diff --git a/packages/next/src/client/components/app-router-state.ts b/packages/next/src/client/components/app-router-state.ts index e7321f0976bd..ea4471e8fb89 100644 --- a/packages/next/src/client/components/app-router-state.ts +++ b/packages/next/src/client/components/app-router-state.ts @@ -1,3 +1,4 @@ +import type { RouteTree } from './segment-cache/cache' import type { FlightRouterState, ScrollRef, @@ -60,7 +61,7 @@ export function navigate( url: URL, currentUrl: URL, currentRenderedSearch: string, - currentCacheNode: CacheNode | null, + currentRenderTree: RouteTree, currentFlightRouterState: FlightRouterState, nextUrl: string | null, freshnessPolicy: FreshnessPolicy, @@ -89,7 +90,7 @@ export function navigate( url, currentUrl, currentRenderedSearch, - currentCacheNode, + currentRenderTree, currentFlightRouterState, nextUrl, freshnessPolicy, @@ -105,7 +106,7 @@ export function navigate( url, currentUrl, currentRenderedSearch, - currentCacheNode, + currentRenderTree, currentFlightRouterState, nextUrl, freshnessPolicy, @@ -122,7 +123,7 @@ function navigateImpl( url: URL, currentUrl: URL, currentRenderedSearch: string, - currentCacheNode: CacheNode | null, + currentRenderTree: RouteTree, currentFlightRouterState: FlightRouterState, nextUrl: string | null, freshnessPolicy: FreshnessPolicy, @@ -147,8 +148,7 @@ function navigateImpl( currentUrl, currentRenderedSearch, nextUrl, - currentCacheNode, - currentFlightRouterState, + currentRenderTree, freshnessPolicy, scrollBehavior, navigateType, @@ -185,8 +185,7 @@ function navigateImpl( currentUrl, currentRenderedSearch, nextUrl, - currentCacheNode, - currentFlightRouterState, + currentRenderTree, freshnessPolicy, scrollBehavior, navigateType, @@ -210,7 +209,7 @@ function navigateImpl( currentUrl, currentRenderedSearch, nextUrl, - currentCacheNode, + currentRenderTree, currentFlightRouterState, freshnessPolicy, scrollBehavior, @@ -231,8 +230,7 @@ export function navigateToKnownRoute( navigationSeed: NavigationSeed, currentUrl: URL, currentRenderedSearch: string, - currentCacheNode: CacheNode | null, - currentFlightRouterState: FlightRouterState, + currentRenderTree: RouteTree, freshnessPolicy: FreshnessPolicy, nextUrl: string | null, scrollBehavior: ScrollBehavior, @@ -345,8 +343,7 @@ export function navigateToKnownRoute( now, currentUrl, currentRenderedSearch, - currentCacheNode, - currentFlightRouterState, + currentRenderTree, navigationSeed.routeTree, navigationSeed.metadataVaryPath, freshnessPolicy, @@ -397,8 +394,7 @@ function navigateUsingPrefetchedRouteTree( currentUrl: URL, currentRenderedSearch: string, nextUrl: string | null, - currentCacheNode: CacheNode | null, - currentFlightRouterState: FlightRouterState, + currentRenderTree: RouteTree, freshnessPolicy: FreshnessPolicy, scrollBehavior: ScrollBehavior, navigateType: 'push' | 'replace', @@ -429,8 +425,7 @@ function navigateUsingPrefetchedRouteTree( prefetchSeed, currentUrl, currentRenderedSearch, - currentCacheNode, - currentFlightRouterState, + currentRenderTree, freshnessPolicy, nextUrl, scrollBehavior, @@ -463,7 +458,7 @@ async function navigateToUnknownRoute( currentUrl: URL, currentRenderedSearch: string, nextUrl: string | null, - currentCacheNode: CacheNode | null, + currentRenderTree: RouteTree, currentFlightRouterState: FlightRouterState, freshnessPolicy: FreshnessPolicy, scrollBehavior: ScrollBehavior, @@ -479,7 +474,7 @@ async function navigateToUnknownRoute( // // To avoid duplication of logic, we're going to pretend that the tree // returned by the dynamic request is, in fact, a prefetch tree. Then we can - // use the same server response to write the actual data into the CacheNode + // use the same server response to write the actual data into the render // tree. So it's the same flow as the "happy path" (prefetch, then // navigation), except we use a single server response for both stages. @@ -618,8 +613,7 @@ async function navigateToUnknownRoute( navigationSeed, currentUrl, currentRenderedSearch, - currentCacheNode, - currentFlightRouterState, + currentRenderTree, freshnessPolicy, nextUrl, scrollBehavior, @@ -677,7 +671,7 @@ export function completeSoftNavigation( url: URL, referringNextUrl: string | null, tree: FlightRouterState, - cache: CacheNode, + cache: RouteTree, renderedSearch: string, canonicalUrl: string, navigateType: 'push' | 'replace', @@ -737,7 +731,7 @@ export function completeSoftNavigation( // // If this navigation created new scroll targets (scrollRef !== null), // neutralize them. If it didn't, any prior scroll targets carried - // forward on the cache nodes via reuseSharedCacheNode remain active. + // forward on reused cache nodes remain active. if (scrollRef !== null) { scrollRef.current = false } @@ -814,7 +808,7 @@ export function completeTraverseNavigation( state: AppRouterState, url: URL, renderedSearch: string, - cache: CacheNode, + cache: RouteTree, tree: FlightRouterState, nextUrl: string | null ) { @@ -854,7 +848,7 @@ async function ensurePrefetchThenNavigate( url: URL, currentUrl: URL, currentRenderedSearch: string, - currentCacheNode: CacheNode | null, + currentRenderTree: RouteTree, currentFlightRouterState: FlightRouterState, nextUrl: string | null, freshnessPolicy: FreshnessPolicy, @@ -877,7 +871,7 @@ async function ensurePrefetchThenNavigate( const navigationLockPrefetch = beginNavigationLockPrefetch() const prefetchTask = schedulePrefetchTask( cacheKey, - currentFlightRouterState, + currentRenderTree, fetchStrategy, PrefetchPriority.Default, null, // onInvalidate @@ -897,7 +891,7 @@ async function ensurePrefetchThenNavigate( url, currentUrl, currentRenderedSearch, - currentCacheNode, + currentRenderTree, currentFlightRouterState, nextUrl, freshnessPolicy, diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index 00d29f3e6b7c..3349ec36cb36 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -1,3 +1,4 @@ +import type { RouteTree } from './segment-cache/cache' import React, { useEffect, useMemo, @@ -159,8 +160,8 @@ function HistoryUpdater({ // task. Re-prefetch all visible links with the updated values. In most // cases, this will not result in any new network requests, only if // the prefetch result actually varies on one of these inputs. - pingVisibleLinks(appRouterState.nextUrl, appRouterState.tree) - }, [appRouterState.nextUrl, appRouterState.tree]) + pingVisibleLinks(appRouterState.nextUrl, appRouterState.cache) + }, [appRouterState.nextUrl, appRouterState.cache]) return null } @@ -182,16 +183,16 @@ function copyNextJsInternalHistoryState(data: any) { } function Head({ - headCacheNode, + headRenderTree, }: { - headCacheNode: CacheNode | null + headRenderTree: RouteTree | null }): React.ReactNode { // If this segment has a `prefetchHead`, it's the statically prefetched data. // We should use that on initial render instead of `head`. Then we'll switch // to `head` when the dynamic response streams in. - const head = headCacheNode !== null ? headCacheNode.head : null + const head = headRenderTree !== null ? headRenderTree.data.head : null const prefetchHead = - headCacheNode !== null ? headCacheNode.prefetchHead : null + headRenderTree !== null ? headRenderTree.data.prefetchHead : null // If no prefetch data is available, then we go straight to rendering `head`. const resolvedPrefetchRsc = prefetchHead !== null ? prefetchHead : head @@ -466,7 +467,7 @@ function Router({ const layoutRouterContext = useMemo(() => { return { parentTree: tree, - parentCacheNode: cache, + parentRenderTree: cache, parentSegmentPath: null, parentParams: {}, parentLoadingData: null, @@ -498,9 +499,9 @@ function Router({ // // The `key` is used to remount the component whenever the head moves to // a different segment. - const [headCacheNode, headKey] = matchingHead + const [headRenderTree, headKey] = matchingHead - head = + head = } else { head = null } @@ -511,7 +512,7 @@ function Router({ {/* RootLayoutBoundary enables detection of Suspense boundaries around the root layout. When users wrap their layout in , this creates the component stack pattern "Suspense -> RootLayoutBoundary" which dynamic-rendering.ts uses to allow dynamic rendering. */} - {cache.rsc} + {cache.data.rsc} ) diff --git a/packages/next/src/client/components/bfcache-state-manager.ts b/packages/next/src/client/components/bfcache-state-manager.ts index f2198d07e96c..91f675e06381 100644 --- a/packages/next/src/client/components/bfcache-state-manager.ts +++ b/packages/next/src/client/components/bfcache-state-manager.ts @@ -1,3 +1,4 @@ +import type { RouteTree } from './segment-cache/cache' import type { CacheNode, FlightRouterState, @@ -9,7 +10,7 @@ const MAX_BF_CACHE_ENTRIES = process.env.__NEXT_CACHE_COMPONENTS ? 3 : 1 export type RouterBFCacheEntry = { tree: FlightRouterState - cacheNode: CacheNode + renderTree: RouteTree stateKey: string // The entries form a linked list, sorted in order of most recently active. next: RouterBFCacheEntry | null @@ -30,14 +31,13 @@ export type RouterBFCacheEntry = { * unmounted, then the React tree would be, too. So, we use React state to * manage it. * - * Note that we don't store the RSC data for the cache entries in this hook — - * the data for inactive segments is stored in the parent CacheNode, which - * *does* have a longer lifetime than the React tree. This hook only determines - * which of those trees should have their *state* preserved, by . + * Each entry retains its render tree, including its RSC data. This hook + * determines which trees should have their React and DOM state preserved + * by . */ export function useRouterBFCache( activeTree: FlightRouterState, - activeCacheNode: CacheNode, + activeRenderTree: RouteTree, activeStateKey: string ): RouterBFCacheEntry { // The currently active entry. The entries form a linked list, sorted in @@ -53,7 +53,7 @@ export function useRouterBFCache( () => { const initialEntry: RouterBFCacheEntry = { tree: activeTree, - cacheNode: activeCacheNode, + renderTree: activeRenderTree, stateKey: activeStateKey, next: null, } @@ -78,7 +78,7 @@ export function useRouterBFCache( // linked list. const newActiveEntry: RouterBFCacheEntry = { tree: activeTree, - cacheNode: activeCacheNode, + renderTree: activeRenderTree, stateKey: activeStateKey, next: null, } @@ -105,7 +105,7 @@ export function useRouterBFCache( n++ const entry: RouterBFCacheEntry = { tree: oldEntry.tree, - cacheNode: oldEntry.cacheNode, + renderTree: oldEntry.renderTree, stateKey: oldEntry.stateKey, next: null, } diff --git a/packages/next/src/client/components/layout-router.tsx b/packages/next/src/client/components/layout-router.tsx index 829d00ea265a..117dacb0fc16 100644 --- a/packages/next/src/client/components/layout-router.tsx +++ b/packages/next/src/client/components/layout-router.tsx @@ -1,5 +1,7 @@ 'use client' +import type { RouteTree } from './segment-cache/cache' + import type { CacheNode } from '../../shared/lib/app-router-types' import type { LoadingModuleData } from '../../shared/lib/app-router-types' import type { @@ -136,7 +138,7 @@ function getHashFragmentDomNode(hashFragment: string) { interface ScrollHandlerProps { scrollRef: ScrollHandlerRef children: React.ReactNode - cacheNode: CacheNode + renderTree: RouteTree } /** @@ -148,11 +150,11 @@ function InnerScrollHandler(props: ScrollHandlerProps) { useLayoutEffect( () => { - const { scrollRef: scrollHandlerRef, cacheNode } = props + const { scrollRef: scrollHandlerRef, renderTree } = props const scrollRef = scrollHandlerRef.forceScroll ? scrollHandlerRef.scrollRef - : cacheNode.scrollRef + : renderTree.data.scrollRef if (scrollRef === null || !scrollRef.current) return let instance: FragmentInstance | HTMLElement | null = null @@ -277,10 +279,10 @@ function InnerScrollHandler(props: ScrollHandlerProps) { function ScrollHandler({ children, - cacheNode, + renderTree, }: { children: React.ReactNode - cacheNode: CacheNode + renderTree: RouteTree }) { const context = useContext(GlobalLayoutRouterContext) if (!context) { @@ -288,7 +290,7 @@ function ScrollHandler({ } return ( - + {children} ) @@ -301,7 +303,7 @@ function InnerLayoutRouter({ tree, segmentPath, debugNameContext, - cacheNode: maybeCacheNode, + renderTree, params, url, isActive, @@ -309,7 +311,7 @@ function InnerLayoutRouter({ tree: FlightRouterState segmentPath: FlightSegmentPath debugNameContext: string - cacheNode: CacheNode | null + renderTree: RouteTree params: Params url: string isActive: boolean @@ -321,19 +323,6 @@ function InnerLayoutRouter({ throw new Error('invariant global layout router not mounted') } - const cacheNode = - maybeCacheNode !== null - ? maybeCacheNode - : // This segment is not in the cache. Suspend indefinitely. - // - // This should only be reachable for inactive/hidden segments, during - // prerendering The active segment should always be consistent with the - // CacheNode tree. Regardless, if we don't have a matching CacheNode, we - // must suspend rather than render nothing, to prevent showing an - // inconsistent route. - - (use(unresolvedThenable) as never) - // `rsc` represents the renderable node for this segment. // If this segment has a `prefetchRsc`, it's the statically prefetched data. @@ -342,12 +331,14 @@ function InnerLayoutRouter({ // // If no prefetch data is available, then we go straight to rendering `rsc`. const resolvedPrefetchRsc = - cacheNode.prefetchRsc !== null ? cacheNode.prefetchRsc : cacheNode.rsc + renderTree.data.prefetchRsc !== null + ? renderTree.data.prefetchRsc + : renderTree.data.rsc // We use `useDeferredValue` to handle switching between the prefetched and // final values. The second argument is returned on initial render, then it // re-renders with the first argument. - const rsc: any = useDeferredValue(cacheNode.rsc, resolvedPrefetchRsc) + const rsc: any = useDeferredValue(renderTree.data.rsc, resolvedPrefetchRsc) // `rsc` is either a React node or a promise for a React node, except we // special case `null` to represent that this segment's data is missing. If @@ -401,7 +392,7 @@ function InnerLayoutRouter({ = [] do { const tree = bfcacheEntry.tree - const cacheNode = bfcacheEntry.cacheNode + const renderTree = bfcacheEntry.renderTree const stateKey = bfcacheEntry.stateKey const segment = tree[0] /* @@ -676,7 +666,7 @@ export default function OuterLayoutRouter({ const debugNameToDisplay = isVirtual ? undefined : debugNameContext let templateValue = ( - + ) { // For each currently visible link, cancel the existing prefetch task (if it // exists) and schedule a new one. This is effectively the same as if all the // visible links left and then re-entered the viewport. // - // This is called when the Next-Url or the base tree changes, since those + // This is called when the Next-Url or the active cache tree changes, since those // may affect the result of a prefetch task. It's also called after a // cache invalidation. for (const instance of prefetchableAndVisible) { const task = instance.prefetchTask - if (task !== null && !isPrefetchTaskDirty(task, nextUrl, tree)) { + if (task !== null && !isPrefetchTaskDirty(task, nextUrl, cache)) { // The cache has not been invalidated, and none of the inputs have // changed. Bail out. continue @@ -391,7 +391,7 @@ export function pingVisibleLinks( const cacheKey = createCacheKey(instance.prefetchHref, nextUrl) instance.prefetchTask = scheduleSegmentPrefetchTask( cacheKey, - tree, + cache, instance.fetchStrategy, PrefetchPriority.Default, null, diff --git a/packages/next/src/client/components/navigation.ts b/packages/next/src/client/components/navigation.ts index 2e1df203d82a..95e6d439a15b 100644 --- a/packages/next/src/client/components/navigation.ts +++ b/packages/next/src/client/components/navigation.ts @@ -177,7 +177,7 @@ export function useRouter(): AppRouterInstance { // a `b` prefix, so the id can be safely concatenated with other keys // without collision. const layout = useContext(LayoutRouterContext) - const bfcacheIdNumber = layout?.parentCacheNode.bfcacheId ?? 0 + const bfcacheIdNumber = layout?.parentRenderTree.data.bfcacheId ?? 0 return useMemo( () => ({ back: router.back, diff --git a/packages/next/src/client/components/prefetch.ts b/packages/next/src/client/components/prefetch.ts index 547407546085..07cda2eb79cd 100644 --- a/packages/next/src/client/components/prefetch.ts +++ b/packages/next/src/client/components/prefetch.ts @@ -1,4 +1,5 @@ -import type { FlightRouterState } from '../../shared/lib/app-router-types' +import type { RouteTree } from './segment-cache/cache' +import type { CacheNode } from '../../shared/lib/app-router-types' import type { PrefetchOptions } from '../../shared/lib/app-router-context.shared-runtime' import { PrefetchKind } from './router-reducer/router-reducer-types' import { createPrefetchURL } from './app-router-utils' @@ -61,7 +62,7 @@ export function prefetchRoute(href: string, options?: PrefetchOptions): void { prefetch( href, state.nextUrl, - state.tree, + state.cache, fetchStrategy, options?.onInvalidate ?? null ) @@ -73,8 +74,7 @@ export function prefetchRoute(href: string, options?: PrefetchOptions): void { * or router.prefetch. It must be validated before we attempt to prefetch it. * @param nextUrl - A special header used by the server for interception routes. * Roughly corresponds to the current URL. - * @param treeAtTimeOfPrefetch - The FlightRouterState at the time the prefetch - * was requested. This is only used when PPR is disabled. + * @param renderTreeAtTimeOfPrefetch - The active data and its vary paths. * @param fetchStrategy - Whether to prefetch dynamic data, in addition to * static data. This is used by ``. * @param onInvalidate - A callback that will be called when the prefetch cache @@ -90,7 +90,7 @@ export function prefetchRoute(href: string, options?: PrefetchOptions): void { export function prefetch( href: string, nextUrl: string | null, - treeAtTimeOfPrefetch: FlightRouterState, + renderTreeAtTimeOfPrefetch: RouteTree, fetchStrategy: PrefetchTaskFetchStrategy, onInvalidate: null | (() => void) ) { @@ -102,7 +102,7 @@ export function prefetch( const cacheKey = createCacheKey(url.href, nextUrl) schedulePrefetchTask( cacheKey, - treeAtTimeOfPrefetch, + renderTreeAtTimeOfPrefetch, fetchStrategy, PrefetchPriority.Default, onInvalidate, diff --git a/packages/next/src/client/components/render-tree.ts b/packages/next/src/client/components/render-tree.ts index 3a95312ca0fa..53c6974c79f0 100644 --- a/packages/next/src/client/components/render-tree.ts +++ b/packages/next/src/client/components/render-tree.ts @@ -31,7 +31,7 @@ import { type RSCSegmentData, type RefreshState, type FulfilledRouteCacheEntry, - convertReusedFlightRouterStateToRouteTree, + rebaseInactiveRouteTree, readSegmentCacheEntryForNavigation, waitForSegmentCacheEntry, invalidateRouteCacheEntries, @@ -66,8 +66,8 @@ export type NavigationTask = { status: NavigationTaskStatus // The router state that corresponds to the tree that this Task represents. route: FlightRouterState - // The CacheNode that corresponds to the tree that this Task represents. - node: CacheNode + // The render tree being constructed by this task. + node: RouteTree // The tree sent to the server during the dynamic request. If all the segments // are static, then this will be null, and no server request is required. // Otherwise, this is the same as `route`, except with the `refetch` marker @@ -154,7 +154,7 @@ export type NavigationLock = Promise const noop = () => {} -export function createInitialCacheNodeForHydration( +export function createInitialRenderTreeForHydration( navigatedAt: number, initialTree: RouteTree, seedHead: HeadData, @@ -167,7 +167,7 @@ export function createInitialCacheNodeForHydration( scrollRef: null, } const restrictToShell = false - const task = createCacheNodeOnNavigation( + const task = createRenderTreeOnNavigation( navigatedAt, initialTree, null, @@ -216,8 +216,7 @@ export function startPPRNavigation( navigatedAt: number, oldUrl: URL, oldRenderedSearch: string, - oldCacheNode: CacheNode | null, - oldRouterState: FlightRouterState, + oldRenderTree: RouteTree, newRouteTree: RouteTree, newMetadataVaryPath: VaryPath | null, freshness: FreshnessPolicy, @@ -238,11 +237,9 @@ export function startPPRNavigation( canonicalUrl: createHrefFromUrl(oldUrl), renderedSearch: oldRenderedSearch as NormalizedSearch, } - return updateCacheNodeOnNavigation( + return updateRenderTreeOnNavigation( navigatedAt, - oldUrl, - oldCacheNode !== null ? oldCacheNode : undefined, - oldRouterState, + oldRenderTree, newRouteTree, newMetadataVaryPath, freshness, @@ -258,11 +255,9 @@ export function startPPRNavigation( ) } -function updateCacheNodeOnNavigation( +function updateRenderTreeOnNavigation( navigatedAt: number, - oldUrl: URL, - oldCacheNode: CacheNode | void, - oldRouterState: FlightRouterState, + oldRenderTree: RouteTree, newRouteTree: RouteTree, newMetadataVaryPath: VaryPath | null, freshness: FreshnessPolicy, @@ -280,9 +275,9 @@ function updateCacheNodeOnNavigation( ): NavigationTask | null { // Check if this segment matches the one in the previous route. A // search-param-only difference at a page segment falls through to the - // matched branch — the CacheNode is rebuilt (so data refetches), but the + // matched branch — the render tree is rebuilt (so data refetches), but the // bfcacheId carries forward as if the segment had matched. - const oldSegment = oldRouterState[0] + const oldSegment = createSegmentFromRouteTree(oldRenderTree) const newSegment = createSegmentFromRouteTree(newRouteTree) const segmentMatchKind = compareSegments(newSegment, oldSegment) if (segmentMatchKind === SegmentMatchKind.Change) { @@ -312,7 +307,7 @@ function updateCacheNodeOnNavigation( // the root layout. We also only need to compare the subtree that is not // shared. In the common case, this branch is skipped completely. ((newRouteTree.prefetchHints & PrefetchHint.IsRootLayoutOrAbove) !== 0 && - isNavigatingToNewRootLayout(oldRouterState, newRouteTree)) || + isNavigatingToNewRootLayout(oldRenderTree, newRouteTree)) || // The global Not Found route (app/global-not-found.tsx) is a special // case, because it acts like a root layout, but in the router tree, it // is rendered in the same position as app/layout.tsx. @@ -327,7 +322,7 @@ function updateCacheNodeOnNavigation( ) { return null } - return createCacheNodeOnNavigation( + return createRenderTreeOnNavigation( navigatedAt, newRouteTree, newMetadataVaryPath, @@ -342,7 +337,6 @@ function updateCacheNodeOnNavigation( } const newSlots = newRouteTree.slots - const oldRouterStateChildren = oldRouterState[1] let shouldRefreshDynamicData: boolean = false switch (freshness) { @@ -369,13 +363,11 @@ function updateCacheNodeOnNavigation( const isLeafSegment = newSlots === null // Get the data for this segment. Since it was part of the previous route, - // usually we just clone the data from the old CacheNode. However, during a - // refresh or a revalidation, there won't be any existing CacheNode. So we - // may need to consult the prefetch cache, like we would for a new segment. - let newCacheNode: CacheNode + // usually we just reuse the data from the old render tree. During a refresh + // or revalidation, consult the prefetch cache or response seed instead. + let newRenderTree: RouteTree let needsDynamicRequest: boolean if ( - oldCacheNode !== undefined && !shouldRefreshDynamicData && // During a same-page navigation, we always refetch the page segments !(isLeafSegment && isSamePageNavigation) && @@ -384,16 +376,20 @@ function updateCacheNodeOnNavigation( // the node in the route tree is the same. segmentMatchKind !== SegmentMatchKind.SearchParamOnlyChange ) { - // Reuse the existing CacheNode - const dropPrefetchRsc = false - newCacheNode = reuseSharedCacheNode(dropPrefetchRsc, oldCacheNode) + // This segment appears in both the old and new routes. Reuse the existing + // data without triggering a request. + // TODO: Consider adding a fast path where if this segment is unchanged and + // all of its children are unchanged, we return the exact same RenderTree + // object. Reusing the exact previous object gives React more of a chance to + // bail out of rendering. + newRenderTree = createRenderTree(newRouteTree, oldRenderTree.data) needsDynamicRequest = false } else { - // If this is part of a refresh, ignore the existing CacheNode and create a + // If this is part of a refresh, ignore the existing render tree and create a // new one. const data = newRouteTree.data const seedRsc = data !== null ? data.rsc : null - const result = createCacheNodeForSegment( + const result = createRenderTreeForSegment( navigatedAt, newRouteTree, seedRsc, @@ -401,16 +397,12 @@ function updateCacheNodeOnNavigation( seedHead, freshness, seedDynamicStaleAt, - // Carry forward the existing bfcacheId when there's a prior CacheNode: - // even though the data is being refreshed, the state identity of the - // route hasn't changed. Otherwise (no prior node) mint a fresh one. - oldCacheNode !== undefined - ? oldCacheNode.bfcacheId - : generateBFCacheId(freshness), + // Refreshing data preserves the identity of the active segment. + oldRenderTree.data.bfcacheId, map, restrictToShell ) - newCacheNode = result.cacheNode + newRenderTree = result.node needsDynamicRequest = result.needsDynamicRequest // Scroll handling @@ -420,16 +412,14 @@ function updateCacheNodeOnNavigation( ) { // Special case: A search param change mostly acts the same as a // refresh, except it does trigger a scroll. - accumulateScrollRef(freshness, newCacheNode, accumulation) + accumulateScrollRef(freshness, newRenderTree.data, accumulation) } else { // Normal case: This is a refresh of an existing segment. Carry forward // the old node's scrollRef. This preserves scroll intent when a prior - // navigation's CacheNode is replaced by a refresh before the scroll + // navigation's render tree is replaced by a refresh before the scroll // handler has had a chance to fire — e.g. when router.push() and // router.refresh() are called in the same startTransition batch. - if (oldCacheNode !== undefined) { - newCacheNode.scrollRef = oldCacheNode.scrollRef - } + newRenderTree.data.scrollRef = oldRenderTree.data.scrollRef } } @@ -446,6 +436,7 @@ function updateCacheNodeOnNavigation( maybeRefreshState : // Inherit the refresh URL from the parent. parentRefreshState + newRenderTree.refreshState = refreshState // If this segment itself needs to fetch new data from the server, then by // definition it is being refreshed. Track its refresh URL so we know which @@ -485,24 +476,22 @@ function updateCacheNodeOnNavigation( [parallelRouteKey: string]: FlightRouterState } = {} - let newCacheNodeSlots: Record | null = null if (newSlots !== null) { - const oldCacheNodeSlots = - oldCacheNode !== undefined ? oldCacheNode.slots : null + const oldRenderTreeSlots = oldRenderTree.slots - newCacheNode.slots = newCacheNodeSlots = {} + const newRenderTreeSlots = new Map>() + newRenderTree.slots = newRenderTreeSlots taskChildren = new Map() for (let [parallelRouteKey, newRouteTreeChild] of newSlots) { - const oldRouterStateChild: FlightRouterState | void = - oldRouterStateChildren[parallelRouteKey] - if (oldRouterStateChild === undefined) { + const oldRenderTreeChild = oldRenderTreeSlots?.get(parallelRouteKey) + if (oldRenderTreeChild === undefined) { // This should never happen, but if it does, it suggests a malformed // server response. Trigger a full-page navigation. return null } - const oldSegmentChild = oldRouterStateChild[0] - let newSegmentChild = createSegmentFromRouteTree(newRouteTreeChild) + const oldSegmentChild = oldRenderTreeChild.segment + const newSegmentChild = createSegmentFromRouteTree(newRouteTreeChild) let seedHeadChild = seedHead if ( // Skip this branch during a history traversal. We restore the tree that @@ -515,12 +504,9 @@ function updateCacheNodeOnNavigation( // a soft navigation; instead, the client reuses whatever segment was // already active in that slot on the previous route. newRouteTreeChild = reuseActiveSegmentInDefaultSlot( - newRouteTree, - parallelRouteKey, oldRootRefreshState, - oldRouterStateChild + oldRenderTreeChild ) - newSegmentChild = createSegmentFromRouteTree(newRouteTreeChild) // Discard the seed head, which corresponds to the outer route tree, // not the reused one we're switching to. (Segment data needs no @@ -529,16 +515,9 @@ function updateCacheNodeOnNavigation( seedHeadChild = null } - const oldCacheNodeChild = - oldCacheNodeSlots !== null - ? oldCacheNodeSlots[parallelRouteKey] - : undefined - - const taskChild = updateCacheNodeOnNavigation( + const taskChild = updateRenderTreeOnNavigation( navigatedAt, - oldUrl, - oldCacheNodeChild, - oldRouterStateChild, + oldRenderTreeChild, newRouteTreeChild, newMetadataVaryPath, freshness, @@ -562,7 +541,7 @@ function updateCacheNodeOnNavigation( // Recursively propagate up the child tasks. taskChildren.set(parallelRouteKey, taskChild) - newCacheNodeSlots[parallelRouteKey] = taskChild.node + newRenderTreeSlots.set(parallelRouteKey, taskChild.node) // The child tree's route state may be different from the prefetched // route sent by the server. We need to clone it as we traverse back up @@ -596,7 +575,7 @@ function updateCacheNodeOnNavigation( ? NavigationTaskStatus.Pending : NavigationTaskStatus.Fulfilled, route: newFlightRouterState, - node: newCacheNode, + node: newRenderTree, dynamicRequestTree: createDynamicRequestTree( newFlightRouterState, dynamicRequestTreeChildren, @@ -615,7 +594,7 @@ function updateCacheNodeOnNavigation( * navigation share the same ScrollRef — the first segment to scroll * consumes it, preventing others from also scrolling. * - * This is only called inside `createCacheNodeOnNavigation`, which only + * This is only called inside `createRenderTreeOnNavigation`, which only * runs when segments diverge from the previous route. So for a refresh * where the route structure stays the same, segments match, the update * path is taken, and this function is never called — no scroll ref is @@ -653,7 +632,7 @@ function accumulateScrollRef( } } -function createCacheNodeOnNavigation( +function createRenderTreeOnNavigation( navigatedAt: number, newRouteTree: RouteTree, newMetadataVaryPath: VaryPath | null, @@ -667,12 +646,12 @@ function createCacheNodeOnNavigation( // entries. Always false outside the testing API. See navigation-testing-lock. restrictToShell: boolean ): NavigationTask { - // Same traversal as updateCacheNodeNavigation, but simpler. We switch to this + // Same traversal as updateRenderTreeOnNavigation, but simpler. We switch to this // path once we reach the part of the tree that was not in the previous route. // We don't need to diff against the old tree, we just need to create a new // one. We also don't need to worry about any refresh-related logic. // - // For the most part, this is a subset of updateCacheNodeOnNavigation, so any + // For the most part, this is a subset of updateRenderTreeOnNavigation, so any // change that happens in this function likely needs to be applied to that // one, too. However there are some places where the behavior intentionally // diverges, which is why we keep them separate. @@ -683,7 +662,7 @@ function createCacheNodeOnNavigation( const data = newRouteTree.data const seedRsc = data !== null ? data.rsc : null - const result = createCacheNodeForSegment( + const result = createRenderTreeForSegment( navigatedAt, newRouteTree, seedRsc, @@ -697,12 +676,12 @@ function createCacheNodeOnNavigation( map, restrictToShell ) - const newCacheNode = result.cacheNode + const newRenderTree = result.node const needsDynamicRequest = result.needsDynamicRequest const isLeafSegment = newSlots === null if (isLeafSegment) { - accumulateScrollRef(freshness, newCacheNode, accumulation) + accumulateScrollRef(freshness, newRenderTree.data, accumulation) } let patchedRouterStateChildren: { @@ -715,12 +694,12 @@ function createCacheNodeOnNavigation( [parallelRouteKey: string]: FlightRouterState } = {} - let newCacheNodeSlots: Record | null = null if (newSlots !== null) { - newCacheNode.slots = newCacheNodeSlots = {} + const newRenderTreeSlots = new Map>() + newRenderTree.slots = newRenderTreeSlots taskChildren = new Map() for (const [parallelRouteKey, newRouteTreeChild] of newSlots) { - const taskChild = createCacheNodeOnNavigation( + const taskChild = createRenderTreeOnNavigation( navigatedAt, newRouteTreeChild, newMetadataVaryPath, @@ -734,7 +713,7 @@ function createCacheNodeOnNavigation( ) taskChildren.set(parallelRouteKey, taskChild) - newCacheNodeSlots[parallelRouteKey] = taskChild.node + newRenderTreeSlots.set(parallelRouteKey, taskChild.node) const taskChildRoute = taskChild.route patchedRouterStateChildren[parallelRouteKey] = taskChildRoute @@ -762,7 +741,7 @@ function createCacheNodeOnNavigation( ? NavigationTaskStatus.Pending : NavigationTaskStatus.Fulfilled, route: newFlightRouterState, - node: newCacheNode, + node: newRenderTree, dynamicRequestTree: createDynamicRequestTree( newFlightRouterState, dynamicRequestTreeChildren, @@ -777,21 +756,17 @@ function createCacheNodeOnNavigation( } } -function createSegmentFromRouteTree( - newRouteTree: RouteTree +function createSegmentFromRouteTree( + newRouteTree: RouteTree ): Segment { if (newRouteTree.segment === PAGE_SEGMENT_KEY) { // In a dynamic server response, the server embeds the search params into // the segment key, but in a static one it's omitted. The client handles // this inconsistency by adding the search params back right at the end. // - // TODO: The only thing this is used for is to create a cache key for - // ChildSegmentMap. But we already track the `renderedSearch` everywhere as - // part of the varyPath. The plan is get rid of ChildSegmentMap and - // store the page data in a CacheMap using the varyPath, like we do - // for prefetches. Then we can remove it from the segment key. - // // As an incremental step, we can grab the search params from the varyPath. + // + // TODO: Remove the search params from the segment key entirely. const renderedSearch = getRenderedSearchFromVaryPath(newRouteTree.varyPath) if (renderedSearch === null) { return PAGE_SEGMENT_KEY @@ -888,25 +863,23 @@ function accumulateRefreshUrl( } function reuseActiveSegmentInDefaultSlot( - parentRouteTree: RouteTree, - parallelRouteKey: string, oldRootRefreshState: RefreshState, - oldRouterState: FlightRouterState + oldRenderTree: RouteTree ): RouteTree { // This is a "default" segment. These are never sent by the server during a // soft navigation; instead, the client reuses whatever segment was already // active in that slot on the previous route. This means if we later need to // refresh the segment, it will have to be refetched from the previous route's - // URL. We store it in the Flight Router State. + // URL. We store the refresh context on the active render tree. let reusedUrl: string let reusedRenderedSearch: NormalizedSearch - const oldRefreshState = oldRouterState[2] - if (oldRefreshState !== undefined && oldRefreshState !== null) { + const oldRefreshState = oldRenderTree.refreshState + if (oldRefreshState !== null) { // This segment was already reused from an even older route. Keep its // existing URL and refresh state. - reusedUrl = oldRefreshState[0] - reusedRenderedSearch = oldRefreshState[1] as NormalizedSearch + reusedUrl = oldRefreshState.canonicalUrl + reusedRenderedSearch = oldRefreshState.renderedSearch } else { // Since this route didn't already have a refresh state, it must have been // reachable from the root of the old route. So we use the refresh state @@ -915,14 +888,7 @@ function reuseActiveSegmentInDefaultSlot( reusedRenderedSearch = oldRootRefreshState.renderedSearch } - const acc = { metadataVaryPath: null, treeDivergedFromBase: false } - const reusedRouteTree = convertReusedFlightRouterStateToRouteTree( - parentRouteTree, - parallelRouteKey, - oldRouterState, - reusedRenderedSearch, - acc - ) + const reusedRouteTree = rebaseInactiveRouteTree(oldRenderTree) reusedRouteTree.refreshState = { canonicalUrl: reusedUrl, renderedSearch: reusedRenderedSearch, @@ -930,26 +896,23 @@ function reuseActiveSegmentInDefaultSlot( return reusedRouteTree } -function reuseSharedCacheNode( - dropPrefetchRsc: boolean, - existingCacheNode: CacheNode -): CacheNode { - // Clone the CacheNode that was already present in the previous tree. - // Carry forward the scrollRef so scroll intent from a prior navigation - // survives tree rebuilds (e.g. push + refresh in the same batch). - // Carry forward the bfcacheId so shared-layout segments retain stable - // identity across navigations. - return createCacheNode( - existingCacheNode.rsc, - dropPrefetchRsc ? null : existingCacheNode.prefetchRsc, - existingCacheNode.head, - dropPrefetchRsc ? null : existingCacheNode.prefetchHead, - existingCacheNode.bfcacheId, - existingCacheNode.scrollRef - ) +function createRenderTree( + routeTree: RouteTree, + cacheNode: CacheNode +): RouteTree { + return { + requestKey: routeTree.requestKey, + segment: routeTree.segment, + shellVaryPath: routeTree.shellVaryPath, + refreshState: null, + data: cacheNode, + varyPath: routeTree.varyPath, + slots: null, + prefetchHints: routeTree.prefetchHints, + } } -function createCacheNodeForSegment( +function createRenderTreeForSegment( now: number, tree: RouteTree, seedRsc: React.ReactNode | null, @@ -962,8 +925,8 @@ function createCacheNodeForSegment( // Instant Navigation Testing API only — restricts segment reads to shell // entries. Always false outside the testing API. See navigation-testing-lock. restrictToShell: boolean -): { cacheNode: CacheNode; needsDynamicRequest: boolean } { - // Construct a new CacheNode using data from the BFCache, the client's +): { node: RouteTree; needsDynamicRequest: boolean } { + // Construct an owned render tree using data from the BFCache, the client's // Segment Cache, or seeded from a server response. // // If there's a cache miss, or if we only have a partial hit, we'll render @@ -998,12 +961,15 @@ function createCacheNodeForSegment( // BFCacheEntry's id is only restored on history-traversal // navigations. return { - cacheNode: createCacheNode( - bfcacheEntry.rsc, - bfcacheEntry.prefetchRsc, - bfcacheEntry.head, - bfcacheEntry.prefetchHead, - bfcacheId + node: createRenderTree( + tree, + createCacheNode( + bfcacheEntry.rsc, + bfcacheEntry.prefetchRsc, + bfcacheEntry.head, + bfcacheEntry.prefetchHead, + bfcacheId + ) ), needsDynamicRequest: false, } @@ -1026,39 +992,20 @@ function createCacheNodeForSegment( // triggers this path. But it does render correctly despite that. That's an // unusual render path so it's not surprising, but we should look into // modeling it in a more consistent way. See also the /_notFound special - // case in updateCacheNodeOnNavigation. - const rsc = seedRsc - const prefetchRsc = null - const head = isPage ? seedHead : null - const prefetchHead = null - writeToBFCache( - now, - tree.varyPath, - rsc, - prefetchRsc, - head, - prefetchHead, - dynamicStaleAt, + // case in updateRenderTreeOnNavigation. + const cacheNode = createCacheNode( + seedRsc, + null, + isPage ? seedHead : null, + null, bfcacheId ) + writeToBFCache(now, tree.varyPath, cacheNode, dynamicStaleAt) if (isPage && metadataVaryPath !== null) { - writeHeadToBFCache( - now, - metadataVaryPath, - head, - prefetchHead, - dynamicStaleAt, - bfcacheId - ) + writeHeadToBFCache(now, metadataVaryPath, cacheNode, dynamicStaleAt) } return { - cacheNode: createCacheNode( - rsc, - prefetchRsc, - head, - prefetchHead, - bfcacheId - ), + node: createRenderTree(tree, cacheNode), needsDynamicRequest: false, } } @@ -1083,12 +1030,15 @@ function createCacheNodeForSegment( // navigations preserve the original id, regardless of whether // `cacheComponents` Activity preservation is enabled. return { - cacheNode: createCacheNode( - bfcacheEntry.rsc, - dropPrefetchRsc ? null : bfcacheEntry.prefetchRsc, - bfcacheEntry.head, - dropPrefetchRsc ? null : bfcacheEntry.prefetchHead, - bfcacheEntry.bfcacheId + node: createRenderTree( + tree, + createCacheNode( + bfcacheEntry.rsc, + dropPrefetchRsc ? null : bfcacheEntry.prefetchRsc, + bfcacheEntry.head, + dropPrefetchRsc ? null : bfcacheEntry.prefetchHead, + bfcacheEntry.bfcacheId + ) ), needsDynamicRequest: false, } @@ -1293,31 +1243,22 @@ function createCacheNodeForSegment( // // Skip BFCache writes for optimistic navigations since they are transient // and will be replaced by the canonical navigation. + const cacheNode = createCacheNode( + rsc, + prefetchRsc, + head, + prefetchHead, + bfcacheId + ) if (freshness !== FreshnessPolicy.Gesture) { - writeToBFCache( - now, - tree.varyPath, - rsc, - prefetchRsc, - head, - prefetchHead, - dynamicStaleAt, - bfcacheId - ) + writeToBFCache(now, tree.varyPath, cacheNode, dynamicStaleAt) if (isPage && metadataVaryPath !== null) { - writeHeadToBFCache( - now, - metadataVaryPath, - head, - prefetchHead, - dynamicStaleAt, - bfcacheId - ) + writeHeadToBFCache(now, metadataVaryPath, cacheNode, dynamicStaleAt) } } return { - cacheNode: createCacheNode(rsc, prefetchRsc, head, prefetchHead, bfcacheId), + node: createRenderTree(tree, cacheNode), // TODO: We should store this field on the CacheNode itself. I think we can // probably unify NavigationTask, CacheNode, and DeferredRsc into a // single type. Or at least CacheNode and DeferredRsc. @@ -1339,7 +1280,6 @@ function createCacheNode( prefetchRsc, head, prefetchHead, - slots: null, scrollRef, bfcacheId, } @@ -1360,15 +1300,15 @@ function generateBFCacheId(freshness: FreshnessPolicy): number { } const enum SegmentMatchKind { - // Two segments are equivalent: the CacheNode can be reused as-is. + // Two segments are equivalent: the render tree can be reused as-is. Match, // The segments differ in the parts that determine the route (segment kind, - // dynamic param value, etc.). The CacheNode must be created fresh. + // dynamic param value, etc.). The render tree must be created fresh. Change, // Two page segments differ only in their search params. Conceptually this // is a refresh of the current page rather than a navigation to a new // route — search params don't contribute to the LayoutRouter state key, - // and they shouldn't change the bfcacheId either. The CacheNode is rebuilt + // and they shouldn't change the bfcacheId either. The render tree is rebuilt // (so data refetches) but the bfcacheId carries forward. SearchParamOnlyChange, } @@ -1397,7 +1337,7 @@ function compareSegments( let previousNavigationDidMismatch = false // Writes a dynamic server response into the tree created by -// updateCacheNodeOnNavigation. All pending promises that were spawned by the +// updateRenderTreeOnNavigation. All pending promises that were spawned by the // navigation will be resolved, either with dynamic data from the server, or // `null` to indicate that the data is missing. // @@ -1587,7 +1527,7 @@ async function finishNavigationTask( primaryRequestResult.url, nextUrl, primaryRequestResult.seed, - task.route, + task, routeCacheEntry, navigateType, FreshnessPolicy.RefreshAll @@ -1606,7 +1546,7 @@ async function finishNavigationTask( primaryRequestResult.url, nextUrl, primaryRequestResult.seed, - task.route, + task, routeCacheEntry, navigateType, FreshnessPolicy.HistoryTraversal @@ -1629,7 +1569,7 @@ async function finishNavigationTask( primaryRequestResult.url, nextUrl, primaryRequestResult.seed, - task.route, + task, routeCacheEntry, navigateType, FreshnessPolicy.RefreshAll @@ -1652,7 +1592,7 @@ function waitForRequestsToFinish( // we don't assume that's available. // // Each promise resolves once the server responsds and the data is written - // into the CacheNode tree. Resolve the combined promise once all the + // into the render tree. Resolve the combined promise once all the // requests finish. // // Or, resolve as soon as one of the requests fails, without waiting for the @@ -1696,7 +1636,7 @@ function dispatchRetryDueToTreeMismatch( retryUrl: URL, retryNextUrl: string | null, seed: NavigationSeed | null, - baseTree: FlightRouterState, + task: NavigationTask, // The route cache entry used for this navigation, if it came from route // prediction. If the navigation results in a mismatch, we mark it as having // a dynamic rewrite so future predictions bail out. @@ -1748,7 +1688,7 @@ function dispatchRetryDueToTreeMismatch( // the server resolved, its tree is what the server just contradicted, so // the retry must re-fetch it rather than navigate with it again. This also // triggers re-prefetching of visible links. - invalidateRouteCacheEntries(retryNextUrl, baseTree) + invalidateRouteCacheEntries(retryNextUrl, task.node) // If this is the second time in a row that a navigation resulted in a // mismatch, fall back to a hard (MPA) refresh. @@ -1769,6 +1709,7 @@ function dispatchRetryDueToTreeMismatch( // not here where the action is constructed. But the current action queue // doesn't provide a natural place for that. Revisit when we refactor the // action queue into a more reactive navigation model. + const baseTree = task.route const lastCommitted = getLastCommittedTree() const retryNavigateType: 'push' | 'replace' = lastCommitted !== null && baseTree !== lastCommitted @@ -1961,8 +1902,9 @@ function writeDynamicDataIntoNavigationTask( const dynamicData = serverRouteTree.data if (task.status === NavigationTaskStatus.Pending && dynamicData !== null) { task.status = NavigationTaskStatus.Fulfilled + const cacheNode = task.node.data finishPendingCacheNode( - task.node, + cacheNode, dynamicData, dynamicHead, debugInfo, @@ -2004,7 +1946,7 @@ function writeDynamicDataIntoNavigationTask( // path. But as an extra precaution, we validate in prod, too. didReceiveUnknownParallelRoute = true } else { - const taskSegment = taskChild.route[0] + const taskSegment = createSegmentFromRouteTree(taskChild.node) const serverSegment = createSegmentFromRouteTree(serverRouteTreeChild) if ( matchSegment(serverSegment, taskSegment) && @@ -2044,24 +1986,23 @@ function finishPendingCacheNode( debugInfo: Array | null, revealAfter: Promise | null ): void { - // Writes a dynamic response into an existing Cache Node tree. This does _not_ + // Writes a dynamic response into an existing render tree. This does _not_ // create a new tree, it updates the existing tree in-place. So it must follow // the Suspense rules of cache safety — it can resolve pending promises, but // it cannot overwrite existing data. It can add segments to the tree (because - // a missing segment will cause the layout router to suspend). - // but it cannot delete them. + // a missing segment will cause the layout router to suspend) but it cannot + // delete them. // // We must resolve every promise in the tree, or else it will suspend // indefinitely. If we did not receive data for a segment, we will resolve its // data promise to `null` to trigger a lazy fetch during render. - // Use the dynamic data from the server to fulfill the deferred RSC promise - // on the Cache Node. + // Use the dynamic data from the server to fulfill the deferred RSC promise. const rsc = cacheNode.rsc const dynamicSegmentData = dynamicData.rsc if (dynamicSegmentData === null) { - // This is an empty CacheNode; this particular server request did not + // This particular server request did not // render this segment. There may be a separate pending request that will, // though, so we won't abort the task until all pending requests finish. return @@ -2113,7 +2054,7 @@ function abortRemainingPendingTasks( if (task.status === NavigationTaskStatus.Pending) { // The data for this segment is still missing. task.status = NavigationTaskStatus.Rejected - abortPendingCacheNode(task.node, error, debugInfo) + abortPendingCacheNode(task.node.data, error, debugInfo) // If the server failed to fulfill the data for this segment, it implies // that the route tree received from the server mismatched the tree that diff --git a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts index 8c8744aca1d1..d1832d1efdd5 100644 --- a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts +++ b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts @@ -5,7 +5,7 @@ import { extractPathFromFlightRouterState } from './compute-changed-path' import type { AppRouterState } from './router-reducer-types' import { transportNodeToFlightRouterState } from '../../../shared/lib/rsc-transport' -import { createInitialCacheNodeForHydration } from '../render-tree' +import { createInitialRenderTreeForHydration } from '../render-tree' import { writeRuntimePrefetchStreamIntoCache, spawnStaticStageCacheWrite, @@ -71,7 +71,7 @@ export function createInitialRouterState({ // stores this tree in the route cache, which strips the data on write — // see stripDataFromRouteTree.) // NOTE: The metadataVaryPath isn't used for anything currently because the - // head is embedded into the CacheNode tree, but eventually we'll lift it out + // head is embedded into the render tree, but eventually we'll lift it out // and store it on the top-level state object. // // For statically-generated-at-build-time HTML pages, the tree baked into @@ -104,7 +104,7 @@ export function createInitialRouterState({ acc ) const metadataVaryPath = acc.metadataVaryPath - const initialTask = createInitialCacheNodeForHydration( + const initialTask = createInitialRenderTreeForHydration( navigatedAt, initialRouteTree, initialHead, diff --git a/packages/next/src/client/components/router-reducer/is-navigating-to-new-root-layout.ts b/packages/next/src/client/components/router-reducer/is-navigating-to-new-root-layout.ts index b2f575fab110..aef8fb55aa4e 100644 --- a/packages/next/src/client/components/router-reducer/is-navigating-to-new-root-layout.ts +++ b/packages/next/src/client/components/router-reducer/is-navigating-to-new-root-layout.ts @@ -1,10 +1,9 @@ -import type { FlightRouterState } from '../../../shared/lib/app-router-types' import { PrefetchHint } from '../../../shared/lib/app-router-types' -import type { RouteTree, RSCSegmentData } from '../segment-cache/cache' +import type { RouteTree } from '../segment-cache/cache' -export function isNavigatingToNewRootLayout( - currentTree: FlightRouterState, - nextTree: RouteTree +export function isNavigatingToNewRootLayout( + currentTree: RouteTree, + nextTree: RouteTree ): boolean { // Decides whether navigating from currentTree to nextTree crosses into a // different root layout, which requires a full-page (MPA-style) navigation. @@ -16,7 +15,7 @@ export function isNavigatingToNewRootLayout( // dynamic param *values*) for the same depth. So we walk the prefix in // lockstep and report a change as soon as the prefixes diverge. const currentInPrefix = - ((currentTree[4] ?? 0) & PrefetchHint.IsRootLayoutOrAbove) !== 0 + (currentTree.prefetchHints & PrefetchHint.IsRootLayoutOrAbove) !== 0 const nextInPrefix = (nextTree.prefetchHints & PrefetchHint.IsRootLayoutOrAbove) !== 0 @@ -38,7 +37,7 @@ export function isNavigatingToNewRootLayout( // (e.g. /[name] for slug1 vs slug2) still resolve to the same /[name]/layout. // E.g. /same/(group1)/layout.js -> /same/(group2)/layout.js: (group1) changed // to (group2) inside the prefix, so the root layout changed. - const currentTreeSegment = currentTree[0] + const currentTreeSegment = currentTree.segment const nextTreeSegment = nextTree.segment if (Array.isArray(currentTreeSegment) && Array.isArray(nextTreeSegment)) { if ( @@ -54,10 +53,10 @@ export function isNavigatingToNewRootLayout( // Keep walking the prefix. (Above the root layout there is only a `children` // slot, but we traverse all slots defensively.) const slots = nextTree.slots - const currentTreeChildren = currentTree[1] + const currentTreeChildren = currentTree.slots if (slots !== null) { for (const [slot, nextTreeChild] of slots) { - const currentTreeChild = currentTreeChildren[slot] + const currentTreeChild = currentTreeChildren?.get(slot) if ( currentTreeChild === undefined || isNavigatingToNewRootLayout(currentTreeChild, nextTreeChild) diff --git a/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts b/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts index f6e8f0a7bea8..e04d48cade1c 100644 --- a/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts +++ b/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts @@ -1,3 +1,4 @@ +import type { RouteTree } from '../../segment-cache/cache' import type { FlightRouterState, CacheNode, @@ -6,20 +7,20 @@ import { DEFAULT_SEGMENT_KEY } from '../../../../shared/lib/segment' import { createSegmentKey } from '../create-segment-key' export function findHeadInCache( - cache: CacheNode, + cache: RouteTree, parallelRoutes: FlightRouterState[1] -): [CacheNode, string] | null { +): [RouteTree, string] | null { return findHeadInCacheImpl(cache, parallelRoutes, '') } function findHeadInCacheImpl( - cache: CacheNode, + cache: RouteTree, parallelRoutes: FlightRouterState[1], keyPrefix: string -): [CacheNode, string] | null { +): [RouteTree, string] | null { const isLastItem = Object.keys(parallelRoutes).length === 0 if (isLastItem) { - // Returns the entire Cache Node of the segment whose head we will render. + // Returns the render tree of the segment whose head we will render. return [cache, keyPrefix] } @@ -44,8 +45,8 @@ function findHeadInCacheImpl( continue } - const childCacheNode = slots[key] - if (!childCacheNode) { + const childRenderTree = slots.get(key) + if (!childRenderTree) { continue } @@ -54,7 +55,7 @@ function findHeadInCacheImpl( const segmentKey = createSegmentKey(segment) const item = findHeadInCacheImpl( - childCacheNode, + childRenderTree, childParallelRoutes, keyPrefix + '/' + segmentKey ) diff --git a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts index bfbfd9931576..f7b59087ea42 100644 --- a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts @@ -33,8 +33,8 @@ export function refreshReducer( process.env.__NEXT_EXPOSE_TESTING_API && action.bypassCacheInvalidation if (!bypassCacheInvalidation) { const currentNextUrl = state.nextUrl - const currentRouterState = state.tree - invalidateSegmentCacheEntries(currentNextUrl, currentRouterState) + const currentRenderTree = state.cache + invalidateSegmentCacheEntries(currentNextUrl, currentRenderTree) } // A full refresh has no HMR generation to cancel. return refreshDynamicData(state, FreshnessPolicy.RefreshAll, undefined) @@ -100,7 +100,6 @@ export function refreshDynamicData( currentUrl, currentRenderedSearch, state.cache, - currentFlightRouterState, freshnessPolicy, nextUrlForRefresh, scrollBehavior, diff --git a/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts index f6192e5b43c6..9fea9bdbbebc 100644 --- a/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts @@ -70,7 +70,6 @@ export function restoreReducer( currentUrl, state.renderedSearch, state.cache, - state.tree, restoreSeed.routeTree, restoreSeed.metadataVaryPath, FreshnessPolicy.HistoryTraversal, diff --git a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts index 902d1feaa0b4..fde5cbe3c7f2 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts @@ -365,7 +365,7 @@ export function serverActionReducer( // invalidate both caches until we have a way to detect cookie // mutations on the client. if (revalidationKind === ActionDidRevalidateStaticAndDynamic) { - invalidateEntirePrefetchCache(nextUrl, state.tree) + invalidateEntirePrefetchCache(nextUrl, state.cache) } // Start a cooldown before re-prefetching to allow CDN cache @@ -525,7 +525,6 @@ export function serverActionReducer( currentUrl, currentRenderedSearch, state.cache, - currentFlightRouterState, freshnessPolicy, nextUrl, scrollBehavior, diff --git a/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts index a401e3bd4c2f..f3f6088053ec 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts @@ -60,7 +60,6 @@ export function serverPatchReducer( currentUrl, currentRenderedSearch, state.cache, - state.tree, action.freshnessPolicy, retryNextUrl, scrollBehavior, diff --git a/packages/next/src/client/components/router-reducer/router-reducer-types.ts b/packages/next/src/client/components/router-reducer/router-reducer-types.ts index d4937bba7d66..cdef75da5d9e 100644 --- a/packages/next/src/client/components/router-reducer/router-reducer-types.ts +++ b/packages/next/src/client/components/router-reducer/router-reducer-types.ts @@ -1,3 +1,4 @@ +import type { RouteTree } from '../segment-cache/cache' import type { CacheNode, ScrollRef } from '../../../shared/lib/app-router-types' import type { FlightRouterState } from '../../../shared/lib/app-router-types' import type { NavigationSeed } from '../segment-cache/decode-server-response' @@ -185,7 +186,7 @@ export type ScrollHandlerRef = { * When true, the scroll handler uses the navigation-level `scrollRef` * for every segment regardless of per-node state. Used for hash-only * navigations where every segment should be treated as a scroll - * target. When false, the handler checks `cacheNode.scrollRef` + * target. When false, the handler checks `renderTree.data.scrollRef` * instead (per-node), so only segments that actually navigated scroll. */ forceScroll: boolean @@ -213,7 +214,7 @@ export type AppRouterState = { * The cache holds React nodes for every segment that is shown on screen as well as previously shown segments. * It also holds in-progress data requests. */ - cache: CacheNode + cache: RouteTree /** * Decides if the update should create a new history entry and if the navigation has to trigger a browser navigation. */ diff --git a/packages/next/src/client/components/segment-cache/bfcache.ts b/packages/next/src/client/components/segment-cache/bfcache.ts index daaa95e46082..ed2bd72a539c 100644 --- a/packages/next/src/client/components/segment-cache/bfcache.ts +++ b/packages/next/src/client/components/segment-cache/bfcache.ts @@ -1,4 +1,5 @@ import { DYNAMIC_STALETIME_MS } from '../router-reducer/reducers/navigate-reducer' +import type { CacheNode } from '../../../shared/lib/app-router-types' import type { VaryPath } from './vary-path' /** @@ -68,6 +69,24 @@ export function invalidateBfCache(): void { } export function writeToBFCache( + now: number, + varyPath: VaryPath, + cacheNode: CacheNode, + dynamicStaleAt: number +): void { + writeEntryToBFCache( + now, + varyPath, + cacheNode.rsc, + cacheNode.prefetchRsc, + cacheNode.head, + cacheNode.prefetchHead, + dynamicStaleAt, + cacheNode.bfcacheId + ) +} + +function writeEntryToBFCache( now: number, varyPath: VaryPath, rsc: React.ReactNode, @@ -116,21 +135,20 @@ export function writeToBFCache( export function writeHeadToBFCache( now: number, varyPath: VaryPath, - head: React.ReactNode, - prefetchHead: React.ReactNode, - dynamicStaleAt: number, - bfcacheId: number + cacheNode: CacheNode, + dynamicStaleAt: number ): void { - // Read the special "segment" that represents the head data. - writeToBFCache( + // Write the special "segment" that represents the head data. The page + // node's head fields take the place of the entry's segment fields. + writeEntryToBFCache( now, varyPath, - head, - prefetchHead, + cacheNode.head, + cacheNode.prefetchHead, null, null, dynamicStaleAt, - bfcacheId + cacheNode.bfcacheId ) } diff --git a/packages/next/src/client/components/segment-cache/cache.ts b/packages/next/src/client/components/segment-cache/cache.ts index 6eb1ec265e4d..bc05e66e8449 100644 --- a/packages/next/src/client/components/segment-cache/cache.ts +++ b/packages/next/src/client/components/segment-cache/cache.ts @@ -1,5 +1,5 @@ +import type { CacheNode, Segment } from '../../../shared/lib/app-router-types' import type React from 'react' -import type { Segment as FlightRouterStateSegment } from '../../../shared/lib/app-router-types' import { PrefetchHint, StaticAttemptHints, @@ -201,9 +201,7 @@ export type RSCSegmentData = { export type RouteTree = { requestKey: SegmentRequestKey - // TODO: Remove the `segment` field, now that it can be reconstructed - // from `param`. - segment: FlightRouterStateSegment + segment: Segment varyPath: VaryPath // The vary path used for shell-scoped keying of this segment: the // segment's vary path with every non-root param replaced with Fallback @@ -212,14 +210,6 @@ export type RouteTree = { // don't have to recompute it on every shell request. shellVaryPath: VaryPath refreshState: RefreshState | null - // Render output for this segment, when the tree was created from a server - // response that rendered it. The type parameter encodes a lifecycle - // invariant: trees stored long-term in the route cache - // (RouteCacheEntry.tree / .metadata) are RouteTree — structure only — - // so RSC payloads can never be pinned in memory outside the segment - // cache's eviction control. Trees that carry data must be transient: - // created for a navigation or cache-write, then dropped once the data is - // transferred into CacheNodes / SegmentCacheEntries. data: TData // Keyed by parallel route slot name. Stored as a Map rather than a plain // object because slot names are app-defined; with a plain object, every @@ -454,7 +444,7 @@ export function getCurrentSegmentCacheVersion(): number { */ export function invalidateEntirePrefetchCache( nextUrl: string | null, - tree: FlightRouterState + tree: RouteTree ): void { currentRouteCacheVersion++ currentSegmentCacheVersion++ @@ -472,7 +462,7 @@ export function invalidateEntirePrefetchCache( */ export function invalidateRouteCacheEntries( nextUrl: string | null, - tree: FlightRouterState + tree: RouteTree ): void { currentRouteCacheVersion++ @@ -489,7 +479,7 @@ export function invalidateRouteCacheEntries( */ export function invalidateSegmentCacheEntries( nextUrl: string | null, - tree: FlightRouterState + tree: RouteTree ): void { currentSegmentCacheVersion++ @@ -534,7 +524,7 @@ function notifyInvalidationListener(task: PrefetchTask): void { export function pingInvalidationListeners( nextUrl: string | null, - tree: FlightRouterState + cache: RouteTree ): void { // The rough equivalent of pingVisibleLinks, but for onInvalidate callbacks. // This is called when the Next-Url or the base tree changes, since those @@ -544,7 +534,7 @@ export function pingInvalidationListeners( const tasks = invalidationListeners invalidationListeners = null for (const task of tasks) { - if (isPrefetchTaskDirty(task, nextUrl, tree)) { + if (isPrefetchTaskDirty(task, nextUrl, cache)) { notifyInvalidationListener(task) } } @@ -1639,36 +1629,31 @@ export function convertRootFlightRouterStateToRouteTree( ) } -export function convertReusedFlightRouterStateToRouteTree( - parentRouteTree: RouteTree, - parallelRouteKey: string, - flightRouterState: FlightRouterState, - renderedSearch: NormalizedSearch, - acc: RouteTreeAccumulator -) { - // Create a RouteTree for a FlightRouterState that was reused from an older - // route. This happens during a navigation when a parallel route slot does not - // match the target route; we reuse whatever slot was already active. - - // Unlike a FlightRouterState, the RouteTree type contains backreferences to - // the parent segments. Append the vary path to the parent's vary path. - const parentPartialVaryPath = getPartialVaryPath(parentRouteTree.varyPath) - const segment = flightRouterState[0] - // And the request key. - const parentRequestKey = parentRouteTree.requestKey - const requestKeyPart = createSegmentRequestKeyPart(segment) - const requestKey = appendSegmentRequestKeyPart( - parentRequestKey, - parallelRouteKey, - requestKeyPart - ) - return convertFlightRouterStateToRouteTree( - flightRouterState, - requestKey, - parentPartialVaryPath, - renderedSearch, - acc - ) +export function rebaseInactiveRouteTree( + treeToRebase: RouteTree +): RouteTree { + // A parallel route slot that the target route doesn't provide keeps the + // slot already active on the current route, under the new parent. The slot + // sits at the same route position, so its request keys and vary paths are + // unchanged: copy the structure and drop the payloads, which belong to the + // previous render and are reused separately by render-tree. + let slots: Map> | null = null + if (treeToRebase.slots !== null) { + slots = new Map() + for (const [parallelRouteKey, child] of treeToRebase.slots) { + slots.set(parallelRouteKey, rebaseInactiveRouteTree(child)) + } + } + return { + requestKey: treeToRebase.requestKey, + segment: treeToRebase.segment, + varyPath: treeToRebase.varyPath, + shellVaryPath: treeToRebase.shellVaryPath, + refreshState: treeToRebase.refreshState, + data: null, + slots, + prefetchHints: treeToRebase.prefetchHints, + } } export function convertFlightRouterStateToRouteTree( @@ -1744,8 +1729,8 @@ export function convertFlightRouterStateToRouteTree( return tree } -export function convertRouteTreeToFlightRouterState( - routeTree: RouteTree +export function convertRouteTreeToFlightRouterState( + routeTree: RouteTree ): FlightRouterState { const parallelRoutes: Record = {} const slots = routeTree.slots diff --git a/packages/next/src/client/components/segment-cache/decode-server-response.ts b/packages/next/src/client/components/segment-cache/decode-server-response.ts index 07ca19cf619e..6183500f6172 100644 --- a/packages/next/src/client/components/segment-cache/decode-server-response.ts +++ b/packages/next/src/client/components/segment-cache/decode-server-response.ts @@ -230,9 +230,8 @@ export function createNavigationSeed( * information (vary paths, the normalized segment value) * initialized, and the remaining fields set to their defaults. The caller * finishes initializing those in place after recursing into the children. - * Shared by the FlightRouterState converter and the transport decoder so the - * two cannot drift, and so every node they produce has the same property - * order (one hidden class). + * Shared by FlightRouterState conversion, transport decoding, and subtree + * rebasing so their routing identity stays consistent. */ export function createRouteTreeNode( originalSegment: FlightRouterStateSegment, @@ -323,7 +322,7 @@ export function createRouteTreeNode( * * TODO: The base is a FlightRouterState only because that's the * representation the client router currently renders from (the router - * reducer's `state.tree`, which the CacheNode tree and layout-router are + * reducer's `state.tree`, which the render tree and layout-router are * keyed against). Once the rendering path is updated to use RouteTree as its * source of truth, the base tree here can be a RouteTree, and the base-only * conversion path (convertFlightRouterStateToRouteTree) goes away with it. diff --git a/packages/next/src/client/components/segment-cache/scheduler.ts b/packages/next/src/client/components/segment-cache/scheduler.ts index 3b204484befc..aec472fe958f 100644 --- a/packages/next/src/client/components/segment-cache/scheduler.ts +++ b/packages/next/src/client/components/segment-cache/scheduler.ts @@ -1,13 +1,13 @@ +import { matchSegment } from '../match-segments' +import { getRenderedSearchFromVaryPath } from './vary-path' import type { FlightRouterState, - Segment as FlightRouterStateSegment, - Segment, + CacheNode, } from '../../../shared/lib/app-router-types' import { PrefetchHint, StaticPrefetchDisabled, } from '../../../shared/lib/app-router-types' -import { matchSegment } from '../match-segments' import { readOrCreateRouteCacheEntry, readRouteCacheEntry, @@ -29,9 +29,8 @@ import { attemptToFulfillDynamicSegmentFromBFCache, attemptToUpgradeSegmentFromBFCache, } from './cache' -import type { RouteCacheKey } from './cache-key' +import type { NormalizedSearch, RouteCacheKey } from './cache-key' import { createCacheKey } from './cache-key' -import { urlSearchParamsToParsedUrlQuery } from '../../route-params' import { FetchStrategy, type PrefetchTaskFetchStrategy, @@ -44,10 +43,7 @@ import { } from './cache' import type { CacheMap } from './cache-map' import type { NavigationLockPrefetch } from './navigation-testing-lock' -import { - addSearchParamsIfPageSegment, - PAGE_SEGMENT_KEY, -} from '../../../shared/lib/segment' +import { PAGE_SEGMENT_KEY } from '../../../shared/lib/segment' import type { SegmentRequestKey } from '../../../shared/lib/segment-cache/segment-value-encoding' import { cleanup } from './lru' @@ -66,12 +62,8 @@ const scheduleMicrotask = export type PrefetchTask = { key: RouteCacheKey - /** - * The FlightRouterState at the time the task was initiated. This is needed - * when falling back to the non-PPR behavior, which only prefetches up to - * the first loading boundary. - */ - treeAtTimeOfPrefetch: FlightRouterState + // The active render tree when this task was scheduled. + renderTreeAtTimeOfPrefetch: RouteTree /** * The cache versions at the time the task was initiated. Used to determine @@ -308,7 +300,7 @@ export type IncludeDynamicData = null | 'full' | 'dynamic' * expected to be validated and normalized. * * @param key The RouteCacheKey to prefetch. - * @param treeAtTimeOfPrefetch The app's current FlightRouterState + * @param renderTreeAtTimeOfPrefetch The active render tree and its vary paths * @param fetchStrategy Whether to prefetch dynamic data, in addition to * static data. This is used by ``. * @param navigationLockPrefetch Testing API only. Non-null when this prefetch @@ -317,7 +309,7 @@ export type IncludeDynamicData = null | 'full' | 'dynamic' */ export function schedulePrefetchTask( key: RouteCacheKey, - treeAtTimeOfPrefetch: FlightRouterState, + renderTreeAtTimeOfPrefetch: RouteTree, fetchStrategy: PrefetchTaskFetchStrategy, priority: PrefetchPriority, onInvalidate: null | (() => void), @@ -341,7 +333,7 @@ export function schedulePrefetchTask( // Spawn a new prefetch task const task: PrefetchTask = { key, - treeAtTimeOfPrefetch, + renderTreeAtTimeOfPrefetch, routeCacheVersion: getCurrentRouteCacheVersion(), segmentCacheVersion: getCurrentSegmentCacheVersion(), segmentCacheMap: taskSegmentCacheMap, @@ -391,7 +383,7 @@ export function cancelPrefetchTask(task: PrefetchTask): void { export function reschedulePrefetchTask( task: PrefetchTask, - treeAtTimeOfPrefetch: FlightRouterState, + renderTreeAtTimeOfPrefetch: RouteTree, fetchStrategy: PrefetchTaskFetchStrategy, priority: PrefetchPriority ): void { @@ -419,7 +411,7 @@ export function reschedulePrefetchTask( // Intent priority, even if the rescheduled priority is lower. task === mostRecentlyHoveredLink ? PrefetchPriority.Intent : priority - task.treeAtTimeOfPrefetch = treeAtTimeOfPrefetch + task.renderTreeAtTimeOfPrefetch = renderTreeAtTimeOfPrefetch task.fetchStrategy = fetchStrategy trackMostRecentlyHoveredLink(task) @@ -436,7 +428,7 @@ export function reschedulePrefetchTask( export function isPrefetchTaskDirty( task: PrefetchTask, nextUrl: string | null, - tree: FlightRouterState + cache: RouteTree ): boolean { // This is used to quickly bail out of a prefetch task if the result is // guaranteed to not have changed since the task was initiated. This is @@ -446,7 +438,7 @@ export function isPrefetchTaskDirty( return ( task.routeCacheVersion !== getCurrentRouteCacheVersion() || task.segmentCacheVersion !== getCurrentSegmentCacheVersion() || - task.treeAtTimeOfPrefetch !== tree || + task.renderTreeAtTimeOfPrefetch !== cache || task.key.nextUrl !== nextUrl ) } @@ -923,7 +915,7 @@ function pingRootRouteTree( now, task, route, - task.treeAtTimeOfPrefetch, + task.renderTreeAtTimeOfPrefetch, tree, null, staticWalkStrategy @@ -1007,7 +999,7 @@ function pingRootRouteTree( now, task, route, - task.treeAtTimeOfPrefetch, + task.renderTreeAtTimeOfPrefetch, tree, spawnedEntries, fetchStrategy @@ -1324,7 +1316,7 @@ function pingSharedPartOfCacheComponentsTree( now: number, task: PrefetchTask, route: FulfilledRouteCacheEntry, - oldTree: FlightRouterState, + oldTree: RouteTree, newTree: RouteTree, parentBundle: SegmentBundle | null, // The per-pass static walk strategy; see pingRootRouteTree where @@ -1359,7 +1351,7 @@ function pingSharedPartOfCacheComponentsTree( ).bundle // Recursively ping the children. - const oldTreeChildren = oldTree[1] + const oldSlots = oldTree.slots const newTreeChildren = newTree.slots if (newTreeChildren !== null) { for (const [parallelRouteKey, newTreeChild] of newTreeChildren) { @@ -1367,11 +1359,7 @@ function pingSharedPartOfCacheComponentsTree( // Stop prefetching segments until there's more bandwidth. return PrefetchTaskExitStatus.InProgress } - const newTreeChildSegment = newTreeChild.segment - const oldTreeChild: FlightRouterState | void = - oldTreeChildren[parallelRouteKey] - const oldTreeChildSegment: FlightRouterStateSegment | void = - oldTreeChild?.[0] + const oldTreeChild = oldSlots?.get(parallelRouteKey) // Only pass the bundle to the child that accepts it. A parent is // only ever bundled into one child. const bundleForChild = @@ -1382,12 +1370,8 @@ function pingSharedPartOfCacheComponentsTree( : null let childExitStatus if ( - oldTreeChildSegment !== undefined && - doesCurrentSegmentMatchCachedSegment( - route, - newTreeChildSegment, - oldTreeChildSegment - ) + oldTreeChild !== undefined && + doesCurrentSegmentMatchCachedSegment(route, oldTreeChild, newTreeChild) ) { // We're still in the "shared" part of the tree. childExitStatus = pingSharedPartOfCacheComponentsTree( @@ -1573,7 +1557,7 @@ function diffRouteTreeAgainstCurrent( now: number, task: PrefetchTask, route: FulfilledRouteCacheEntry, - oldTree: FlightRouterState, + oldTree: RouteTree, newTree: RouteTree, spawnedEntries: Map, fetchStrategy: @@ -1582,31 +1566,22 @@ function diffRouteTreeAgainstCurrent( | FetchStrategy.LoadingBoundary ): FlightRouterState { // This is a single recursive traversal that does multiple things: - // - Finds the parts of the target route (newTree) that are not part of - // of the current page (oldTree) by diffing them, using the same algorithm - // as a real navigation. + // - Finds the segments that differ from the current route, comparing each + // segment's identity as we traverse. // - Constructs a request tree (FlightRouterState) that describes which // segments need to be prefetched and which ones are already cached. // - Creates a set of pending cache entries for the segments that need to // be prefetched, so that a subsequent prefetch task does not request the // same segments again. - const oldTreeChildren = oldTree[1] + const oldSlots = oldTree.slots const newTreeChildren = newTree.slots let requestTreeChildren: Record = {} if (newTreeChildren !== null) { for (const [parallelRouteKey, newTreeChild] of newTreeChildren) { - const newTreeChildSegment = newTreeChild.segment - const oldTreeChild: FlightRouterState | void = - oldTreeChildren[parallelRouteKey] - const oldTreeChildSegment: FlightRouterStateSegment | void = - oldTreeChild?.[0] + const oldTreeChild = oldSlots?.get(parallelRouteKey) if ( - oldTreeChildSegment !== undefined && - doesCurrentSegmentMatchCachedSegment( - route, - newTreeChildSegment, - oldTreeChildSegment - ) + oldTreeChild !== undefined && + doesCurrentSegmentMatchCachedSegment(route, oldTreeChild, newTreeChild) ) { // This segment is already part of the current route. Keep traversing. const requestTreeChild = diffRouteTreeAgainstCurrent( @@ -2624,34 +2599,25 @@ function pingFullSegmentRevalidation( } } +// TODO: Removed in a later change, which compares route structure by +// request key. function doesCurrentSegmentMatchCachedSegment( route: FulfilledRouteCacheEntry, - currentSegment: Segment, - cachedSegment: Segment + currentTree: RouteTree, + cachedTree: RouteTree ): boolean { - if (cachedSegment === PAGE_SEGMENT_KEY) { - // In the FlightRouterState stored by the router, the page segment has the - // rendered search params appended to the name of the segment. In the - // prefetch cache, however, this is stored separately. So, when comparing - // the router's current FlightRouterState to the cached FlightRouterState, - // we need to make sure we compare both parts of the segment. - // TODO: This is not modeled clearly. We use the same type, - // FlightRouterState, for both the CacheNode tree _and_ the prefetch cache - // _and_ the server response format, when conceptually those are three - // different things and treated in different ways. We should encode more of - // this information into the type design so mistakes are less likely. - return ( - currentSegment === - addSearchParamsIfPageSegment( - PAGE_SEGMENT_KEY, - urlSearchParamsToParsedUrlQuery( - new URLSearchParams(route.renderedSearch) - ) - ) - ) + if (!matchSegment(currentTree.segment, cachedTree.segment)) { + return false + } + if (cachedTree.segment === PAGE_SEGMENT_KEY) { + // The render tree stores the page's rendered search on its vary path; the + // route cache stores it on the route entry. + const currentSearch = + getRenderedSearchFromVaryPath(currentTree.varyPath) ?? + ('' as NormalizedSearch) + return currentSearch === route.renderedSearch } - // Non-page segments are compared using the same function as the server - return matchSegment(cachedSegment, currentSegment) + return true } /** diff --git a/packages/next/src/client/components/segment-cache/vary-path.ts b/packages/next/src/client/components/segment-cache/vary-path.ts index 1c352237d0ad..093a37845e91 100644 --- a/packages/next/src/client/components/segment-cache/vary-path.ts +++ b/packages/next/src/client/components/segment-cache/vary-path.ts @@ -4,7 +4,7 @@ import type { NormalizedSearch, NormalizedNextUrl, } from './cache-key' -import type { RouteTree, RSCSegmentData } from './cache' +import type { RouteTree } from './cache' import { Fallback, type FallbackType } from './cache-map' import { HEAD_REQUEST_KEY, @@ -251,9 +251,9 @@ export function finalizeMetadataVaryPath( ) } -export function getSegmentVaryPathForRequest( +export function getSegmentVaryPathForRequest( fetchStrategy: FetchStrategy, - tree: RouteTree + tree: RouteTree ): VaryPath { // This is used for storing pending requests in the cache. We want to choose // the most generic vary path based on the strategy used to fetch it, i.e. diff --git a/packages/next/src/shared/lib/app-router-context.shared-runtime.ts b/packages/next/src/shared/lib/app-router-context.shared-runtime.ts index a1a83495aef2..b77ef9627a28 100644 --- a/packages/next/src/shared/lib/app-router-context.shared-runtime.ts +++ b/packages/next/src/shared/lib/app-router-context.shared-runtime.ts @@ -1,5 +1,7 @@ 'use client' +import type { RouteTree } from '../../client/components/segment-cache/cache' + import type { ScrollHandlerRef, PrefetchKind, @@ -94,7 +96,7 @@ export const AppRouterContext = React.createContext( ) export const LayoutRouterContext = React.createContext<{ parentTree: FlightRouterState - parentCacheNode: CacheNode + parentRenderTree: RouteTree parentSegmentPath: FlightSegmentPath | null parentParams: Params parentLoadingData: LoadingModuleData | null diff --git a/packages/next/src/shared/lib/app-router-types.ts b/packages/next/src/shared/lib/app-router-types.ts index f59b446d4ab8..a379db272b9e 100644 --- a/packages/next/src/shared/lib/app-router-types.ts +++ b/packages/next/src/shared/lib/app-router-types.ts @@ -18,9 +18,9 @@ import type { FullTransportData, PartialTransportData } from './rsc-transport' export type HeadData = React.ReactNode /** - * Cache node used in app-router / layout-router. + * Render state for a segment. Reuse this object while its data is unchanged; + * create a new one when a navigation replaces the segment's data. */ - export type CacheNode = { /** * When rsc is not null, it represents the RSC data for the @@ -49,8 +49,6 @@ export type CacheNode = { head: HeadData - slots: Record | null - /** * A shared mutable ref that tracks whether this segment should be scrolled * to. All new segments created during a single navigation share the same @@ -371,7 +369,7 @@ export function propagateSubtreeBits( /** * A path through the segment tree: a repeating sequence of segment and * parallel route key. Used by the client to address positions in the - * CacheNode tree (see layout-router). + * render tree (see layout-router). */ export type FlightSegmentPath = // Uses `any` as repeating pattern can't be typed. diff --git a/test/development/browser-logs/browser-logs.test.ts b/test/development/browser-logs/browser-logs.test.ts index 9de931a2b3ca..68955dce2176 100644 --- a/test/development/browser-logs/browser-logs.test.ts +++ b/test/development/browser-logs/browser-logs.test.ts @@ -377,14 +377,14 @@ describe(`Terminal Logging (${bundlerName})`, () => { https://react.dev/link/hydration-mismatch ... - - + + - + diff --git a/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/@side/default.tsx b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/@side/default.tsx new file mode 100644 index 000000000000..86b9e9a38812 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/@side/default.tsx @@ -0,0 +1,3 @@ +export default function Default() { + return null +} diff --git a/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/@side/one/page.tsx b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/@side/one/page.tsx new file mode 100644 index 000000000000..f7d103e9ed7a --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/@side/one/page.tsx @@ -0,0 +1,16 @@ +import { connection } from 'next/server' + +export default async function Side({ + searchParams, +}: { + searchParams: Promise<{ value?: string }> +}) { + await connection() + const { value } = await searchParams + return ( + <> +

{value}

+

{crypto.randomUUID()}

+ + ) +} diff --git a/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/layout.tsx b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/layout.tsx new file mode 100644 index 000000000000..893d72c2f6f3 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/layout.tsx @@ -0,0 +1,25 @@ +import Link from 'next/link' +import { Suspense } from 'react' +import { RefreshButton } from '../components/RefreshButton' + +export default function Layout({ + children, + side, +}: { + children: React.ReactNode + side: React.ReactNode +}) { + return ( + <> + + Two + + + Three + + + {children} + {side} + + ) +} diff --git a/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/one/page.tsx b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/one/page.tsx new file mode 100644 index 000000000000..7397dc3abb9f --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/one/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

one

+} diff --git a/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/three/page.tsx b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/three/page.tsx new file mode 100644 index 000000000000..02458ac83b6b --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/three/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

three

+} diff --git a/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/two/page.tsx b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/two/page.tsx new file mode 100644 index 000000000000..add603d1a517 --- /dev/null +++ b/test/e2e/app-dir/parallel-routes-revalidation/app/retained-search/two/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return

two

+} diff --git a/test/e2e/app-dir/parallel-routes-revalidation/parallel-routes-revalidation.test.ts b/test/e2e/app-dir/parallel-routes-revalidation/parallel-routes-revalidation.test.ts index 6105af962902..23a0e2866474 100644 --- a/test/e2e/app-dir/parallel-routes-revalidation/parallel-routes-revalidation.test.ts +++ b/test/e2e/app-dir/parallel-routes-revalidation/parallel-routes-revalidation.test.ts @@ -1,5 +1,7 @@ import { nextTestSetup } from 'e2e-utils' import { check, retry } from 'next-test-utils' +import { createRouterAct } from 'router-act' +import type * as Playwright from 'playwright' describe('parallel-routes-revalidation', () => { const { next, isNextDev, isNextStart, isNextDeploy } = nextTestSetup({ @@ -43,6 +45,43 @@ describe('parallel-routes-revalidation', () => { }) } + it('refreshes a retained slot using its original URL after multiple navigations', async () => { + let act: ReturnType + const browser = await next.browser('/retained-search/one?value=first', { + beforePageLoad(p: Playwright.Page) { + act = createRouterAct(p) + }, + }) + const originalRender = await browser.elementById('retained-render').text() + expect(await browser.elementById('retained-value').text()).toBe('first') + + for (const [page, value] of [ + ['two', 'second'], + ['three', 'third'], + ]) { + await act(async () => { + await browser + .elementByCss(`a[href="/retained-search/${page}?value=${value}"]`) + .click() + }) + expect(await browser.elementById('active-page').text()).toBe(page) + expect(await browser.elementById('retained-value').text()).toBe('first') + expect(await browser.elementById('retained-render').text()).toBe( + originalRender + ) + } + + await act(async () => { + await browser.elementById('refresh-button').click() + }) + expect(await browser.elementById('retained-render').text()).not.toBe( + originalRender + ) + expect(await browser.elementById('retained-value').text()).toBe('first') + expect(await browser.elementById('active-page').text()).toBe('three') + expect(new URL(await browser.url()).search).toBe('?value=third') + }) + it('should handle router.refresh() when called in a slot', async () => { const browser = await next.browser('/') await check(() => browser.hasElementByCssSelector('#refresh-router'), false) From 2f19ae8e01e544e21dcd0a26142e59ade22a5ebc Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 25 Sep 2026 00:25:27 -0400 Subject: [PATCH 08/13] Record which params a CacheNode's data depends on (#98972) Adds a `varyParams` field to CacheNode. When present, they represent the set of params that the data for that segment varies by. (When null, the set is unknown.) Currently, the vary params are only known when the data is fully cached; vary params are not recorded during a dynamic render. However, this will change in a future PR once we've integrated with the experimental Ledgers feature in React. --- .../next/src/client/components/render-tree.ts | 65 ++++++++--- .../create-initial-router-state.ts | 6 +- .../components/segment-cache/bfcache.ts | 41 +++++-- .../client/components/segment-cache/cache.ts | 102 +++++++++++------- .../segment-cache/decode-server-response.ts | 34 +++--- .../next/src/shared/lib/app-router-types.ts | 14 ++- .../lib/segment-cache/vary-params-decoding.ts | 65 ++++++++--- 7 files changed, 230 insertions(+), 97 deletions(-) diff --git a/packages/next/src/client/components/render-tree.ts b/packages/next/src/client/components/render-tree.ts index 53c6974c79f0..1cda2f581f18 100644 --- a/packages/next/src/client/components/render-tree.ts +++ b/packages/next/src/client/components/render-tree.ts @@ -52,9 +52,10 @@ import { readFromBFCacheDuringRegularNavigation, writeToBFCache, writeHeadToBFCache, - updateBFCacheEntryStaleAt, + updateBFCacheEntryFromDynamicResponse, computeDynamicStaleAt, } from './segment-cache/bfcache' +import type { VaryParams } from '../../shared/lib/segment-cache/vary-params-decoding' // This is yet another tree type that is used to track pending promises that // need to be fulfilled once the dynamic data is received. The terminal nodes of @@ -389,10 +390,12 @@ function updateRenderTreeOnNavigation( // new one. const data = newRouteTree.data const seedRsc = data !== null ? data.rsc : null + const seedVaryParams = data !== null ? data.varyParams : null const result = createRenderTreeForSegment( navigatedAt, newRouteTree, seedRsc, + seedVaryParams, newMetadataVaryPath, seedHead, freshness, @@ -662,10 +665,12 @@ function createRenderTreeOnNavigation( const data = newRouteTree.data const seedRsc = data !== null ? data.rsc : null + const seedVaryParams = data !== null ? data.varyParams : null const result = createRenderTreeForSegment( navigatedAt, newRouteTree, seedRsc, + seedVaryParams, newMetadataVaryPath, seedHead, freshness, @@ -916,6 +921,7 @@ function createRenderTreeForSegment( now: number, tree: RouteTree, seedRsc: React.ReactNode | null, + seedVaryParams: VaryParams | null, metadataVaryPath: VaryPath | null, seedHead: HeadData | null, freshness: FreshnessPolicy, @@ -966,6 +972,7 @@ function createRenderTreeForSegment( createCacheNode( bfcacheEntry.rsc, bfcacheEntry.prefetchRsc, + bfcacheEntry.varyParams, bfcacheEntry.head, bfcacheEntry.prefetchHead, bfcacheId @@ -996,6 +1003,7 @@ function createRenderTreeForSegment( const cacheNode = createCacheNode( seedRsc, null, + seedVaryParams, isPage ? seedHead : null, null, bfcacheId @@ -1035,6 +1043,7 @@ function createRenderTreeForSegment( createCacheNode( bfcacheEntry.rsc, dropPrefetchRsc ? null : bfcacheEntry.prefetchRsc, + bfcacheEntry.varyParams, bfcacheEntry.head, dropPrefetchRsc ? null : bfcacheEntry.prefetchHead, bfcacheEntry.bfcacheId @@ -1056,6 +1065,7 @@ function createRenderTreeForSegment( let cachedRsc: React.ReactNode | null = null let isCachedRscPartial: boolean = true + let cachedVaryParams: VaryParams | null = null const segmentEntry = readSegmentCacheEntryForNavigation( now, @@ -1069,6 +1079,7 @@ function createRenderTreeForSegment( // Happy path: a cache hit cachedRsc = segmentEntry.rsc isCachedRscPartial = segmentEntry.isPartial + cachedVaryParams = segmentEntry.varyParams break } case EntryStatus.Pending: { @@ -1079,6 +1090,8 @@ function createRenderTreeForSegment( cachedRsc = promiseForFulfilledEntry.then((entry) => entry !== null ? entry.rsc : null ) + // The entry's data hasn't arrived, and neither has the source of the + // params it depends on; `cachedVaryParams` stays null. // Because the request is still pending, we typically don't know yet // whether the response will be partial. We shouldn't skip this segment // during the dynamic navigation request. Otherwise, we might need to @@ -1115,6 +1128,10 @@ function createRenderTreeForSegment( // means the data failed to load; the LayoutRouter will suspend indefinitely // until the router updates again (refer to finishNavigationTask). let rsc: React.ReactNode | null + // The source of the params `rsc` depends on. A server response or a + // fulfilled segment cache entry carries one; a deferred `rsc` gets its + // source when the response arrives (finishPendingCacheNode). + let varyParams: VaryParams | null let doesSegmentNeedDynamicRequest: boolean if (seedRsc !== null) { @@ -1124,6 +1141,7 @@ function createRenderTreeForSegment( // partial cached state in the meantime. prefetchRsc = cachedRsc rsc = seedRsc + varyParams = seedVaryParams } else { // We already have a completely cached segment. Ignore the seed data, // which may still be streaming in. This shouldn't happen in the normal @@ -1131,6 +1149,7 @@ function createRenderTreeForSegment( // already fully cached, and the server will skip rendering them. prefetchRsc = null rsc = cachedRsc + varyParams = cachedVaryParams } doesSegmentNeedDynamicRequest = false } else { @@ -1143,10 +1162,12 @@ function createRenderTreeForSegment( // data arrives from the server. prefetchRsc = cachedRsc rsc = createDeferredRsc() + varyParams = null } else { // The data is fully cached. prefetchRsc = null rsc = cachedRsc + varyParams = cachedVaryParams } doesSegmentNeedDynamicRequest = isCachedRscPartial } @@ -1246,6 +1267,7 @@ function createRenderTreeForSegment( const cacheNode = createCacheNode( rsc, prefetchRsc, + varyParams, head, prefetchHead, bfcacheId @@ -1270,6 +1292,7 @@ function createRenderTreeForSegment( function createCacheNode( rsc: React.ReactNode | null, prefetchRsc: React.ReactNode | null, + varyParams: VaryParams | null, head: React.ReactNode | null, prefetchHead: HeadData | null, bfcacheId: number, @@ -1278,6 +1301,7 @@ function createCacheNode( return { rsc, prefetchRsc, + varyParams, head, prefetchHead, scrollRef, @@ -1769,9 +1793,7 @@ async function fetchMissingDynamicData( task.route, result.transportData, // Navigation responses stream in incrementally, so their vary params - // can't be drained here — and nothing consumes them from a navigation - // seed (only segment-cache writes read vary params, and those decode - // their own, buffered, payloads). + // can't be drained here; they decode as null. null, result.isResponsePartial, // Navigation responses always include the param values in the tree, so @@ -1911,12 +1933,17 @@ function writeDynamicDataIntoNavigationTask( revealAfter ) - // Update the BFCache entry's staleAt for this segment with the value - // from the dynamic response. This applies the per-page - // unstable_dynamicStaleTime if set, or the default DYNAMIC_STALETIME_MS. - // We only update segments that received dynamic data — static segments - // are unaffected. - updateBFCacheEntryStaleAt(serverRouteTree.varyPath, dynamicStaleAt) + // The BFCache entry for this segment was written before the response + // arrived. Bring it up to date with what the response filled in: its + // staleAt (the per-page unstable_dynamicStaleTime if set, or the default + // DYNAMIC_STALETIME_MS) and the source of the params its data depends + // on. We only update segments that received dynamic data — static + // segments are unaffected. + updateBFCacheEntryFromDynamicResponse( + serverRouteTree.varyPath, + cacheNode, + dynamicStaleAt + ) } const taskChildren = task.children @@ -2008,20 +2035,27 @@ function finishPendingCacheNode( return } + // TODO: `varyParams` must always describe the render that produced `rsc`, + // but nothing in the CacheNode type ties the two fields together; this + // function keeps them in lockstep by writing both at once. Eventually the + // whole CacheNode should be a thenable whose fields are populated through + // dedicated helpers that own the state transition. if (rsc === null) { // This is a lazy cache node. We can overwrite it. This is only safe // because we know that the LayoutRouter suspends if `rsc` is `null`. cacheNode.rsc = dynamicSegmentData - } else if (isDeferredRsc(rsc)) { + cacheNode.varyParams = dynamicData.varyParams + } else if (isDeferredRsc(rsc) && rsc.status === 'pending') { // This is a deferred RSC promise. We can fulfill it with the data we just - // received from the server. If it was already resolved by a different - // navigation, then this does nothing because we can't overwrite data. + // received from the server. The source of the params that data depends + // on travels with it. // // In the streaming dev render, defer the fill until `revealAfter` settles, // so React doesn't render the boundary's children before their row has been // decoded (otherwise it suspends on the still-pending children and commits // a premature fallback). Outside that render `revealAfter` is null and we // resolve immediately. + cacheNode.varyParams = dynamicData.varyParams if (revealAfter !== null) { const resolveRsc = () => rsc.resolve(dynamicSegmentData, debugInfo) // Use the same callback for both outcomes: we don't expect `revealAfter` @@ -2032,8 +2066,9 @@ function finishPendingCacheNode( rsc.resolve(dynamicSegmentData, debugInfo) } } else { - // This is not a deferred RSC promise, nor is it empty, so it must have - // been populated by a different navigation. We must not overwrite it. + // This is not a deferred RSC promise that's still pending, nor is it + // empty, so it must have been populated by a different navigation. We + // must not overwrite it (nor its dependency source). } // Check if this is a leaf segment. If so, it will have a `head` property with diff --git a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts index d1832d1efdd5..5a1da1fd46a3 100644 --- a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts +++ b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts @@ -87,9 +87,9 @@ export function createInitialRouterState({ // render from the root. null, // The initial payload may still be streaming in while we hydrate, so its - // vary params can't be drained here — and nothing consumes them from - // this tree. The segment-cache write below re-decodes the transport data - // with the payload's root params once the stale time has resolved. + // vary params can't be drained here; they decode as null. The + // segment-cache write below re-decodes the transport data with the + // payload's root params once the stale time has resolved. null, // Same for partiality: only segment-cache writes consume it, and the // write below re-decodes with the payload's actual response-level value. diff --git a/packages/next/src/client/components/segment-cache/bfcache.ts b/packages/next/src/client/components/segment-cache/bfcache.ts index ed2bd72a539c..11e20651528a 100644 --- a/packages/next/src/client/components/segment-cache/bfcache.ts +++ b/packages/next/src/client/components/segment-cache/bfcache.ts @@ -1,5 +1,6 @@ import { DYNAMIC_STALETIME_MS } from '../router-reducer/reducers/navigate-reducer' import type { CacheNode } from '../../../shared/lib/app-router-types' +import type { VaryParams } from '../../../shared/lib/segment-cache/vary-params-decoding' import type { VaryPath } from './vary-path' /** @@ -36,6 +37,11 @@ export type BFCacheEntry = { head: React.ReactNode | null prefetchHead: React.ReactNode | null + // The source of the params `rsc` depends on, copied from the CacheNode that + // wrote this entry (see CacheNode.varyParams). A restored node reads it to + // decide whether a later navigation can keep its data. + varyParams: VaryParams | null + // The bfcacheId of the CacheNode that wrote this entry. Restored on // history-traversal navigations so that `useRouter().bfcacheId` is stable // across back/forward, even without `cacheComponents` Activity preservation. @@ -79,6 +85,7 @@ export function writeToBFCache( varyPath, cacheNode.rsc, cacheNode.prefetchRsc, + cacheNode.varyParams, cacheNode.head, cacheNode.prefetchHead, dynamicStaleAt, @@ -91,6 +98,7 @@ function writeEntryToBFCache( varyPath: VaryPath, rsc: React.ReactNode, prefetchRsc: React.ReactNode, + varyParams: VaryParams | null, head: React.ReactNode, prefetchHead: React.ReactNode, dynamicStaleAt: number, @@ -109,6 +117,8 @@ function writeEntryToBFCache( head, prefetchHead, + varyParams, + bfcacheId, ref: null, @@ -139,7 +149,9 @@ export function writeHeadToBFCache( dynamicStaleAt: number ): void { // Write the special "segment" that represents the head data. The page - // node's head fields take the place of the entry's segment fields. + // node's head fields take the place of the entry's segment fields. The + // head's dependency source isn't tracked on the node, so the entry has + // none. writeEntryToBFCache( now, varyPath, @@ -147,19 +159,33 @@ export function writeHeadToBFCache( cacheNode.prefetchHead, null, null, + null, dynamicStaleAt, cacheNode.bfcacheId ) } /** - * Update the staleAt of an existing BFCache entry. Used after a dynamic - * response arrives with a per-page stale time from `unstable_dynamicStaleTime`. - * The per-page value is authoritative — it overrides whatever staleAt was set - * by the default DYNAMIC_STALETIME_MS. + * Patches the entry written for a segment before its dynamic response + * arrived, with what the response filled in on the segment's CacheNode: the + * per-page stale time from `unstable_dynamicStaleTime` (authoritative over + * the default DYNAMIC_STALETIME_MS the entry was written with) and the + * source of the params its data depends on. The entry shares the node's + * deferred `rsc` promise, which the response resolves in place. Only the entry + * that shares the node's `rsc` is updated; a refresh may have replaced the + * entry at the same vary path, and that entry belongs to the newer node. + * + * TODO: This function exists because the entry gets `rsc` when it is written + * but the stale time and vary params only later, through a second write that + * has to find the entry again. The response should fill in all three as one + * unit: make the pending CacheNode itself the thenable (like DeferredRsc, but + * for the whole node) with an explicit pending → fulfilled/rejected + * transition, so the entry holds the node and observes its resolution + * directly, with nothing to look up or patch afterwards. */ -export function updateBFCacheEntryStaleAt( +export function updateBFCacheEntryFromDynamicResponse( varyPath: VaryPath, + cacheNode: CacheNode, newStaleAt: number ): void { if (typeof window === 'undefined') { @@ -175,8 +201,9 @@ export function updateBFCacheEntryStaleAt( isRevalidation, false ) - if (entry !== null) { + if (entry !== null && entry.rsc === cacheNode.rsc) { entry.staleAt = newStaleAt + entry.varyParams = cacheNode.varyParams } } diff --git a/packages/next/src/client/components/segment-cache/cache.ts b/packages/next/src/client/components/segment-cache/cache.ts index bc05e66e8449..e296657b9708 100644 --- a/packages/next/src/client/components/segment-cache/cache.ts +++ b/packages/next/src/client/components/segment-cache/cache.ts @@ -5,6 +5,8 @@ import { StaticAttemptHints, } from '../../../shared/lib/app-router-types' import { + createVaryParams, + readVaryParams, SEARCH_PARAMS_VARY_ID, type VaryParams, } from '../../../shared/lib/segment-cache/vary-params-decoding' @@ -185,10 +187,9 @@ export type RSCSegmentData = { */ isPartial: boolean /** - * The params this segment's output depends on (root params already - * unioned in), drained from the response's wire iterables at decode. Null - * means unknown — tracking wasn't enabled, or the decode had no root - * params to union in — so consumers key on all params. + * The source of the params this segment's output depends on (root params + * included). Null means unknown — tracking wasn't enabled, or the decode + * had no root params to union in — so consumers key on all params. */ varyParams: VaryParams | null /** @@ -361,6 +362,12 @@ export type FulfilledSegmentCacheEntry = SegmentCacheEntryShared & { blockedTasks: null rsc: React.ReactNode | null isPartial: boolean + // The source of the params `rsc` depends on, recorded under exactly the + // condition the entry's key trusts it (see the re-key derivation in + // writeSegmentDataIntoCache). Null means unknown: consumers assume the + // output depends on every param. A navigation that renders this entry's + // `rsc` as its final data carries it onto the CacheNode. + varyParams: VaryParams | null promise: null } @@ -1314,6 +1321,7 @@ export function attemptToFulfillDynamicSegmentFromBFCache( bfcacheEntry.rsc, dynamicPrefetchStaleAt, isPartial, + bfcacheEntry.varyParams, // bfcache data is concrete, never an ISR fallback. false, FetchStrategy.Full @@ -1352,6 +1360,7 @@ export function attemptToUpgradeSegmentFromBFCache( bfcacheEntry.rsc, dynamicPrefetchStaleAt, isPartial, + bfcacheEntry.varyParams, // bfcache data is concrete, never an ISR fallback. false, FetchStrategy.Full @@ -1548,6 +1557,7 @@ function fulfillSegmentCacheEntry( rsc: React.ReactNode, staleAt: number, isPartial: boolean, + varyParams: VaryParams | null, // Only static (per-segment PPR) responses can be ISR fallbacks; all other // callers pass false. Always assigned (even when false) so that re-fulfilling // a previously-fallback entry with a concrete response clears the flag and @@ -1570,6 +1580,7 @@ function fulfillSegmentCacheEntry( fulfilledEntry.rsc = rsc fulfilledEntry.staleAt = staleAt fulfilledEntry.isPartial = isPartial + fulfilledEntry.varyParams = varyParams fulfilledEntry.isUpgradeableISRFallback = isUpgradeableISRFallback fulfilledEntry.fetchStrategy = fetchStrategy // Resolve any listeners that were waiting for this data. @@ -3430,46 +3441,59 @@ function writeSegmentDataIntoCache( // vary path). const payloadStrategy = contentFetchStrategy ?? fetchStrategy let fulfilledVaryPath: VaryPath | null = null + // The dependency source the entry records, so a navigation that renders + // its content can tell which params that content read. It is the same + // evidence the key derivation below trusts, under the same condition, with + // the same correction; otherwise null, and consumers assume every param. + let recordedVaryParams: VaryParams | null = null if ( process.env.__NEXT_VARY_PARAMS && payloadStrategy !== FetchStrategy.Full && segmentVaryParams !== null ) { - let varyParams = segmentVaryParams - if ( - payloadStrategy === FetchStrategy.RuntimeShell && - varyParams.has(SEARCH_PARAMS_VARY_ID) - ) { - // SPECIAL CASE: for a RuntimeShell payload, the search params entry - // is dropped from the server's vary evidence before deriving the - // key, so the search component of the resulting path is marked as - // the fallback. This exists ONLY because of a known compromise in - // how the server reports search params: accessing `searchParams` - // records a dependency on them at access time, even when the render - // suspends on that access and cuts the content at the param - // fallback. A shell render's page and head segments therefore report - // the search params while the emitted bytes contain no - // search-dependent content. - // Trusting that report would key shell-grade content at a concrete - // search value, where shell-restricted reads (which generalize every - // non-root param — see getShellSegmentVaryPath) can never find it. A - // RuntimeShell payload's search-dependent content is reduced to - // fallbacks by construction, so its key must not vary on search - // regardless of the over-reported evidence. Every other component of - // the evidence is still honored as-is. - // - // Nothing else should rely on this branch; for every other payload - // grade — and every other param — the server's evidence - // is authoritative. - // - // TODO: Reconsider special-casing this on the server instead: don't - // report a param access that never resolved past the fallback cut in - // the emitted stage. A shell payload's evidence would then be - // accurate, and this branch could be deleted. - varyParams = new Set(varyParams) - varyParams.delete(SEARCH_PARAMS_VARY_ID) + // Read the reported set now, when the key is chosen. The payload is fully + // buffered by the time it's written, so the source has settled; a read of + // null means the report is unavailable and every param varies. + let varyParams = readVaryParams(segmentVaryParams) + if (varyParams !== null) { + if ( + payloadStrategy === FetchStrategy.RuntimeShell && + varyParams.has(SEARCH_PARAMS_VARY_ID) + ) { + // SPECIAL CASE: for a RuntimeShell payload, the search params entry + // is dropped from the server's vary evidence before deriving the + // key, so the search component of the resulting path is marked as + // the fallback. This exists ONLY because of a known compromise in + // how the server reports search params: accessing `searchParams` + // records a dependency on them at access time, even when the render + // suspends on that access and cuts the content at the param + // fallback. A shell render's page and head segments therefore report + // the search params while the emitted bytes contain no + // search-dependent content. + // Trusting that report would key shell-grade content at a concrete + // search value, where shell-restricted reads (which generalize every + // non-root param — see getShellSegmentVaryPath) can never find it. A + // RuntimeShell payload's search-dependent content is reduced to + // fallbacks by construction, so its key must not vary on search + // regardless of the over-reported evidence. Every other component of + // the evidence is still honored as-is. + // + // Nothing else should rely on this branch; for every other payload + // grade — and every other param — the server's evidence + // is authoritative. + // + // TODO: Reconsider special-casing this on the server instead: don't + // report a param access that never resolved past the fallback cut in + // the emitted stage. A shell payload's evidence would then be + // accurate, and this branch could be deleted. + varyParams = new Set(varyParams) + varyParams.delete(SEARCH_PARAMS_VARY_ID) + recordedVaryParams = createVaryParams(varyParams) + } else { + recordedVaryParams = segmentVaryParams + } + fulfilledVaryPath = getFulfilledSegmentVaryPath(tree.varyPath, varyParams) } - fulfilledVaryPath = getFulfilledSegmentVaryPath(tree.varyPath, varyParams) } // The canonical path to (re-)key the entry at. When the derivation above @@ -3508,6 +3532,7 @@ function writeSegmentDataIntoCache( rsc, staleAt, isPartial, + recordedVaryParams, isUpgradeableISRFallback, recordedFetchStrategy ) @@ -3527,6 +3552,7 @@ function writeSegmentDataIntoCache( rsc, staleAt, isPartial, + recordedVaryParams, isUpgradeableISRFallback, recordedFetchStrategy ) diff --git a/packages/next/src/client/components/segment-cache/decode-server-response.ts b/packages/next/src/client/components/segment-cache/decode-server-response.ts index 6183500f6172..3fa7a383eadc 100644 --- a/packages/next/src/client/components/segment-cache/decode-server-response.ts +++ b/packages/next/src/client/components/segment-cache/decode-server-response.ts @@ -25,7 +25,7 @@ import type { VaryParams, VaryParamsIterable, } from '../../../shared/lib/segment-cache/vary-params-decoding' -import { readVaryParams } from '../../../shared/lib/segment-cache/vary-params-decoding' +import { decodeVaryParams } from '../../../shared/lib/segment-cache/vary-params-decoding' import { type SegmentRequestKey, ROOT_SEGMENT_REQUEST_KEY, @@ -70,10 +70,9 @@ export type NavigationSeed = { head: HeadData | null isHeadPartial: boolean /** - * The params the head's output depends on (root params already unioned - * in), drained from the response's wire iterables at decode. Null means - * unknown — tracking wasn't enabled, or the decode had no root params to - * union in — so consumers key on all params. + * The source of the params the head's output depends on (root params + * included). Null means unknown — tracking wasn't enabled, or the decode + * had no root params to union in — so consumers key on all params. */ headVaryParams: VaryParams | null /** @@ -118,13 +117,11 @@ export function createNavigationSeed( // The response's root vary params (its `r` field): the root params // accessed anywhere in the response, emitted once at the response level // and unioned into the head's and every segment's own drained set here at - // the decode boundary. Pass null when vary params are unavailable or - // unwanted: navigation and reducer flows, whose responses stream in - // incrementally (the wire iterables can only be drained completely from a - // fully-buffered response) and whose seeds' vary params nothing consumes — - // only segment-cache writes read them, and those decode their own, - // buffered, payloads. Null decodes every set as null ("unknown; key on - // all params") without touching the wire iterables. + // the decode boundary. Pass null when the response streams in + // incrementally (navigation and reducer flows): the wire iterables can + // only be drained completely from a fully-buffered response, so their sets + // decode as null ("unknown; key on all params") without touching the wire + // iterables. rootVaryParams: VaryParamsIterable | null, // Whether anything in the response is not fully resolved: dynamic holes, runtime holes, anything suspended. // Boolean-form nodes resolve their partiality to this value (their wire @@ -192,7 +189,7 @@ export function createNavigationSeed( ? isResponsePartial : transportHead.p : readFulfilledIsPartial(transportHead.p) - headVaryParams = readVaryParams(transportHead.v, rootVaryParams) + headVaryParams = decodeVaryParams(transportHead.v, rootVaryParams) headStaleTimeSeconds = transportHead.s !== undefined ? readFulfilledStaleTimeSeconds(transportHead.s) @@ -636,11 +633,12 @@ function decodeTransportNode( typeof nodeData.p === 'boolean' ? isResponsePartial : readFulfilledIsPartial(nodeData.p), - // Drain the segment's wire iterable into a plain set, unioning in the - // response-level root params. Same buffered-read reasoning as `p` - // above; skipped entirely (decoded as null, "unknown") when the caller - // passed no root params — see createNavigationSeed. - varyParams: readVaryParams(nodeData.v, rootVaryParams), + // The source of the params this segment's output depends on: the + // segment's wire iterable, drained here, unioning in the response-level + // root params (same buffered-read reasoning as `p` above), or decoded + // as null ("unknown") when the caller passed no root params — see + // createNavigationSeed. + varyParams: decodeVaryParams(nodeData.v, rootVaryParams), // Per-node staleTime, only present in per-segment prefetch responses // (same buffered-read reasoning as `p` above). staleTimeSeconds: diff --git a/packages/next/src/shared/lib/app-router-types.ts b/packages/next/src/shared/lib/app-router-types.ts index a379db272b9e..19aebfdae9c5 100644 --- a/packages/next/src/shared/lib/app-router-types.ts +++ b/packages/next/src/shared/lib/app-router-types.ts @@ -11,7 +11,10 @@ export type LoadingModuleData = | [React.JSX.Element, React.ReactNode, React.ReactNode] | null -import type { VaryParamsIterable } from './segment-cache/vary-params-decoding' +import type { + VaryParams, + VaryParamsIterable, +} from './segment-cache/vary-params-decoding' import type { FullTransportData, PartialTransportData } from './rsc-transport' /** viewport metadata node */ @@ -45,6 +48,15 @@ export type CacheNode = { */ prefetchRsc: React.ReactNode + /** + * The source of the params `rsc` depends on, from the response that + * produced it. Null when unknown: the data came from the segment cache, or + * from a render that didn't track params, or `rsc` is still pending — it + * is set alongside `rsc` when the response arrives. A navigation that only + * changes params this output did not depend on can keep rendering it. + */ + varyParams: VaryParams | null + prefetchHead: HeadData | null head: HeadData diff --git a/packages/next/src/shared/lib/segment-cache/vary-params-decoding.ts b/packages/next/src/shared/lib/segment-cache/vary-params-decoding.ts index 84db2d9430d1..24f9ed67fc5f 100644 --- a/packages/next/src/shared/lib/segment-cache/vary-params-decoding.ts +++ b/packages/next/src/shared/lib/segment-cache/vary-params-decoding.ts @@ -20,7 +20,16 @@ export const SEARCH_PARAMS_VARY_ID = 0 // Path param names, and SEARCH_PARAMS_VARY_ID for the search params. export type VaryParamId = string | number -export type VaryParams = Set +/** + * The params a piece of rendered output depends on — the ids of the vary path + * nodes it read: path param names, and SEARCH_PARAMS_VARY_ID for the search + * params — as the source that reports them rather than a snapshot of it. + * + * The wire iterables can only be drained from a fully-buffered response; they + * are drained once at decode into an already-settled thenable, and read at the + * point a decision needs the set (readVaryParams). + */ +export type VaryParams = PromiseLike> /** * Vary params are serialized into the Flight stream as an @@ -33,9 +42,9 @@ export type VaryParams = Set * * Root params are NOT included in a segment's own iterable. They're emitted * once at the top level of the response (as a separate iterable) and unioned in - * by `readVaryParams`, because root params can be accessed at any point during - * the render — folding them into every segment would otherwise require a merge - * once the whole render is complete. + * by `decodeVaryParams`, because root params can be accessed at any point + * during the render — folding them into every segment would otherwise require + * a merge once the whole render is complete. */ export type VaryParamsIterable = AsyncIterable @@ -59,7 +68,7 @@ export type VaryParamsIterable = AsyncIterable */ function drainVaryParams( iterable: VaryParamsIterable, - target: VaryParams + target: Set ): void { const iterator = iterable[Symbol.asyncIterator]() while (true) { @@ -74,13 +83,13 @@ function drainVaryParams( } /** - * Reads a segment's (or the head's) vary params, unioning in the response-level - * root params. + * Converts a segment's (or the head's) vary params off the wire, at the + * decode boundary, unioning in the response-level root params. * * Root params are emitted once at the top level rather than folded into every - * segment by the server, so every read recombines them here — building the - * merge into the read means a caller can't forget it, and it's done in a single - * pass with no intermediate set. + * segment by the server, so every decode recombines them here — building the + * merge into the decode means a caller can't forget it, and it's done in a + * single pass with no intermediate set. * * Returns null ("unknown", key on all params) unless BOTH iterables are * present. A null/absent `iterable` means the segment's own tracking wasn't @@ -94,7 +103,7 @@ function drainVaryParams( * set — a tracked segment that read no params, with no root params accessed, * can be shared across all param values. */ -export function readVaryParams( +export function decodeVaryParams( iterable: VaryParamsIterable | null | undefined, rootIterable: VaryParamsIterable | null | undefined ): VaryParams | null { @@ -106,8 +115,34 @@ export function readVaryParams( ) { return null } - const varyParams: VaryParams = new Set() - drainVaryParams(iterable, varyParams) - drainVaryParams(rootIterable, varyParams) - return varyParams + const total: Set = new Set() + drainVaryParams(iterable, total) + drainVaryParams(rootIterable, total) + return createVaryParams(total) +} + +/** + * Wraps an already-known set as a vary params source. Shaped like a settled + * Flight promise so readVaryParams can read it off the thenable's status. + */ +export function createVaryParams(total: Set): VaryParams { + // TODO: Don't need to use a native promise. Just inline a thenable that + // immediately calls its listener. + const settled = Promise.resolve(total) as Promise> & { + status: 'fulfilled' + value: Set + } + settled.status = 'fulfilled' + settled.value = total + return settled +} + +/** + * Reads the set from a vary params source. Null when it is not available; + * the reader assumes every param varies. + */ +export function readVaryParams( + varyParams: VaryParams +): Set | null { + return readFulfilledValue(varyParams, null) } From aa8e252fbd5c317fa853e0a938a3b4492058a146 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 25 Sep 2026 00:25:28 -0400 Subject: [PATCH 09/13] Store the head as its own CacheNode (#98973) Throughout most of the client router implementation, the head is treated as if it belongs to the page segment. For example, CacheNode has separate `rsc` and `headRsc` fields, and the page's CacheNode owns both. This had led to a bunch of special cases related to the head. The Segment Cache largely (though not entirely) avoids this by instead modeling the head as a special kind of segment. It has its own vary path, its own SegmentCacheEntry, etc. There are still a few places where treatment of the head is special, because it doesn't exist inside the normal route tree structure. But we treat it like segment wherever possible. This PR updates the navigation implementation to use the same model: treat the head as a segment that is a sibling to the route segment tree. This is a large diff but the changes are almost entirely mechanical. Although the added/removed balance is about even, that includes a new test case. The net number of implementation lines has gone down. --- .../client/components/app-router-instance.ts | 2 +- .../src/client/components/app-router-state.ts | 152 +++-- .../next/src/client/components/app-router.tsx | 62 +- packages/next/src/client/components/links.ts | 12 +- .../next/src/client/components/prefetch.ts | 9 +- .../next/src/client/components/render-tree.ts | 567 +++++++++--------- .../create-initial-router-state.ts | 57 +- .../create-segment-key.browser.ts | 21 + .../router-reducer/create-segment-key.ts | 11 + .../reducers/find-head-in-cache.ts | 70 --- .../reducers/navigate-reducer.ts | 2 +- .../reducers/refresh-reducer.ts | 7 +- .../reducers/restore-reducer.ts | 16 +- .../reducers/server-action-reducer.ts | 36 +- .../reducers/server-patch-reducer.ts | 2 +- .../router-reducer/router-reducer-types.ts | 13 +- .../components/segment-cache/bfcache.ts | 62 +- .../client/components/segment-cache/cache.ts | 266 ++++---- .../segment-cache/decode-server-response.ts | 123 ++-- .../segment-cache/optimistic-routes.ts | 90 +-- .../components/segment-cache/scheduler.ts | 65 +- .../components/segment-cache/vary-path.ts | 46 +- .../next/src/shared/lib/app-router-types.ts | 4 - .../[locale]/[tenant]/page.tsx | 30 + .../segment-cache/metadata/app/page.tsx | 16 + .../metadata/segment-cache-metadata.test.ts | 82 +++ 26 files changed, 924 insertions(+), 899 deletions(-) delete mode 100644 packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts create mode 100644 test/e2e/app-dir/segment-cache/metadata/app/page-with-per-tenant-head/[locale]/[tenant]/page.tsx diff --git a/packages/next/src/client/components/app-router-instance.ts b/packages/next/src/client/components/app-router-instance.ts index ae8c5e3f6e72..2ea26a3f6b55 100644 --- a/packages/next/src/client/components/app-router-instance.ts +++ b/packages/next/src/client/components/app-router-instance.ts @@ -299,7 +299,7 @@ function gesturePush(href: string, options?: NavigateOptions): void { url, currentUrl, state.renderedSearch, - state.cache, + state.root, state.tree, state.nextUrl, freshnessPolicy, diff --git a/packages/next/src/client/components/app-router-state.ts b/packages/next/src/client/components/app-router-state.ts index ea4471e8fb89..28f4958cb5fd 100644 --- a/packages/next/src/client/components/app-router-state.ts +++ b/packages/next/src/client/components/app-router-state.ts @@ -1,4 +1,4 @@ -import type { RouteTree } from './segment-cache/cache' +import type { RootRouteTree } from './segment-cache/cache' import type { FlightRouterState, ScrollRef, @@ -13,6 +13,7 @@ import { beginLockedNavigation, type NavigationLock, type NavigationRequestAccumulation, + type RootNavigationTask, } from './render-tree' import { createHrefFromUrl } from './router-reducer/create-href-from-url' import { @@ -24,6 +25,7 @@ import { spawnStaticStageCacheWrite, writeRuntimePrefetchStreamIntoCache, type FulfilledRouteCacheEntry, + createRootRouteTree, } from './segment-cache/cache' import { discoverKnownRoute } from './segment-cache/optimistic-routes' import { @@ -61,7 +63,7 @@ export function navigate( url: URL, currentUrl: URL, currentRenderedSearch: string, - currentRenderTree: RouteTree, + currentRoot: RootRouteTree, currentFlightRouterState: FlightRouterState, nextUrl: string | null, freshnessPolicy: FreshnessPolicy, @@ -90,7 +92,7 @@ export function navigate( url, currentUrl, currentRenderedSearch, - currentRenderTree, + currentRoot, currentFlightRouterState, nextUrl, freshnessPolicy, @@ -106,7 +108,7 @@ export function navigate( url, currentUrl, currentRenderedSearch, - currentRenderTree, + currentRoot, currentFlightRouterState, nextUrl, freshnessPolicy, @@ -123,7 +125,7 @@ function navigateImpl( url: URL, currentUrl: URL, currentRenderedSearch: string, - currentRenderTree: RouteTree, + currentRoot: RootRouteTree, currentFlightRouterState: FlightRouterState, nextUrl: string | null, freshnessPolicy: FreshnessPolicy, @@ -148,7 +150,7 @@ function navigateImpl( currentUrl, currentRenderedSearch, nextUrl, - currentRenderTree, + currentRoot, freshnessPolicy, scrollBehavior, navigateType, @@ -185,7 +187,7 @@ function navigateImpl( currentUrl, currentRenderedSearch, nextUrl, - currentRenderTree, + currentRoot, freshnessPolicy, scrollBehavior, navigateType, @@ -209,7 +211,7 @@ function navigateImpl( currentUrl, currentRenderedSearch, nextUrl, - currentRenderTree, + currentRoot, currentFlightRouterState, freshnessPolicy, scrollBehavior, @@ -230,7 +232,7 @@ export function navigateToKnownRoute( navigationSeed: NavigationSeed, currentUrl: URL, currentRenderedSearch: string, - currentRenderTree: RouteTree, + currentRoot: RootRouteTree, freshnessPolicy: FreshnessPolicy, nextUrl: string | null, scrollBehavior: ScrollBehavior, @@ -277,7 +279,7 @@ export function navigateToKnownRoute( if ( link !== null && link.fetchStrategy === FetchStrategy.Full && - (navigationSeed.routeTree.prefetchHints & + (navigationSeed.root.tree.prefetchHints & (PrefetchHint.SubtreeHasPartialPrefetching | PrefetchHint.SubtreeHasInstantFalse)) === 0 @@ -311,7 +313,7 @@ export function navigateToKnownRoute( require('./segment-cache/navigation-testing-lock') as typeof import('./segment-cache/navigation-testing-lock') const link = getLinkForCurrentNavigation() restrictToShell = shouldRestrictNavigationToShell( - navigationSeed.routeTree.prefetchHints, + navigationSeed.root.tree.prefetchHints, link !== null ? link.fetchStrategy : FetchStrategy.PPR ) } @@ -339,25 +341,23 @@ export function navigateToKnownRoute( // data. If the page segment is fully static and prefetched, the request is // skipped. (This is also how refresh() works.) const isSamePageNavigation = url.href === currentUrl.href - const task = startPPRNavigation( + const navigation = startPPRNavigation( now, currentUrl, currentRenderedSearch, - currentRenderTree, - navigationSeed.routeTree, - navigationSeed.metadataVaryPath, + currentRoot, + navigationSeed.root, freshnessPolicy, - navigationSeed.head, navigationSeed.dynamicStaleAt, isSamePageNavigation, accumulation, map, restrictToShell ) - if (task !== null) { + if (navigation !== null) { if (freshnessPolicy !== FreshnessPolicy.Gesture) { spawnDynamicRequests( - task, + navigation, url, nextUrl, freshnessPolicy, @@ -373,8 +373,7 @@ export function navigateToKnownRoute( state, url, nextUrl, - task.route, - task.node, + navigation, navigationSeed.renderedSearch, canonicalUrl, navigateType, @@ -394,7 +393,7 @@ function navigateUsingPrefetchedRouteTree( currentUrl: URL, currentRenderedSearch: string, nextUrl: string | null, - currentRenderTree: RouteTree, + currentRoot: RootRouteTree, freshnessPolicy: FreshnessPolicy, scrollBehavior: ScrollBehavior, navigateType: 'push' | 'replace', @@ -402,17 +401,11 @@ function navigateUsingPrefetchedRouteTree( navigationLock: NavigationLock | null, map: CacheMap ): AppRouterState { - const routeTree = route.tree const canonicalUrl = route.canonicalUrl + url.hash const renderedSearch = route.renderedSearch const prefetchSeed: NavigationSeed = { renderedSearch, - routeTree, - metadataVaryPath: route.metadata.varyPath as any, - head: null, - isHeadPartial: true, - headVaryParams: null, - headStaleTimeSeconds: null, + root: route.root, dynamicStaleAt: computeDynamicStaleAt(now, UnknownDynamicStaleTime), // Not derived from a server response; no base to diverge from. treeDivergedFromBase: false, @@ -425,7 +418,7 @@ function navigateUsingPrefetchedRouteTree( prefetchSeed, currentUrl, currentRenderedSearch, - currentRenderTree, + currentRoot, freshnessPolicy, nextUrl, scrollBehavior, @@ -458,7 +451,7 @@ async function navigateToUnknownRoute( currentUrl: URL, currentRenderedSearch: string, nextUrl: string | null, - currentRenderTree: RouteTree, + currentRoot: RootRouteTree, currentFlightRouterState: FlightRouterState, freshnessPolicy: FreshnessPolicy, scrollBehavior: ScrollBehavior, @@ -538,6 +531,7 @@ async function navigateToUnknownRoute( // there's no pathname to parse them from (nor a need to). null, renderedSearch, + null, dynamicStaleTime ) @@ -546,48 +540,45 @@ async function navigateToUnknownRoute( // unknown route - any rewrite detection happens during the traversal inside // discoverKnownRoute. The hasDynamicRewrite param is only set to true when // retrying after a tree mismatch (see dispatchRetryDueToTreeMismatch). - const metadataVaryPath = navigationSeed.metadataVaryPath - if (metadataVaryPath !== null) { - discoverKnownRoute( + discoverKnownRoute( + now, + url.pathname, + url.search as NormalizedSearch, + nextUrl, + null, // No pending entry + navigationSeed.root, + couldBeIntercepted, + // Store a hashless canonical URL: the entry is shared across hashes, and + // a later same-route hash nav appends `url.hash` to it. + createHrefFromUrl(canonicalUrl, false), + navigationSeed.renderedSearch, + supportsPerSegmentPrefetching, + false // hasDynamicRewrite - not a retry, rewrite detection happens during traversal + ) + + if (staticStageResponse !== null) { + spawnStaticStageCacheWrite( now, - url.pathname, - url.search as NormalizedSearch, - nextUrl, - null, // No pending entry - navigationSeed.routeTree, - metadataVaryPath, - couldBeIntercepted, - // Store a hashless canonical URL: the entry is shared across hashes, and - // a later same-route hash nav appends `url.hash` to it. - createHrefFromUrl(canonicalUrl, false), - supportsPerSegmentPrefetching, - false // hasDynamicRewrite - not a retry, rewrite detection happens during traversal + staticStageResponse, + isResponsePartial, + responseHeaders, + currentFlightRouterState, + renderedSearch, + map ) + } - if (staticStageResponse !== null) { - spawnStaticStageCacheWrite( - now, - staticStageResponse, - isResponsePartial, - responseHeaders, - currentFlightRouterState, - renderedSearch, - map - ) - } - - if (runtimePrefetchStream !== null) { - writeRuntimePrefetchStreamIntoCache( - now, - runtimePrefetchStream, - currentFlightRouterState, - renderedSearch, - map - ).catch(() => { - // The runtime prefetch cache write failed. Not fatal — the - // navigation completed normally, we just won't cache runtime data. - }) - } + if (runtimePrefetchStream !== null) { + writeRuntimePrefetchStreamIntoCache( + now, + runtimePrefetchStream, + currentFlightRouterState, + renderedSearch, + map + ).catch(() => { + // The runtime prefetch cache write failed. Not fatal — the + // navigation completed normally, we just won't cache runtime data. + }) } // In the streaming dev render, this single response's seed content may still @@ -613,7 +604,7 @@ async function navigateToUnknownRoute( navigationSeed, currentUrl, currentRenderedSearch, - currentRenderTree, + currentRoot, freshnessPolicy, nextUrl, scrollBehavior, @@ -657,7 +648,7 @@ export function completeHardNavigation( // router updates without updating React. renderedSearch: state.renderedSearch, scrollRef: state.scrollRef, - cache: state.cache, + root: state.root, tree: state.tree, nextUrl: state.nextUrl, previousNextUrl: state.previousNextUrl, @@ -670,8 +661,7 @@ export function completeSoftNavigation( oldState: AppRouterState, url: URL, referringNextUrl: string | null, - tree: FlightRouterState, - cache: RouteTree, + navigation: RootNavigationTask, renderedSearch: string, canonicalUrl: string, navigateType: 'push' | 'replace', @@ -685,6 +675,7 @@ export function completeSoftNavigation( // same traversal that computes the tree itself. We should also figure out // what is the minimum information needed for the server to correctly // intercept the route. + const tree = navigation.tree.route const changedPath = computeChangedPath(oldState.tree, tree) const nextUrlForNewRoute = changedPath ? changedPath : oldState.nextUrl @@ -795,7 +786,7 @@ export function completeSoftNavigation( ? decodeURIComponent(url.hash.slice(1)) : oldState.scrollRef.hashFragment, }, - cache, + root: createRootRouteTree(navigation.tree.node, navigation.head.node), tree, nextUrl: nextUrlForNewRoute, previousNextUrl, @@ -808,8 +799,7 @@ export function completeTraverseNavigation( state: AppRouterState, url: URL, renderedSearch: string, - cache: RouteTree, - tree: FlightRouterState, + navigation: RootNavigationTask, nextUrl: string | null ) { return { @@ -823,9 +813,9 @@ export function completeTraverseNavigation( preserveCustomHistoryState: true, }, scrollRef: state.scrollRef, - cache, + root: createRootRouteTree(navigation.tree.node, navigation.head.node), // Restore provided tree - tree, + tree: navigation.tree.route, nextUrl, // TODO: We need to restore previousNextUrl, too, which represents the // Next-Url that was used to fetch the data. Anywhere we fetch using the @@ -848,7 +838,7 @@ async function ensurePrefetchThenNavigate( url: URL, currentUrl: URL, currentRenderedSearch: string, - currentRenderTree: RouteTree, + currentRoot: RootRouteTree, currentFlightRouterState: FlightRouterState, nextUrl: string | null, freshnessPolicy: FreshnessPolicy, @@ -871,7 +861,7 @@ async function ensurePrefetchThenNavigate( const navigationLockPrefetch = beginNavigationLockPrefetch() const prefetchTask = schedulePrefetchTask( cacheKey, - currentRenderTree, + currentRoot, fetchStrategy, PrefetchPriority.Default, null, // onInvalidate @@ -891,7 +881,7 @@ async function ensurePrefetchThenNavigate( url, currentUrl, currentRenderedSearch, - currentRenderTree, + currentRoot, currentFlightRouterState, nextUrl, freshnessPolicy, diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index 3349ec36cb36..312dfa6af582 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -16,6 +16,7 @@ import type { AppRouterState, } from './router-reducer/router-reducer-types' import { createHrefFromUrl } from './router-reducer/create-href-from-url' +import { createHeadKey } from './router-reducer/create-segment-key' import { SearchParamsContext, PathnameContext, @@ -27,7 +28,6 @@ import { useActionQueue } from './use-action-queue' import { setLastCommittedTree } from './router-reducer/reducers/committed-state' import { AppRouterAnnouncer } from './app-router-announcer' import { RedirectBoundary } from './redirect-boundary' -import { findHeadInCache } from './router-reducer/reducers/find-head-in-cache' import { unresolvedThenable } from './unresolved-thenable' import { removeBasePath } from '../remove-base-path' import { hasBasePath } from '../has-base-path' @@ -160,8 +160,8 @@ function HistoryUpdater({ // task. Re-prefetch all visible links with the updated values. In most // cases, this will not result in any new network requests, only if // the prefetch result actually varies on one of these inputs. - pingVisibleLinks(appRouterState.nextUrl, appRouterState.cache) - }, [appRouterState.nextUrl, appRouterState.cache]) + pingVisibleLinks(appRouterState.nextUrl, appRouterState.root) + }, [appRouterState.nextUrl, appRouterState.root]) return null } @@ -185,14 +185,13 @@ function copyNextJsInternalHistoryState(data: any) { function Head({ headRenderTree, }: { - headRenderTree: RouteTree | null + headRenderTree: RouteTree }): React.ReactNode { - // If this segment has a `prefetchHead`, it's the statically prefetched data. - // We should use that on initial render instead of `head`. Then we'll switch - // to `head` when the dynamic response streams in. - const head = headRenderTree !== null ? headRenderTree.data.head : null - const prefetchHead = - headRenderTree !== null ? headRenderTree.data.prefetchHead : null + // If the head has a `prefetchRsc`, it's the statically prefetched data. We + // should use that on initial render instead of `rsc`. Then we'll switch to + // `rsc` when the dynamic response streams in. + const head = headRenderTree.data.rsc + const prefetchHead = headRenderTree.data.prefetchRsc // If no prefetch data is available, then we go straight to rendering `head`. const resolvedPrefetchRsc = prefetchHead !== null ? prefetchHead : head @@ -236,7 +235,7 @@ function Router({ }, [canonicalUrl]) if (process.env.NODE_ENV !== 'production') { - const { cache, tree } = state + const { root, tree } = state // This hook is in a conditional but that is ok because `process.env.NODE_ENV` never changes // eslint-disable-next-line react-hooks/rules-of-hooks @@ -246,10 +245,10 @@ function Router({ // @ts-ignore this is for debugging window.nd = { router: publicAppRouterInstance, - cache, + root, tree, } - }, [cache, tree]) + }, [root, tree]) } useEffect(() => { @@ -437,11 +436,7 @@ function Router({ } }, []) - const { cache, tree, nextUrl, scrollRef, previousNextUrl } = state - - const matchingHead = useMemo(() => { - return findHeadInCache(cache, tree[1]) - }, [cache, tree]) + const { root, tree, nextUrl, scrollRef, previousNextUrl } = state // Add memoized pathParams for useParams. const pathParams = useMemo(() => { @@ -467,7 +462,7 @@ function Router({ const layoutRouterContext = useMemo(() => { return { parentTree: tree, - parentRenderTree: cache, + parentRenderTree: root.tree, parentSegmentPath: null, parentParams: {}, parentLoadingData: null, @@ -480,7 +475,7 @@ function Router({ // Root segment is always active isActive: true, } - }, [tree, cache, canonicalUrl]) + }, [tree, root, canonicalUrl]) const globalLayoutRouterContext = useMemo(() => { return { @@ -491,20 +486,17 @@ function Router({ } }, [tree, scrollRef, nextUrl, previousNextUrl]) - let head - if (matchingHead !== null) { - // The head is wrapped in an extra component so we can use - // `useDeferredValue` to swap between the prefetched and final versions of - // the head. (This is what LayoutRouter does for segment data, too.) - // - // The `key` is used to remount the component whenever the head moves to - // a different segment. - const [headRenderTree, headKey] = matchingHead - - head = - } else { - head = null - } + // The head is wrapped in an extra component so we can use + // `useDeferredValue` to swap between the prefetched and final versions of + // the head. (This is what LayoutRouter does for segment data, too.) + // + // The `key` is used to remount the component whenever the head moves to a + // different page, one of its path param values changes (the same inputs as + // LayoutRouter's keys), or its search params change. These are the entries + // of the head's vary path (see getHeadRequestKey). + const head = ( + + ) let content = ( @@ -512,7 +504,7 @@ function Router({ {/* RootLayoutBoundary enables detection of Suspense boundaries around the root layout. When users wrap their layout in , this creates the component stack pattern "Suspense -> RootLayoutBoundary" which dynamic-rendering.ts uses to allow dynamic rendering. */} - {cache.data.rsc} + {root.tree.data.rsc} ) diff --git a/packages/next/src/client/components/links.ts b/packages/next/src/client/components/links.ts index 9d46fc8faf41..502e55309637 100644 --- a/packages/next/src/client/components/links.ts +++ b/packages/next/src/client/components/links.ts @@ -1,4 +1,4 @@ -import type { RouteTree } from './segment-cache/cache' +import type { RootRouteTree } from './segment-cache/cache' import type { CacheNode } from '../../shared/lib/app-router-types' import type { AppRouterInstance } from '../../shared/lib/app-router-context.shared-runtime' import { @@ -345,7 +345,7 @@ function rescheduleLinkPrefetch( const cacheKey = createCacheKey(instance.prefetchHref, nextUrl) instance.prefetchTask = scheduleSegmentPrefetchTask( cacheKey, - appRouterState.cache, + appRouterState.root, instance.fetchStrategy, priority, null, @@ -356,7 +356,7 @@ function rescheduleLinkPrefetch( // effectively the same as canceling the old task and creating a new one. reschedulePrefetchTask( existingPrefetchTask, - appRouterState.cache, + appRouterState.root, instance.fetchStrategy, priority ) @@ -367,7 +367,7 @@ function rescheduleLinkPrefetch( export function pingVisibleLinks( nextUrl: string | null, - cache: RouteTree + root: RootRouteTree ) { // For each currently visible link, cancel the existing prefetch task (if it // exists) and schedule a new one. This is effectively the same as if all the @@ -378,7 +378,7 @@ export function pingVisibleLinks( // cache invalidation. for (const instance of prefetchableAndVisible) { const task = instance.prefetchTask - if (task !== null && !isPrefetchTaskDirty(task, nextUrl, cache)) { + if (task !== null && !isPrefetchTaskDirty(task, nextUrl, root)) { // The cache has not been invalidated, and none of the inputs have // changed. Bail out. continue @@ -391,7 +391,7 @@ export function pingVisibleLinks( const cacheKey = createCacheKey(instance.prefetchHref, nextUrl) instance.prefetchTask = scheduleSegmentPrefetchTask( cacheKey, - cache, + root, instance.fetchStrategy, PrefetchPriority.Default, null, diff --git a/packages/next/src/client/components/prefetch.ts b/packages/next/src/client/components/prefetch.ts index 07cda2eb79cd..4b17f3632d16 100644 --- a/packages/next/src/client/components/prefetch.ts +++ b/packages/next/src/client/components/prefetch.ts @@ -1,4 +1,4 @@ -import type { RouteTree } from './segment-cache/cache' +import type { RootRouteTree } from './segment-cache/cache' import type { CacheNode } from '../../shared/lib/app-router-types' import type { PrefetchOptions } from '../../shared/lib/app-router-context.shared-runtime' import { PrefetchKind } from './router-reducer/router-reducer-types' @@ -62,7 +62,7 @@ export function prefetchRoute(href: string, options?: PrefetchOptions): void { prefetch( href, state.nextUrl, - state.cache, + state.root, fetchStrategy, options?.onInvalidate ?? null ) @@ -74,7 +74,8 @@ export function prefetchRoute(href: string, options?: PrefetchOptions): void { * or router.prefetch. It must be validated before we attempt to prefetch it. * @param nextUrl - A special header used by the server for interception routes. * Roughly corresponds to the current URL. - * @param renderTreeAtTimeOfPrefetch - The active data and its vary paths. + * @param renderTreeAtTimeOfPrefetch - The active render tree and head, and + * their vary paths. * @param fetchStrategy - Whether to prefetch dynamic data, in addition to * static data. This is used by ``. * @param onInvalidate - A callback that will be called when the prefetch cache @@ -90,7 +91,7 @@ export function prefetchRoute(href: string, options?: PrefetchOptions): void { export function prefetch( href: string, nextUrl: string | null, - renderTreeAtTimeOfPrefetch: RouteTree, + renderTreeAtTimeOfPrefetch: RootRouteTree, fetchStrategy: PrefetchTaskFetchStrategy, onInvalidate: null | (() => void) ) { diff --git a/packages/next/src/client/components/render-tree.ts b/packages/next/src/client/components/render-tree.ts index 1cda2f581f18..69ed2aa6d0c2 100644 --- a/packages/next/src/client/components/render-tree.ts +++ b/packages/next/src/client/components/render-tree.ts @@ -3,7 +3,7 @@ import type { Segment, } from '../../shared/lib/app-router-types' import type { CacheNode } from '../../shared/lib/app-router-types' -import type { HeadData, ScrollRef } from '../../shared/lib/app-router-types' +import type { ScrollRef } from '../../shared/lib/app-router-types' import { PrefetchHint } from '../../shared/lib/app-router-types' import { PAGE_SEGMENT_KEY, @@ -11,6 +11,7 @@ import { NOT_FOUND_SEGMENT_KEY, } from '../../shared/lib/segment' import { matchSegment } from './match-segments' +import { HEAD_REQUEST_KEY } from '../../shared/lib/segment-cache/segment-value-encoding' import { createHrefFromUrl } from './router-reducer/create-href-from-url' import { fetchServerResponse } from './router-reducer/fetch-server-response' import { dispatchAppRouterAction } from './use-action-queue' @@ -28,6 +29,7 @@ import { segmentCacheMap, type SegmentCacheEntry, type RouteTree, + type RootRouteTree, type RSCSegmentData, type RefreshState, type FulfilledRouteCacheEntry, @@ -38,20 +40,18 @@ import { spawnStaticStageCacheWrite, writeRuntimePrefetchStreamIntoCache, EntryStatus, + MetadataOnlyRequestTree, } from './segment-cache/cache' import { discoverKnownRoute } from './segment-cache/optimistic-routes' import { urlSearchParamsToParsedUrlQuery } from '../route-params' import type { NormalizedSearch } from './segment-cache/cache-key' import type { CacheMap } from './segment-cache/cache-map' -import { - getRenderedSearchFromVaryPath, - type VaryPath, -} from './segment-cache/vary-path' +import { getRenderedSearchFromVaryPath } from './segment-cache/vary-path' +import type { VaryPathNode } from './segment-cache/vary-path' import { readFromBFCache, readFromBFCacheDuringRegularNavigation, writeToBFCache, - writeHeadToBFCache, updateBFCacheEntryFromDynamicResponse, computeDynamicStaleAt, } from './segment-cache/bfcache' @@ -74,13 +74,23 @@ export type NavigationTask = { // Otherwise, this is the same as `route`, except with the `refetch` marker // set on the top-most segment that needs to be fetched. dynamicRequestTree: FlightRouterState | null - // The URL that should be used to fetch the dynamic data. This is only set - // when the segment cannot be refetched from the current route, because it's - // part of a "default" parallel slot that was reused during a navigation. - refreshState: RefreshState | null children: Map | null } +// The task-side counterpart of RootRouteTree; collapses into it once tasks +// are route trees. +export type RootNavigationTask = { + tree: NavigationTask + head: NavigationTask +} + +export function createRootNavigationTask( + tree: NavigationTask, + head: NavigationTask +): RootNavigationTask { + return { tree, head } +} + export const enum FreshnessPolicy { Default, Hydration, @@ -157,31 +167,40 @@ const noop = () => {} export function createInitialRenderTreeForHydration( navigatedAt: number, - initialTree: RouteTree, - seedHead: HeadData, + initialRoot: RootRouteTree, seedDynamicStaleAt: number -): NavigationTask { +): RootNavigationTask { // Create the initial cache node tree, using the data embedded into the // HTML document. const accumulation: NavigationRequestAccumulation = { separateRefreshUrls: null, scrollRef: null, } + const parentNeedsDynamicRequest = false const restrictToShell = false - const task = createRenderTreeOnNavigation( + // Hydration is bound to the shared map. + const map = segmentCacheMap + const tree = createRenderTreeOnNavigation( navigatedAt, - initialTree, - null, + initialRoot.tree, + FreshnessPolicy.Hydration, + seedDynamicStaleAt, + parentNeedsDynamicRequest, + accumulation, + map, + restrictToShell + ) + const head = createRenderTreeOnNavigation( + navigatedAt, + initialRoot.head, FreshnessPolicy.Hydration, - seedHead, seedDynamicStaleAt, - false, + parentNeedsDynamicRequest, accumulation, - // Hydration is bound to the shared map. - segmentCacheMap, + map, restrictToShell ) - return task + return createRootNavigationTask(tree, head) } // Creates a new Cache Node tree (i.e. copy-on-write) that represents the @@ -208,20 +227,18 @@ export function createInitialRenderTreeForHydration( // a synchronous function). Any new trees that do not have prefetch data will // suspend during rendering, until the dynamic data streams in. // -// Returns a Task object, which contains both the updated Cache Node and a path -// to the pending subtrees that need to be resolved by the navigation response. +// Returns the tasks for the route tree and the head, each containing both the +// updated Cache Node and a path to the pending subtrees that need to be +// resolved by the navigation response. // -// A return value of `null` means there were no changes, and the previous tree -// can be reused without initiating a server request. +// A return value of `null` means a full-page (MPA) navigation is required. export function startPPRNavigation( navigatedAt: number, oldUrl: URL, oldRenderedSearch: string, - oldRenderTree: RouteTree, - newRouteTree: RouteTree, - newMetadataVaryPath: VaryPath | null, + oldRoot: RootRouteTree, + newRoot: RootRouteTree, freshness: FreshnessPolicy, - seedHead: HeadData | null, seedDynamicStaleAt: number, isSamePageNavigation: boolean, accumulation: NavigationRequestAccumulation, @@ -231,20 +248,18 @@ export function startPPRNavigation( // Instant Navigation Testing API only — restricts segment reads to shell // entries. Always false outside the testing API. See navigation-testing-lock. restrictToShell: boolean -): NavigationTask | null { +): RootNavigationTask | null { const parentNeedsDynamicRequest = false const parentRefreshState = null const oldRootRefreshState: RefreshState = { canonicalUrl: createHrefFromUrl(oldUrl), renderedSearch: oldRenderedSearch as NormalizedSearch, } - return updateRenderTreeOnNavigation( + const tree = updateRenderTreeOnNavigation( navigatedAt, - oldRenderTree, - newRouteTree, - newMetadataVaryPath, + oldRoot.tree, + newRoot.tree, freshness, - seedHead, seedDynamicStaleAt, isSamePageNavigation, parentNeedsDynamicRequest, @@ -254,15 +269,99 @@ export function startPPRNavigation( map, restrictToShell ) + if (tree === null) { + // The route tree changed at or above the root layout. Perform a full-page + // navigation. + return null + } + + // The head has no position in the route tree, so there is nothing to + // traverse: either reuse the current head or create a new one, on the same + // terms that decide whether the page it belongs to is reused. + const oldHead = oldRoot.head + const newHead = newRoot.head + switch (freshness) { + case FreshnessPolicy.Default: + case FreshnessPolicy.HistoryTraversal: + case FreshnessPolicy.Gesture: { + if (isSamePageNavigation) { + // During a same-page navigation, we always refetch the page segments + break + } + // The head is the one node that is not compared as part of a tree walk + // (each tree node's params are compared where the walk visits it), so + // it compares its whole vary path here, entry by entry. The head's vary + // path is the page position it's keyed under, the rendered search, and + // every path param, so this covers the same changes that recreate a + // page node. + let oldEntry: VaryPathNode | null = oldHead.varyPath + let newEntry: VaryPathNode | null = newHead.varyPath + while ( + oldEntry !== null && + newEntry !== null && + oldEntry.value === newEntry.value + ) { + oldEntry = oldEntry.parent + newEntry = newEntry.parent + } + if (oldEntry !== null || newEntry !== null) { + // An entry differs, so a new head is created below. + break + } + return createRootNavigationTask( + tree, + createNavigationTask( + NavigationTaskStatus.Fulfilled, + createRouterStateForSegment(newHead, {}, null), + createRenderTree(newHead, oldHead.data), + null, + null + ) + ) + } + case FreshnessPolicy.Hydration: + case FreshnessPolicy.RefreshAll: + case FreshnessPolicy.HMRRefresh: + break + default: + freshness satisfies never + break + } + const head = createRenderTreeOnNavigation( + navigatedAt, + newHead, + freshness, + seedDynamicStaleAt, + parentNeedsDynamicRequest, + accumulation, + map, + restrictToShell + ) + return createRootNavigationTask(tree, head) +} + +// TODO: Unify NavigationTask with CacheNode. +function createNavigationTask( + status: NavigationTaskStatus, + route: FlightRouterState, + node: RouteTree, + dynamicRequestTree: FlightRouterState | null, + children: Map | null +): NavigationTask { + return { + status, + route, + node, + dynamicRequestTree, + children, + } } function updateRenderTreeOnNavigation( navigatedAt: number, oldRenderTree: RouteTree, newRouteTree: RouteTree, - newMetadataVaryPath: VaryPath | null, freshness: FreshnessPolicy, - seedHead: HeadData | null, seedDynamicStaleAt: number, isSamePageNavigation: boolean, parentNeedsDynamicRequest: boolean, @@ -326,9 +425,7 @@ function updateRenderTreeOnNavigation( return createRenderTreeOnNavigation( navigatedAt, newRouteTree, - newMetadataVaryPath, freshness, - seedHead, seedDynamicStaleAt, parentNeedsDynamicRequest, accumulation, @@ -388,16 +485,9 @@ function updateRenderTreeOnNavigation( } else { // If this is part of a refresh, ignore the existing render tree and create a // new one. - const data = newRouteTree.data - const seedRsc = data !== null ? data.rsc : null - const seedVaryParams = data !== null ? data.varyParams : null const result = createRenderTreeForSegment( navigatedAt, newRouteTree, - seedRsc, - seedVaryParams, - newMetadataVaryPath, - seedHead, freshness, seedDynamicStaleAt, // Refreshing data preserves the identity of the active segment. @@ -495,7 +585,6 @@ function updateRenderTreeOnNavigation( const oldSegmentChild = oldRenderTreeChild.segment const newSegmentChild = createSegmentFromRouteTree(newRouteTreeChild) - let seedHeadChild = seedHead if ( // Skip this branch during a history traversal. We restore the tree that // was stashed in the history entry as-is. @@ -510,21 +599,13 @@ function updateRenderTreeOnNavigation( oldRootRefreshState, oldRenderTreeChild ) - - // Discard the seed head, which corresponds to the outer route tree, - // not the reused one we're switching to. (Segment data needs no - // equivalent handling: it lives on the route tree nodes themselves, - // and a reused tree's nodes never carry data.) - seedHeadChild = null } const taskChild = updateRenderTreeOnNavigation( navigatedAt, oldRenderTreeChild, newRouteTreeChild, - newMetadataVaryPath, freshness, - seedHeadChild, seedDynamicStaleAt, isSamePageNavigation, parentNeedsDynamicRequest || needsDynamicRequest, @@ -563,32 +644,27 @@ function updateRenderTreeOnNavigation( } } - const newFlightRouterState: FlightRouterState = [ - createSegmentFromRouteTree(newRouteTree), + const newFlightRouterState = createRouterStateForSegment( + newRouteTree, patchedRouterStateChildren, - refreshState !== null - ? [refreshState.canonicalUrl, refreshState.renderedSearch] - : null, - null, - newRouteTree.prefetchHints, - ] + refreshState + ) - return { - status: needsDynamicRequest + return createNavigationTask( + needsDynamicRequest ? NavigationTaskStatus.Pending : NavigationTaskStatus.Fulfilled, - route: newFlightRouterState, - node: newRenderTree, - dynamicRequestTree: createDynamicRequestTree( + newFlightRouterState, + newRenderTree, + createDynamicRequestTree( newFlightRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest ), - refreshState, - children: taskChildren, - } + taskChildren + ) } /** @@ -607,6 +683,9 @@ function updateRenderTreeOnNavigation( * * Skipped during hydration (initial render should not scroll) and * history traversal (scroll restoration is handled separately). + * + * The head passes through here as a leaf too; its `scrollRef` is never read + * (only LayoutRouter reads one), and the page leaf sets the same shared ref. */ function accumulateScrollRef( freshness: FreshnessPolicy, @@ -638,9 +717,7 @@ function accumulateScrollRef( function createRenderTreeOnNavigation( navigatedAt: number, newRouteTree: RouteTree, - newMetadataVaryPath: VaryPath | null, freshness: FreshnessPolicy, - seedHead: HeadData | null, seedDynamicStaleAt: number, parentNeedsDynamicRequest: boolean, accumulation: NavigationRequestAccumulation, @@ -659,20 +736,11 @@ function createRenderTreeOnNavigation( // one, too. However there are some places where the behavior intentionally // diverges, which is why we keep them separate. - const newSegment = createSegmentFromRouteTree(newRouteTree) - const newSlots = newRouteTree.slots - const data = newRouteTree.data - const seedRsc = data !== null ? data.rsc : null - const seedVaryParams = data !== null ? data.varyParams : null const result = createRenderTreeForSegment( navigatedAt, newRouteTree, - seedRsc, - seedVaryParams, - newMetadataVaryPath, - seedHead, freshness, seedDynamicStaleAt, // This segment was not part of the previous route, so mint a fresh @@ -707,9 +775,7 @@ function createRenderTreeOnNavigation( const taskChild = createRenderTreeOnNavigation( navigatedAt, newRouteTreeChild, - newMetadataVaryPath, freshness, - seedHead, seedDynamicStaleAt, parentNeedsDynamicRequest || needsDynamicRequest, accumulation, @@ -733,32 +799,30 @@ function createRenderTreeOnNavigation( } } - const newFlightRouterState: FlightRouterState = [ - newSegment, + // This route is not part of the current tree, so there's no reason to + // track the refresh URL. + const refreshState = null + const newFlightRouterState = createRouterStateForSegment( + newRouteTree, patchedRouterStateChildren, - null, - null, - newRouteTree.prefetchHints, - ] + refreshState + ) - return { - status: needsDynamicRequest + return createNavigationTask( + needsDynamicRequest ? NavigationTaskStatus.Pending : NavigationTaskStatus.Fulfilled, - route: newFlightRouterState, - node: newRenderTree, - dynamicRequestTree: createDynamicRequestTree( + newFlightRouterState, + newRenderTree, + createDynamicRequestTree( newFlightRouterState, dynamicRequestTreeChildren, needsDynamicRequest, childNeedsDynamicRequest, parentNeedsDynamicRequest ), - // This route is not part of the current tree, so there's no reason to - // track the refresh URL. - refreshState: null, - children: taskChildren, - } + taskChildren + ) } function createSegmentFromRouteTree( @@ -788,6 +852,24 @@ function createSegmentFromRouteTree( return newRouteTree.segment } +// Converts a route tree node into the router state the client sends back to +// the server. +function createRouterStateForSegment( + routeTree: RouteTree, + children: { [parallelRouteKey: string]: FlightRouterState }, + refreshState: RefreshState | null +): FlightRouterState { + return [ + createSegmentFromRouteTree(routeTree), + children, + refreshState !== null + ? [refreshState.canonicalUrl, refreshState.renderedSearch] + : null, + null, + routeTree.prefetchHints, + ] +} + function patchRouterStateWithNewChildren( baseRouterState: FlightRouterState, newChildren: { [parallelRouteKey: string]: FlightRouterState } @@ -919,11 +1001,9 @@ function createRenderTree( function createRenderTreeForSegment( now: number, + // A route tree node, or the one-node metadata tree that stands in for the + // head (see createMetadataRouteTree). tree: RouteTree, - seedRsc: React.ReactNode | null, - seedVaryParams: VaryParams | null, - metadataVaryPath: VaryPath | null, - seedHead: HeadData | null, freshness: FreshnessPolicy, dynamicStaleAt: number, bfcacheId: number, @@ -947,7 +1027,9 @@ function createRenderTreeForSegment( // also be able to use that data without spawning a new request. (This is // referred to as the "seed" data.) - const isPage = tree.segment === PAGE_SEGMENT_KEY + const seedData = tree.data + const seedRsc = seedData !== null ? seedData.rsc : null + const seedVaryParams = seedData !== null ? seedData.varyParams : null // During certain kinds of navigations, we may be able to render from // the BFCache. @@ -973,8 +1055,6 @@ function createRenderTreeForSegment( bfcacheEntry.rsc, bfcacheEntry.prefetchRsc, bfcacheEntry.varyParams, - bfcacheEntry.head, - bfcacheEntry.prefetchHead, bfcacheId ) ), @@ -1004,14 +1084,9 @@ function createRenderTreeForSegment( seedRsc, null, seedVaryParams, - isPage ? seedHead : null, - null, bfcacheId ) writeToBFCache(now, tree.varyPath, cacheNode, dynamicStaleAt) - if (isPage && metadataVaryPath !== null) { - writeHeadToBFCache(now, metadataVaryPath, cacheNode, dynamicStaleAt) - } return { node: createRenderTree(tree, cacheNode), needsDynamicRequest: false, @@ -1044,8 +1119,6 @@ function createRenderTreeForSegment( bfcacheEntry.rsc, dropPrefetchRsc ? null : bfcacheEntry.prefetchRsc, bfcacheEntry.varyParams, - bfcacheEntry.head, - dropPrefetchRsc ? null : bfcacheEntry.prefetchHead, bfcacheEntry.bfcacheId ) ), @@ -1116,6 +1189,29 @@ function createRenderTreeForSegment( } } + if ( + process.env.__NEXT_OPTIMISTIC_ROUTING && + tree.segment === HEAD_REQUEST_KEY && + isCachedRscPartial + ) { + // TODO: When optimistic routing is enabled, don't block on waiting for + // the viewport to resolve. This is a temporary workaround until Vary + // Params are tracked when rendering the metadata. We'll fix it before + // this feature is stable. However, it's not a critical issue because 1) + // it will stream in eventually anyway 2) metadata is wrapped in an + // internal Suspense boundary, so is always non-blocking; this only + // affects the viewport node, which is meant to blocking, however... 3) + // before Segment Cache landed this wasn't always the case, anyway, so + // it's unlikely that many people are relying on this behavior. Still, + // will be fixed before stable. It's the very next step in the sequence of + // work on this project. + // + // This line of code works because the App Router treats `null` as + // "no renderable head available", rather than an empty head. React treats + // an empty string as empty. + cachedRsc = '' + } + // Now combine the cached data with the seed data to determine what we can // render immediately, versus what needs to stream in later. @@ -1172,111 +1268,15 @@ function createRenderTreeForSegment( doesSegmentNeedDynamicRequest = isCachedRscPartial } - // If this is a page segment, we need to do the same for the head. This - // follows analogous logic to the segment data above. - // TODO: We don't need to store the head on the page segment's CacheNode; we - // can lift it to the main state object. Then we can also delete - // findHeadCache. - - let prefetchHead: HeadData | null = null - let head: React.ReactNode | null = null - let doesHeadNeedDynamicRequest: boolean = isPage - - if (isPage) { - let cachedHead: HeadData | null = null - let isCachedHeadPartial: boolean = true - if (metadataVaryPath !== null) { - const metadataEntry = readSegmentCacheEntryForNavigation( - now, - map, - metadataVaryPath, - restrictToShell - ) - if (metadataEntry !== null) { - switch (metadataEntry.status) { - case EntryStatus.Fulfilled: { - cachedHead = metadataEntry.rsc - isCachedHeadPartial = metadataEntry.isPartial - break - } - case EntryStatus.Pending: { - cachedHead = waitForSegmentCacheEntry(metadataEntry).then( - (entry) => (entry !== null ? entry.rsc : null) - ) - isCachedHeadPartial = metadataEntry.isPartial - break - } - case EntryStatus.Empty: - case EntryStatus.Rejected: { - break - } - default: { - metadataEntry satisfies never - break - } - } - } - } - - if (process.env.__NEXT_OPTIMISTIC_ROUTING && isCachedHeadPartial) { - // TODO: When optimistic routing is enabled, don't block on waiting for - // the viewport to resolve. This is a temporary workaround until Vary - // Params are tracked when rendering the metadata. We'll fix it before - // this feature is stable. However, it's not a critical issue because 1) - // it will stream in eventually anyway 2) metadata is wrapped in an - // internal Suspense boundary, so is always non-blocking; this only - // affects the viewport node, which is meant to blocking, however... 3) - // before Segment Cache landed this wasn't always the case, anyway, so - // it's unlikely that many people are relying on this behavior. Still, - // will be fixed before stable. It's the very next step in the sequence of - // work on this project. - // - // This line of code works because the App Router treats `null` as - // "no renderable head available", rather than an empty head. React treats - // an empty string as empty. - cachedHead = '' - } - - if (seedHead !== null) { - if (isCachedHeadPartial) { - prefetchHead = cachedHead - head = seedHead - } else { - prefetchHead = null - head = cachedHead - } - doesHeadNeedDynamicRequest = false - } else { - if (isCachedHeadPartial) { - prefetchHead = cachedHead - head = createDeferredRsc() - } else { - prefetchHead = null - head = cachedHead - } - doesHeadNeedDynamicRequest = isCachedHeadPartial - } - } - // Now that we're creating a new segment, write its data to the BFCache. A // subsequent back/forward navigation will reuse this same data, until or // unless it's cleared by a refresh/revalidation. // // Skip BFCache writes for optimistic navigations since they are transient // and will be replaced by the canonical navigation. - const cacheNode = createCacheNode( - rsc, - prefetchRsc, - varyParams, - head, - prefetchHead, - bfcacheId - ) + const cacheNode = createCacheNode(rsc, prefetchRsc, varyParams, bfcacheId) if (freshness !== FreshnessPolicy.Gesture) { writeToBFCache(now, tree.varyPath, cacheNode, dynamicStaleAt) - if (isPage && metadataVaryPath !== null) { - writeHeadToBFCache(now, metadataVaryPath, cacheNode, dynamicStaleAt) - } } return { @@ -1284,8 +1284,7 @@ function createRenderTreeForSegment( // TODO: We should store this field on the CacheNode itself. I think we can // probably unify NavigationTask, CacheNode, and DeferredRsc into a // single type. Or at least CacheNode and DeferredRsc. - needsDynamicRequest: - doesSegmentNeedDynamicRequest || doesHeadNeedDynamicRequest, + needsDynamicRequest: doesSegmentNeedDynamicRequest, } } @@ -1293,8 +1292,6 @@ function createCacheNode( rsc: React.ReactNode | null, prefetchRsc: React.ReactNode | null, varyParams: VaryParams | null, - head: React.ReactNode | null, - prefetchHead: HeadData | null, bfcacheId: number, scrollRef: ScrollRef | null = null ): CacheNode { @@ -1302,8 +1299,6 @@ function createCacheNode( rsc, prefetchRsc, varyParams, - head, - prefetchHead, scrollRef, bfcacheId, } @@ -1376,7 +1371,7 @@ let previousNavigationDidMismatch = false // This does _not_ create a new tree; it modifies the existing one in place. // Which means it must follow the Suspense rules of cache safety. export function spawnDynamicRequests( - task: NavigationTask, + navigation: RootNavigationTask, primaryUrl: URL, nextUrl: string | null, freshnessPolicy: FreshnessPolicy, @@ -1396,11 +1391,17 @@ export function spawnDynamicRequests( map: CacheMap, signal: AbortSignal | undefined ): void { - const dynamicRequestTree = task.dynamicRequestTree + let dynamicRequestTree = navigation.tree.dynamicRequestTree if (dynamicRequestTree === null) { - // This navigation was fully cached. There are no dynamic requests to spawn. - previousNavigationDidMismatch = false - return + if (navigation.head.status === NavigationTaskStatus.Pending) { + // Every segment is cached, but the head is not. Ask the server for the + // head alone. + dynamicRequestTree = MetadataOnlyRequestTree + } else { + // This navigation was fully cached. There are no dynamic requests to spawn. + previousNavigationDidMismatch = false + return + } } // This is intentionally not an async function to discourage the caller from @@ -1413,7 +1414,8 @@ export function spawnDynamicRequests( // `finishNavigationTask`, can await the promises in any order without // accidentally introducing a network waterfall. const primaryRequestPromise = fetchMissingDynamicData( - task, + navigation.tree, + navigation.head, dynamicRequestTree, primaryUrl, nextUrl, @@ -1463,7 +1465,9 @@ export function spawnDynamicRequests( if (scopedDynamicRequestTree !== null) { refreshRequestPromises.push( fetchMissingDynamicData( - task, + navigation.tree, + // The head belongs to the primary URL. + null, scopedDynamicRequestTree, new URL(refreshUrl, location.origin), // TODO: Just noticed that this should actually the Next-Url at the @@ -1486,7 +1490,7 @@ export function spawnDynamicRequests( // Further async operations are moved into this separate function to // discourage sequential network requests. const voidPromise = finishNavigationTask( - task, + navigation, nextUrl, primaryRequestPromise, refreshRequestPromises, @@ -1499,7 +1503,7 @@ export function spawnDynamicRequests( } async function finishNavigationTask( - task: NavigationTask, + navigation: RootNavigationTask, nextUrl: string | null, primaryRequestPromise: ReturnType, refreshRequestPromises: Array< @@ -1521,7 +1525,19 @@ async function finishNavigationTask( // first phase; it doesn't matter in that case because we're going to refresh // the whole tree regardless. if (exitStatus === NavigationTaskExitStatus.Done) { - exitStatus = abortRemainingPendingTasks(task, null, null) + exitStatus = abortRemainingPendingTasks(navigation.tree, null, null) + // A response without a head is a mismatch, like any missing segment. The + // head's deferred rsc must be resolved to `null` here, never rejected: it + // renders at the app root, so a rejection would hit the root error + // boundary while the retry is in flight. + const headExitStatus = abortRemainingPendingTasks( + navigation.head, + null, + null + ) + if (headExitStatus > exitStatus) { + exitStatus = headExitStatus + } } switch (exitStatus) { @@ -1551,7 +1567,7 @@ async function finishNavigationTask( primaryRequestResult.url, nextUrl, primaryRequestResult.seed, - task, + navigation, routeCacheEntry, navigateType, FreshnessPolicy.RefreshAll @@ -1570,7 +1586,7 @@ async function finishNavigationTask( primaryRequestResult.url, nextUrl, primaryRequestResult.seed, - task, + navigation, routeCacheEntry, navigateType, FreshnessPolicy.HistoryTraversal @@ -1593,7 +1609,7 @@ async function finishNavigationTask( primaryRequestResult.url, nextUrl, primaryRequestResult.seed, - task, + navigation, routeCacheEntry, navigateType, FreshnessPolicy.RefreshAll @@ -1660,7 +1676,7 @@ function dispatchRetryDueToTreeMismatch( retryUrl: URL, retryNextUrl: string | null, seed: NavigationSeed | null, - task: NavigationTask, + navigation: RootNavigationTask, // The route cache entry used for this navigation, if it came from route // prediction. If the navigation results in a mismatch, we mark it as having // a dynamic rewrite so future predictions bail out. @@ -1689,30 +1705,30 @@ function dispatchRetryDueToTreeMismatch( // mark the route as having a dynamic rewrite by traversing the known route // tree. This handles cases where the navigation didn't originate from a // route prediction, but still needs to mark the pattern. - const metadataVaryPath = seed.metadataVaryPath - if (metadataVaryPath !== null) { - const now = Date.now() - discoverKnownRoute( - now, - retryUrl.pathname, - retryUrl.search as NormalizedSearch, - retryNextUrl, - null, - seed.routeTree, - metadataVaryPath, - false, // couldBeIntercepted - doesn't matter, we're just marking hasDynamicRewrite - createHrefFromUrl(retryUrl), - false, // supportsPerSegmentPrefetching - doesn't matter, we're just marking hasDynamicRewrite - true // hasDynamicRewrite - ) - } + const now = Date.now() + discoverKnownRoute( + now, + retryUrl.pathname, + retryUrl.search as NormalizedSearch, + retryNextUrl, + null, + seed.root, + false, // couldBeIntercepted - doesn't matter, we're just marking hasDynamicRewrite + createHrefFromUrl(retryUrl), + seed.renderedSearch, + false, // supportsPerSegmentPrefetching - doesn't matter, we're just marking hasDynamicRewrite + true // hasDynamicRewrite + ) } // Invalidate all route cache entries. If the navigation used a route entry // the server resolved, its tree is what the server just contradicted, so // the retry must re-fetch it rather than navigate with it again. This also // triggers re-prefetching of visible links. - invalidateRouteCacheEntries(retryNextUrl, task.node) + invalidateRouteCacheEntries(retryNextUrl, { + tree: navigation.tree.node, + head: navigation.head.node, + }) // If this is the second time in a row that a navigation resulted in a // mismatch, fall back to a hard (MPA) refresh. @@ -1733,7 +1749,7 @@ function dispatchRetryDueToTreeMismatch( // not here where the action is constructed. But the current action queue // doesn't provide a natural place for that. Revisit when we refactor the // action queue into a more reactive navigation model. - const baseTree = task.route + const baseTree = navigation.tree.route const lastCommitted = getLastCommittedTree() const retryNavigateType: 'push' | 'replace' = lastCommitted !== null && baseTree !== lastCommitted @@ -1754,7 +1770,8 @@ function dispatchRetryDueToTreeMismatch( } async function fetchMissingDynamicData( - task: NavigationTask, + tree: NavigationTask, + head: NavigationTask | null, dynamicRequestTree: FlightRouterState, url: URL, nextUrl: string | null, @@ -1790,7 +1807,7 @@ async function fetchMissingDynamicData( const seed = createNavigationSeed( now, - task.route, + tree.route, result.transportData, // Navigation responses stream in incrementally, so their vary params // can't be drained here; they decode as null. @@ -1800,6 +1817,7 @@ async function fetchMissingDynamicData( // there's no pathname to parse them from (nor a need to). null, result.renderedSearch, + null, result.dynamicStaleTime ) @@ -1840,14 +1858,23 @@ async function fetchMissingDynamicData( const dynamicStaleAt = computeDynamicStaleAt(now, result.dynamicStaleTime) const didReceiveUnknownParallelRoute = writeDynamicDataIntoNavigationTask( - task, - seed.routeTree, - seed.head, + tree, + seed.root.tree, dynamicStaleAt, result.debugInfo, result.revealAfter ) + if (head !== null) { + writeDynamicDataIntoNavigationTask( + head, + seed.root.head, + dynamicStaleAt, + result.debugInfo, + result.revealAfter + ) + } + const resolvedUrl = new URL(result.canonicalUrl, location.origin) // Decide whether the navigation needs to be retried. @@ -1913,7 +1940,6 @@ async function fetchMissingDynamicData( function writeDynamicDataIntoNavigationTask( task: NavigationTask, serverRouteTree: RouteTree, - dynamicHead: HeadData, dynamicStaleAt: number, debugInfo: Array | null, revealAfter: Promise | null @@ -1925,13 +1951,7 @@ function writeDynamicDataIntoNavigationTask( if (task.status === NavigationTaskStatus.Pending && dynamicData !== null) { task.status = NavigationTaskStatus.Fulfilled const cacheNode = task.node.data - finishPendingCacheNode( - cacheNode, - dynamicData, - dynamicHead, - debugInfo, - revealAfter - ) + finishPendingCacheNode(cacheNode, dynamicData, debugInfo, revealAfter) // The BFCache entry for this segment was written before the response // arrived. Bring it up to date with what the response filled in: its @@ -1984,7 +2004,6 @@ function writeDynamicDataIntoNavigationTask( writeDynamicDataIntoNavigationTask( taskChild, serverRouteTreeChild, - dynamicHead, dynamicStaleAt, debugInfo, revealAfter @@ -2009,7 +2028,6 @@ function writeDynamicDataIntoNavigationTask( function finishPendingCacheNode( cacheNode: CacheNode, dynamicData: RSCSegmentData, - dynamicHead: HeadData, debugInfo: Array | null, revealAfter: Promise | null ): void { @@ -2070,14 +2088,6 @@ function finishPendingCacheNode( // empty, so it must have been populated by a different navigation. We // must not overwrite it (nor its dependency source). } - - // Check if this is a leaf segment. If so, it will have a `head` property with - // a pending promise that needs to be resolved with the dynamic head from - // the server. - const head = cacheNode.head - if (isDeferredRsc(head)) { - head.resolve(dynamicHead, debugInfo) - } } function abortRemainingPendingTasks( @@ -2103,7 +2113,7 @@ function abortRemainingPendingTasks( // // When this happens, we treat this the same as a refresh(). The entire // tree will be re-rendered from the root. - if (task.refreshState === null) { + if (task.node.refreshState === null) { // Trigger a "soft" refresh. Essentially the same as calling `refresh()` // in a Server Action. exitStatus = NavigationTaskExitStatus.SoftRetry @@ -2156,15 +2166,6 @@ function abortPendingCacheNode( rsc.reject(error, debugInfo) } } - - // Check if this is a leaf segment. If so, it will have a `head` property with - // a pending promise that needs to be resolved. If an error was provided, we - // will not resolve it with an error, since this is rendered at the root of - // the app. We want the segment to error, not the entire app. - const head = cacheNode.head - if (isDeferredRsc(head)) { - head.resolve(null, debugInfo) - } } const DEFERRED = Symbol() diff --git a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts index 5a1da1fd46a3..4b9691727003 100644 --- a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts +++ b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts @@ -10,12 +10,10 @@ import { writeRuntimePrefetchStreamIntoCache, spawnStaticStageCacheWrite, segmentCacheMap, + createRootRouteTree, } from '../segment-cache/cache' -import { decodeTransportTreeIntoRouteTree } from '../segment-cache/decode-server-response' -import { - UnknownDynamicStaleTime, - computeDynamicStaleAt, -} from '../segment-cache/bfcache' +import { createNavigationSeed } from '../segment-cache/decode-server-response' +import { UnknownDynamicStaleTime } from '../segment-cache/bfcache' import { decodeStageUntilBoundary } from './fetch-server-response' import { discoverKnownRoute } from '../segment-cache/optimistic-routes' import type { NormalizedSearch } from '../segment-cache/cache-key' @@ -51,8 +49,6 @@ export function createInitialRouterState({ // as a URL that should be crawled. const initialCanonicalUrl = initialCanonicalUrlParts.join('/') - const initialHead = initialTransportData.h.r - // The initial router state tree, derived from the transport tree. Page // segments keep their search params, which travel inside the segment // string. @@ -66,13 +62,10 @@ export function createInitialRouterState({ createHrefFromUrl(location) : initialCanonicalUrl - // Decode the initial transport tree into the RouteTree type, with the - // payload's render output embedded on each node. (discoverKnownRoute below - // stores this tree in the route cache, which strips the data on write — - // see stripDataFromRouteTree.) - // NOTE: The metadataVaryPath isn't used for anything currently because the - // head is embedded into the render tree, but eventually we'll lift it out - // and store it on the top-level state object. + // Decode the initial transport data into the RouteTree type, with the + // payload's render output embedded on each node and the head as its own + // one-node tree. (discoverKnownRoute below stores the route tree in the + // route cache, which strips the data on write — see stripDataFromRouteTree.) // // For statically-generated-at-build-time HTML pages, the tree baked into // the initial RSC payload won't have the correct segment inlining hints @@ -80,12 +73,12 @@ export function createInitialRouterState({ // trees with InliningHintsStale, which causes the route cache entry to be // immediately expired. The next prefetch will re-fetch the tree with // correct hints from the /_tree response. - const acc = { metadataVaryPath: null, treeDivergedFromBase: false } - const initialRouteTree = decodeTransportTreeIntoRouteTree( - initialTransportData.t, + const initialSeed = createNavigationSeed( + navigatedAt, // There's no base tree to overlay onto; the initial payload is a full // render from the root. null, + initialTransportData, // The initial payload may still be streaming in while we hydrate, so its // vary params can't be drained here; they decode as null. The // segment-cache write below re-decodes the transport data with the @@ -100,23 +93,20 @@ export function createInitialRouterState({ // see createInitialRSCPayloadFromFallbackPrerender), so there's no // pathname to parse them from. null, - initialRenderedSearch as NormalizedSearch, - acc + initialRenderedSearch, + null, + initialDynamicStaleTimeSeconds ?? UnknownDynamicStaleTime ) - const metadataVaryPath = acc.metadataVaryPath - const initialTask = createInitialRenderTreeForHydration( + const initialRoot = initialSeed.root + const initialNavigation = createInitialRenderTreeForHydration( navigatedAt, - initialRouteTree, - initialHead, - computeDynamicStaleAt( - navigatedAt, - initialDynamicStaleTimeSeconds ?? UnknownDynamicStaleTime - ) + initialRoot, + initialSeed.dynamicStaleAt ) // The following only applies in the browser (location !== null) since neither // route learning nor segment cache state persists from SSR to client. - if (location !== null && metadataVaryPath !== null) { + if (location !== null) { // Learn the route pattern so we can predict it for future navigations. discoverKnownRoute( Date.now(), @@ -124,10 +114,10 @@ export function createInitialRouterState({ location.search as NormalizedSearch, null, // nextUrl — initial render is never an interception null, // No pending entry - initialRouteTree, - metadataVaryPath, + initialRoot, initialCouldBeIntercepted, canonicalUrl, + initialSeed.renderedSearch, initialSupportsPerSegmentPrefetching, false // hasDynamicRewrite ) @@ -245,8 +235,11 @@ export function createInitialRouterState({ // complete tree.) const initialState = { - tree: initialTask.route, - cache: initialTask.node, + tree: initialNavigation.tree.route, + root: createRootRouteTree( + initialNavigation.tree.node, + initialNavigation.head.node + ), pushRef: { pendingPush: false, mpaNavigation: false, diff --git a/packages/next/src/client/components/router-reducer/create-segment-key.browser.ts b/packages/next/src/client/components/router-reducer/create-segment-key.browser.ts index d8fb04c7c25f..85521147b972 100644 --- a/packages/next/src/client/components/router-reducer/create-segment-key.browser.ts +++ b/packages/next/src/client/components/router-reducer/create-segment-key.browser.ts @@ -1,3 +1,24 @@ +import type { VaryPath, VaryPathNode } from '../segment-cache/vary-path' + // In the browser, React keys include concrete params so navigation preserves // or resets component state according to the segment's actual identity. export { createRouterCacheKey as createSegmentKey } from './create-router-cache-key' + +// The head's key also includes the search params. Otherwise, inside a +// transition, `useDeferredValue` returns the new (still pending) head instead +// of the prefetched one, and the whole navigation suspends. +export function createHeadKey(varyPath: VaryPath): string { + // Encode the parts as a JSON array rather than joining them. A catch-all + // value and a search string can both contain `/`, so joined strings from + // different params could be the same. + const parts: Array = [varyPath.value] + let params: VaryPathNode | null = varyPath.parent + while (params !== null) { + const value = params.value + if (typeof value === 'string') { + parts.push(value) + } + params = params.parent + } + return JSON.stringify(parts) +} diff --git a/packages/next/src/client/components/router-reducer/create-segment-key.ts b/packages/next/src/client/components/router-reducer/create-segment-key.ts index 540f1ea7a11a..e250153f8475 100644 --- a/packages/next/src/client/components/router-reducer/create-segment-key.ts +++ b/packages/next/src/client/components/router-reducer/create-segment-key.ts @@ -1,4 +1,5 @@ import type { Segment } from '../../../shared/lib/app-router-types' +import type { VaryPath } from '../segment-cache/vary-path' import { createRouterCacheKey } from './create-router-cache-key' // React uses these keys to find suspended subtrees when resuming HTML. They @@ -17,3 +18,13 @@ export function createSegmentKey( return createRouterCacheKey(segment, true) } + +// TODO: To model this more accurately, we should use React.optimisticKey +// instead. Perhaps a separate Fragment that wraps around the Head: +// where headKey is React.optimisticKey during +// SSR. We should do this for all fallback param values. +export function createHeadKey(varyPath: VaryPath): string { + // The head's vary path starts with its request key: the route structure, + // with param names but no values. + return varyPath.value +} diff --git a/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts b/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts deleted file mode 100644 index e04d48cade1c..000000000000 --- a/packages/next/src/client/components/router-reducer/reducers/find-head-in-cache.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { RouteTree } from '../../segment-cache/cache' -import type { - FlightRouterState, - CacheNode, -} from '../../../../shared/lib/app-router-types' -import { DEFAULT_SEGMENT_KEY } from '../../../../shared/lib/segment' -import { createSegmentKey } from '../create-segment-key' - -export function findHeadInCache( - cache: RouteTree, - parallelRoutes: FlightRouterState[1] -): [RouteTree, string] | null { - return findHeadInCacheImpl(cache, parallelRoutes, '') -} - -function findHeadInCacheImpl( - cache: RouteTree, - parallelRoutes: FlightRouterState[1], - keyPrefix: string -): [RouteTree, string] | null { - const isLastItem = Object.keys(parallelRoutes).length === 0 - if (isLastItem) { - // Returns the render tree of the segment whose head we will render. - return [cache, keyPrefix] - } - - // First try the 'children' parallel route if it exists - // when starting from the "root", this corresponds with the main page component - const parallelRoutesKeys = Object.keys(parallelRoutes).filter( - (key) => key !== 'children' - ) - - // if we are at the root, we need to check the children slot first - if ('children' in parallelRoutes) { - parallelRoutesKeys.unshift('children') - } - - const slots = cache.slots - if (slots !== null) { - for (const key of parallelRoutesKeys) { - const [segment, childParallelRoutes] = parallelRoutes[key] - // If the parallel is not matched and using the default segment, - // skip searching the head from it. - if (segment === DEFAULT_SEGMENT_KEY) { - continue - } - - const childRenderTree = slots.get(key) - if (!childRenderTree) { - continue - } - - // This key identifies the Head component, not a cache entry. On the - // server it must match even when resuming with previously unknown params. - const segmentKey = createSegmentKey(segment) - - const item = findHeadInCacheImpl( - childRenderTree, - childParallelRoutes, - keyPrefix + '/' + segmentKey - ) - - if (item) { - return item - } - } - } - - return null -} diff --git a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts index a2666d7c5595..bf20d216cf05 100644 --- a/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/navigate-reducer.ts @@ -46,7 +46,7 @@ export function navigateReducer( url, currentUrl, currentRenderedSearch, - state.cache, + state.root, state.tree, state.nextUrl, FreshnessPolicy.Default, diff --git a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts index f7b59087ea42..8de4cae419da 100644 --- a/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/refresh-reducer.ts @@ -33,8 +33,8 @@ export function refreshReducer( process.env.__NEXT_EXPOSE_TESTING_API && action.bypassCacheInvalidation if (!bypassCacheInvalidation) { const currentNextUrl = state.nextUrl - const currentRenderTree = state.cache - invalidateSegmentCacheEntries(currentNextUrl, currentRenderTree) + const currentRoot = state.root + invalidateSegmentCacheEntries(currentNextUrl, currentRoot) } // A full refresh has no HMR generation to cancel. return refreshDynamicData(state, FreshnessPolicy.RefreshAll, undefined) @@ -83,6 +83,7 @@ export function refreshDynamicData( true, null, currentRenderedSearch, + null, UnknownDynamicStaleTime ) @@ -99,7 +100,7 @@ export function refreshDynamicData( refreshSeed, currentUrl, currentRenderedSearch, - state.cache, + state.root, freshnessPolicy, nextUrlForRefresh, scrollBehavior, diff --git a/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts index 9fea9bdbbebc..1b744a453f36 100644 --- a/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/restore-reducer.ts @@ -63,17 +63,16 @@ export function restoreReducer( true, null, renderedSearch, + null, UnknownDynamicStaleTime ) - const task = startPPRNavigation( + const navigation = startPPRNavigation( now, currentUrl, state.renderedSearch, - state.cache, - restoreSeed.routeTree, - restoreSeed.metadataVaryPath, + state.root, + restoreSeed.root, FreshnessPolicy.HistoryTraversal, - null, restoreSeed.dynamicStaleAt, false, accumulation, @@ -83,11 +82,11 @@ export function restoreReducer( false ) - if (task === null) { + if (navigation === null) { return completeHardNavigation(state, restoredUrl, 'replace') } spawnDynamicRequests( - task, + navigation, restoredUrl, restoredNextUrl, FreshnessPolicy.HistoryTraversal, @@ -117,8 +116,7 @@ export function restoreReducer( state, restoredUrl, renderedSearch, - task.node, - task.route, + navigation, restoredNextUrl ) } diff --git a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts index fde5cbe3c7f2..764906a376be 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/server-action-reducer.ts @@ -365,7 +365,7 @@ export function serverActionReducer( // invalidate both caches until we have a way to detect cookie // mutations on the client. if (revalidationKind === ActionDidRevalidateStaticAndDynamic) { - invalidateEntirePrefetchCache(nextUrl, state.cache) + invalidateEntirePrefetchCache(nextUrl, state.root) } // Start a cooldown before re-prefetching to allow CDN cache @@ -494,26 +494,24 @@ export function serverActionReducer( // tree, so there's no pathname to parse them from (nor a need to). null, flightDataRenderedSearch, + null, UnknownDynamicStaleTime ) // Learn the route pattern so we can predict it for future navigations. - const metadataVaryPath = redirectSeed.metadataVaryPath - if (metadataVaryPath !== null) { - discoverKnownRoute( - now, - redirectUrl.pathname, - redirectUrl.search as NormalizedSearch, - nextUrl, - null, // No pending entry - redirectSeed.routeTree, - metadataVaryPath, - couldBeIntercepted, - redirectCanonicalUrl, - isPrerender, - false // hasDynamicRewrite - ) - } + discoverKnownRoute( + now, + redirectUrl.pathname, + redirectUrl.search as NormalizedSearch, + nextUrl, + null, // No pending entry + redirectSeed.root, + couldBeIntercepted, + redirectCanonicalUrl, + redirectSeed.renderedSearch, + isPrerender, + false // hasDynamicRewrite + ) const navigationLock = getCurrentNavigationLock() return navigateToKnownRoute( @@ -524,7 +522,7 @@ export function serverActionReducer( redirectSeed, currentUrl, currentRenderedSearch, - state.cache, + state.root, freshnessPolicy, nextUrl, scrollBehavior, @@ -550,7 +548,7 @@ export function serverActionReducer( redirectUrl, currentUrl, currentRenderedSearch, - state.cache, + state.root, currentFlightRouterState, nextUrl, freshnessPolicy, diff --git a/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts b/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts index f3f6088053ec..fbd29207ca11 100644 --- a/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts +++ b/packages/next/src/client/components/router-reducer/reducers/server-patch-reducer.ts @@ -59,7 +59,7 @@ export function serverPatchReducer( retrySeed, currentUrl, currentRenderedSearch, - state.cache, + state.root, action.freshnessPolicy, retryNextUrl, scrollBehavior, diff --git a/packages/next/src/client/components/router-reducer/router-reducer-types.ts b/packages/next/src/client/components/router-reducer/router-reducer-types.ts index cdef75da5d9e..0081ec8a9f2a 100644 --- a/packages/next/src/client/components/router-reducer/router-reducer-types.ts +++ b/packages/next/src/client/components/router-reducer/router-reducer-types.ts @@ -1,4 +1,4 @@ -import type { RouteTree } from '../segment-cache/cache' +import type { RootRouteTree } from '../segment-cache/cache' import type { CacheNode, ScrollRef } from '../../../shared/lib/app-router-types' import type { FlightRouterState } from '../../../shared/lib/app-router-types' import type { NavigationSeed } from '../segment-cache/decode-server-response' @@ -211,10 +211,15 @@ export type AppRouterState = { */ tree: FlightRouterState /** - * The cache holds React nodes for every segment that is shown on screen as well as previously shown segments. - * It also holds in-progress data requests. + * The render tree and the document head (see RootRouteTree). The tree holds + * React nodes for every segment that is shown on screen as well as + * previously shown segments, and in-progress data requests. The head is a + * one-node render tree keyed at the metadata vary path. + * + * One object per committed navigation; the prefetch scheduler compares it by + * identity (see PrefetchTask.renderTreeAtTimeOfPrefetch). */ - cache: RouteTree + root: RootRouteTree /** * Decides if the update should create a new history entry and if the navigation has to trigger a browser navigation. */ diff --git a/packages/next/src/client/components/segment-cache/bfcache.ts b/packages/next/src/client/components/segment-cache/bfcache.ts index 11e20651528a..ff33719fcbb5 100644 --- a/packages/next/src/client/components/segment-cache/bfcache.ts +++ b/packages/next/src/client/components/segment-cache/bfcache.ts @@ -34,8 +34,6 @@ import { export type BFCacheEntry = { rsc: React.ReactNode | null prefetchRsc: React.ReactNode | null - head: React.ReactNode | null - prefetchHead: React.ReactNode | null // The source of the params `rsc` depends on, copied from the CacheNode that // wrote this entry (see CacheNode.varyParams). A restored node reads it to @@ -79,47 +77,18 @@ export function writeToBFCache( varyPath: VaryPath, cacheNode: CacheNode, dynamicStaleAt: number -): void { - writeEntryToBFCache( - now, - varyPath, - cacheNode.rsc, - cacheNode.prefetchRsc, - cacheNode.varyParams, - cacheNode.head, - cacheNode.prefetchHead, - dynamicStaleAt, - cacheNode.bfcacheId - ) -} - -function writeEntryToBFCache( - now: number, - varyPath: VaryPath, - rsc: React.ReactNode, - prefetchRsc: React.ReactNode, - varyParams: VaryParams | null, - head: React.ReactNode, - prefetchHead: React.ReactNode, - dynamicStaleAt: number, - bfcacheId: number ): void { if (typeof window === 'undefined') { return } const entry: BFCacheEntry = { - rsc, - prefetchRsc, - - // TODO: These fields will be removed from both BFCacheEntry and - // SegmentCacheEntry. The head has its own separate cache entry. - head, - prefetchHead, + rsc: cacheNode.rsc, + prefetchRsc: cacheNode.prefetchRsc, - varyParams, + varyParams: cacheNode.varyParams, - bfcacheId, + bfcacheId: cacheNode.bfcacheId, ref: null, // TODO: This is just a heuristic. Getting the actual size of the segment @@ -142,29 +111,6 @@ function writeEntryToBFCache( setInCacheMap(bfcacheMap, varyPath, entry, isRevalidation) } -export function writeHeadToBFCache( - now: number, - varyPath: VaryPath, - cacheNode: CacheNode, - dynamicStaleAt: number -): void { - // Write the special "segment" that represents the head data. The page - // node's head fields take the place of the entry's segment fields. The - // head's dependency source isn't tracked on the node, so the entry has - // none. - writeEntryToBFCache( - now, - varyPath, - cacheNode.head, - cacheNode.prefetchHead, - null, - null, - null, - dynamicStaleAt, - cacheNode.bfcacheId - ) -} - /** * Patches the entry written for a segment before its dynamic response * arrived, with what the response filled in on the segment's CacheNode: the diff --git a/packages/next/src/client/components/segment-cache/cache.ts b/packages/next/src/client/components/segment-cache/cache.ts index e296657b9708..fd65a7223e4a 100644 --- a/packages/next/src/client/components/segment-cache/cache.ts +++ b/packages/next/src/client/components/segment-cache/cache.ts @@ -47,7 +47,6 @@ import { getShellSegmentVaryPath, cloneVaryPathWithNewSearchParams, getPartialVaryPath, - getRenderedSearchFromVaryPath, } from './vary-path' import { createHrefFromUrl } from '../router-reducer/create-href-from-url' import type { @@ -228,6 +227,23 @@ export type RefreshState = { renderedSearch: NormalizedSearch } +// A route's complete render structure. The head is fetched, cached, and +// rendered like a page segment, but it has no position in the route tree, so +// it sits beside the tree as its own one-node tree (see +// createMetadataRouteTree). This is the shape a server response decodes to, +// the router state holds, and a navigation produces. +export type RootRouteTree = { + tree: RouteTree + head: RouteTree +} + +export function createRootRouteTree( + tree: RouteTree, + head: RouteTree +): RootRouteTree { + return { tree, head } +} + type RouteCacheEntryShared = { // This is false only if we're certain the route cannot be intercepted. It's // true in all other cases, including on initialization when we haven't yet @@ -258,8 +274,7 @@ export type PendingRouteCacheEntry = RouteCacheEntryShared & { blockedTasks: Set | null canonicalUrl: null renderedSearch: null - tree: null - metadata: null + root: null supportsPerSegmentPrefetching: false } @@ -268,8 +283,7 @@ type RejectedRouteCacheEntry = RouteCacheEntryShared & { blockedTasks: Set | null canonicalUrl: null renderedSearch: null - tree: null - metadata: null + root: null supportsPerSegmentPrefetching: boolean } @@ -278,8 +292,7 @@ export type FulfilledRouteCacheEntry = RouteCacheEntryShared & { blockedTasks: null canonicalUrl: string renderedSearch: NormalizedSearch - tree: RouteTree - metadata: RouteTree + root: RootRouteTree supportsPerSegmentPrefetching: boolean } @@ -451,13 +464,13 @@ export function getCurrentSegmentCacheVersion(): number { */ export function invalidateEntirePrefetchCache( nextUrl: string | null, - tree: RouteTree + root: RootRouteTree ): void { currentRouteCacheVersion++ currentSegmentCacheVersion++ - pingVisibleLinks(nextUrl, tree) - pingInvalidationListeners(nextUrl, tree) + pingVisibleLinks(nextUrl, root) + pingInvalidationListeners(nextUrl, root) } /** @@ -469,12 +482,12 @@ export function invalidateEntirePrefetchCache( */ export function invalidateRouteCacheEntries( nextUrl: string | null, - tree: RouteTree + root: RootRouteTree ): void { currentRouteCacheVersion++ - pingVisibleLinks(nextUrl, tree) - pingInvalidationListeners(nextUrl, tree) + pingVisibleLinks(nextUrl, root) + pingInvalidationListeners(nextUrl, root) } /** @@ -486,12 +499,12 @@ export function invalidateRouteCacheEntries( */ export function invalidateSegmentCacheEntries( nextUrl: string | null, - tree: RouteTree + root: RootRouteTree ): void { currentSegmentCacheVersion++ - pingVisibleLinks(nextUrl, tree) - pingInvalidationListeners(nextUrl, tree) + pingVisibleLinks(nextUrl, root) + pingInvalidationListeners(nextUrl, root) } function attachInvalidationListener(task: PrefetchTask): void { @@ -531,7 +544,7 @@ function notifyInvalidationListener(task: PrefetchTask): void { export function pingInvalidationListeners( nextUrl: string | null, - cache: RouteTree + root: RootRouteTree ): void { // The rough equivalent of pingVisibleLinks, but for onInvalidate callbacks. // This is called when the Next-Url or the base tree changes, since those @@ -541,7 +554,7 @@ export function pingInvalidationListeners( const tasks = invalidationListeners invalidationListeners = null for (const task of tasks) { - if (isPrefetchTaskDirty(task, nextUrl, cache)) { + if (isPrefetchTaskDirty(task, nextUrl, root)) { notifyInvalidationListener(task) } } @@ -672,8 +685,7 @@ function createDetachedRouteCacheEntry(): PendingRouteCacheEntry { canonicalUrl: null, status: EntryStatus.Empty, blockedTasks: null, - tree: null, - metadata: null, + root: null, // This is initialized to true because we don't know yet whether the route // could be intercepted. It's only set to false once we receive a response // from the server. @@ -811,12 +823,17 @@ export function deprecated_requestOptimisticRouteCacheEntry( const optimisticCanonicalUrl = createHrefFromUrl(optimisticUrl) const optimisticRouteTree = deprecated_createOptimisticRouteTree( - routeWithNoSearchParams.tree, + routeWithNoSearchParams.root.tree, optimisticRenderedSearch ) - const optimisticMetadataTree = deprecated_createOptimisticRouteTree( - routeWithNoSearchParams.metadata, - optimisticRenderedSearch + const baseMetadataTree = routeWithNoSearchParams.root.head + const optimisticMetadataTree = createMetadataRouteTree( + cloneVaryPathWithNewSearchParams( + baseMetadataTree.varyPath, + optimisticRenderedSearch + ), + baseMetadataTree.prefetchHints, + null ) // Clone the base route tree, and override the relevant fields with our @@ -827,8 +844,7 @@ export function deprecated_requestOptimisticRouteCacheEntry( status: EntryStatus.Fulfilled, // This isn't cloned because it's instance-specific blockedTasks: null, - tree: optimisticRouteTree, - metadata: optimisticMetadataTree, + root: createRootRouteTree(optimisticRouteTree, optimisticMetadataTree), couldBeIntercepted: routeWithNoSearchParams.couldBeIntercepted, supportsPerSegmentPrefetching: routeWithNoSearchParams.supportsPerSegmentPrefetching, @@ -1398,23 +1414,49 @@ function pingBlockedTasks(entry: { } } -export function createMetadataRouteTree( +/** + * The head's request key on the client. The server's own key for the head, + * HEAD_REQUEST_KEY, carries no path information: there is only one head per + * URL, so the server has no need to distinguish parallel pages. On the client + * the request key is the head's cache identity, so the head takes its page's + * request key with HEAD_REQUEST_KEY appended — the key the server would have + * assigned had the head been a segment below the page — and two pages' heads + * never share a key. The head varies on the same params as its page, so the + * rest of its vary path is the page's. + * The page must be the route's own: a page in a slot retained from another + * URL (one with a refresh state) belongs to that URL's head. When a route has + * multiple parallel pages of its own, the first one is used; the keys only + * differ in route groups and slot names, so any of them works as long as it + * is always the same one. + */ +export function getHeadRequestKey( + pageRequestKey: SegmentRequestKey +): SegmentRequestKey { + return (pageRequestKey + HEAD_REQUEST_KEY) as SegmentRequestKey +} + +export function createMetadataRouteTree( metadataVaryPath: VaryPath, // The route root's prefetch hints. The head has no node of its own on the - // wire, so route-level hints are read from the root on its behalf — the - // same convention as pingStaticHead in scheduler.ts. - rootPrefetchHints: number -): RouteTree { - // The Head is not actually part of the route tree, but other than that, it's - // fetched and cached like a segment. Some functions expect a RouteTree - // object, so rather than fork the logic in all those places, we use this - // "fake" one. - const metadata: RouteTree = { - requestKey: HEAD_REQUEST_KEY, + // wire, so the route-level hint that applies to it is copied from the root. + rootPrefetchHints: number, + // The head's payload, with the same lifetimes as a segment node's `data` + // (see RouteTree): null in the route cache, the response's decoded + // head on a navigation seed, a CacheNode on the router state. + data: TData +): RouteTree { + // The head is a one-node tree beside the route tree (see RootRouteTree). It + // has no position in the route tree, but it's fetched, cached, compared, and + // rendered like a segment, so it is a RouteTree node like any other. + const metadata: RouteTree = { + // The first entry of the head's vary path (see getHeadRequestKey). The + // server knows nothing of this key; it is always asked for + // HEAD_REQUEST_KEY, which is why the segment stays the bare marker. + requestKey: metadataVaryPath.value, segment: HEAD_REQUEST_KEY, shellVaryPath: getShellSegmentVaryPath(metadataVaryPath), refreshState: null, - data: null, + data, varyPath: metadataVaryPath, slots: null, // Only the static-attempt bits apply to the head: it's a route-level @@ -1481,21 +1523,18 @@ function stripDataFromRouteTree( export function fulfillRouteCacheEntry( now: number, entry: PendingRouteCacheEntry, - tree: RouteTree, - metadataVaryPath: VaryPath, + root: RootRouteTree, couldBeIntercepted: boolean, canonicalUrl: string, + renderedSearch: NormalizedSearch, supportsPerSegmentPrefetching: boolean ): FulfilledRouteCacheEntry { - // Get the rendered search from the vary path - const renderedSearch = - getRenderedSearchFromVaryPath(metadataVaryPath) ?? ('' as NormalizedSearch) + const tree = root.tree const fulfilledEntry: FulfilledRouteCacheEntry = entry as any fulfilledEntry.status = EntryStatus.Fulfilled - fulfilledEntry.tree = stripDataFromRouteTree(tree) - fulfilledEntry.metadata = createMetadataRouteTree( - metadataVaryPath, - tree.prefetchHints + fulfilledEntry.root = createRootRouteTree( + stripDataFromRouteTree(tree), + stripDataFromRouteTree(root.head) ) // Route structure is essentially static — it only changes on deploy. // Always use the static stale time. @@ -1525,20 +1564,20 @@ export function writeRouteIntoCache( pathname: NormalizedPathname, search: NormalizedSearch, nextUrl: string | null, - tree: RouteTree, - metadataVaryPath: VaryPath, + root: RootRouteTree, couldBeIntercepted: boolean, canonicalUrl: string, + renderedSearch: NormalizedSearch, supportsPerSegmentPrefetching: boolean ): FulfilledRouteCacheEntry { const pendingEntry = createDetachedRouteCacheEntry() const fulfilledEntry = fulfillRouteCacheEntry( now, pendingEntry, - tree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) const varyPath = getFulfilledRouteVaryPath( @@ -1702,9 +1741,9 @@ export function convertFlightRouterStateToRouteTree( requestKey, parentPartialVaryPath, renderedSearch, + refreshState, acc ) - tree.refreshState = refreshState const partialVaryPath = getPartialVaryPath(tree.varyPath) let slots: Map> | null = null @@ -1973,10 +2012,17 @@ export async function fetchRouteOnCacheMiss( search, nextUrl, entry, - routeTree, - metadataVaryPath, + { + tree: routeTree, + head: createMetadataRouteTree( + metadataVaryPath, + routeTree.prefetchHints, + null + ), + }, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching, false // hasDynamicRewrite ) @@ -2137,17 +2183,23 @@ async function fetchAndWritePerSegmentPrefetchResponse( const url = new URL(route.canonicalUrl, location.origin) const nextUrl = routeKey.nextUrl - const requestKey = tree.requestKey - const normalizedRequestKey = - requestKey === ROOT_SEGMENT_REQUEST_KEY - ? // The root segment is a special case. To simplify the server-side - // handling of these requests, we encode the root segment path as - // `_index` instead of as an empty string. This should be treated as - // an implementation detail and not as a stable part of the protocol. - // It just needs to match the equivalent logic that happens when - // prerendering the responses. It should not leak outside of Next.js. - ('/_index' as SegmentRequestKey) - : requestKey + let normalizedRequestKey: SegmentRequestKey + if (tree.segment === HEAD_REQUEST_KEY) { + // The head's request key is its client cache identity; the server only + // knows the head by its bare marker, which is also the head node's + // segment (see createMetadataRouteTree). + normalizedRequestKey = HEAD_REQUEST_KEY + } else if (tree.requestKey === ROOT_SEGMENT_REQUEST_KEY) { + // The root segment is a special case. To simplify the server-side + // handling of these requests, we encode the root segment path as + // `_index` instead of as an empty string. This should be treated as + // an implementation detail and not as a stable part of the protocol. + // It just needs to match the equivalent logic that happens when + // prerendering the responses. It should not leak outside of Next.js. + normalizedRequestKey = '/_index' as SegmentRequestKey + } else { + normalizedRequestKey = tree.requestKey + } const headers: RequestHeaders = { [RSC_HEADER]: '1', @@ -2313,10 +2365,12 @@ async function fetchAndWritePerSegmentPrefetchResponse( // response-level value — so the conservative value (true) is passed. // - The head is keyed at the route's own metadata vary path: the head has // no tree position, so the decode could only derive a vary path for it - // from a page node in the payload's own tree, which a standalone head - // response (a bare root identity) doesn't have. + // from a page node in the payload's own tree. A per-segment response + // carries only the spine from the root to the requested segment, so any + // response whose terminal isn't a page has no page node to key the head + // from; the standalone head response is one such case. const now = Date.now() - const metadataVaryPath = route.metadata.varyPath + const metadataVaryPath = route.root.head.varyPath writeResponsePayloadsIntoCache( now, fetchStrategy, @@ -2462,23 +2516,33 @@ export async function fetchSegmentPrefetchesUsingRuntimeRequest( | FetchStrategy.PPRRuntime | FetchStrategy.RuntimeShell | FetchStrategy.Full, - dynamicRequestTree: FlightRouterState, + requestTree: FlightRouterState, spawnedEntries: Map ): Promise | null> { const key = task.key const url = new URL(route.canonicalUrl, location.origin) const nextUrl = key.nextUrl + // When the request tree was derived from a predicted route entry, pass the + // node it was predicted from to the write path so the prediction can be + // disabled if the server's rendered tree diverges from it. For an entry + // the server resolved this is null: a divergence from it says nothing + // about route prediction, and its unfulfilled entries take the usual + // backoff. + let dynamicRequestTree: FlightRouterState + let predictedFrom: KnownRoutePart | null if ( spawnedEntries.size === 1 && - spawnedEntries.has(route.metadata.requestKey) + spawnedEntries.has(route.root.head.requestKey) ) { - // The only thing pending is the head. Instruct the server to - // skip over everything else. - // TODO: Lift this logic into the caller. Or perhaps unify the - // "request tree" and the spawnedEntries into the same type so they are - // guaranteed to always been in sync. + // Only the head is pending, so ask the server for metadata only: it skips + // the segments and renders just the head. The stub is not derived from + // the route entry, so divergence from it carries no signal. dynamicRequestTree = MetadataOnlyRequestTree + predictedFrom = null + } else { + dynamicRequestTree = requestTree + predictedFrom = route.predictedFrom } const headers: RequestHeaders = { @@ -2598,21 +2662,6 @@ export async function fetchSegmentPrefetchesUsingRuntimeRequest( const buildId = response.headers.get(NEXT_NAV_DEPLOYMENT_ID_HEADER) ?? serverData.b - // When the request tree was derived from a predicted route entry, pass the - // node it was predicted from to the write path so the prediction can be - // disabled if the server's rendered tree diverges from it. For an entry - // the server resolved this is null: a divergence from it says nothing - // about route prediction, and its unfulfilled entries take the usual - // backoff. A head-only request uses the MetadataOnlyRequestTree stub - // rather than a tree derived from the route entry, so divergence from it - // carries no signal. - // TODO: This special case goes away once the response is diffed against - // the base RouteTree (route.tree) instead of the request tree. - const predictedFrom = - dynamicRequestTree !== MetadataOnlyRequestTree - ? route.predictedFrom - : null - // Extract the response's shell-stage payload, when it carries one. No // shell can be extracted without cache metadata (only present when // Cached Navigations is enabled); for responses without a distinct @@ -3040,9 +3089,7 @@ function writeServerResponseIntoCache( // partiality per node, so their writes pass the conservative value // (true), which is never read. isResponsePartial: boolean, - // Where to key the head. Null derives it from the decoded tree's first - // page node; per-segment payloads pass the route's own metadata vary path - // instead, since a standalone head response's tree has no page node. + // Where to key the head; see createNavigationSeed. metadataVaryPath: VaryPath | null, spawnedEntries: Map | null, // The strategy tier describing the CONTENT of the payload being written, @@ -3106,12 +3153,13 @@ function writeServerResponseIntoCache( isResponsePartial, renderedPathname, renderedSearch, + metadataVaryPath, // Only navigations consume the seed's dynamicStaleAt; cache writes pass // unknown to use the default. UnknownDynamicStaleTime ) const requiresRuntimeCompleteness = - (navigationSeed.routeTree.prefetchHints & + (navigationSeed.root.tree.prefetchHints & PrefetchHint.SubtreeHasPartialPrefetching) !== 0 @@ -3145,17 +3193,7 @@ function writeServerResponseIntoCache( ? readFulfilledValue(response.u, false, /* rejectedValue */ true) : null - const routeTree = navigationSeed.routeTree - if (metadataVaryPath === null) { - metadataVaryPath = navigationSeed.metadataVaryPath - } - const metadataTree = - metadataVaryPath !== null - ? createMetadataRouteTree( - metadataVaryPath, - navigationSeed.routeTree.prefetchHints - ) - : null + const routeTree = navigationSeed.root.tree // The route tree carries the render output of every segment the response // included, so a single traversal from the root writes all of it into @@ -3175,13 +3213,13 @@ function writeServerResponseIntoCache( writtenEntries ) - const head = navigationSeed.head - if (head !== null && metadataTree !== null) { - // The head carries its own staleTime in per-segment prefetch responses; - // everywhere else the response-level staleness governs it. + const metadataTree = navigationSeed.root.head + const headData = metadataTree.data + if (headData !== null && headData.rsc !== null) { + // The head follows the same stale-time rules as a segment. const headStaleAt = - navigationSeed.headStaleTimeSeconds !== null - ? now + getStaleTimeMs(navigationSeed.headStaleTimeSeconds) + headData.staleTimeSeconds !== null + ? now + getStaleTimeMs(headData.staleTimeSeconds) : staleAt // A head has no loading boundary. Match pingRuntimeHead, which spawns @@ -3194,13 +3232,13 @@ function writeServerResponseIntoCache( now, map, headFetchStrategy, - head, + headData.rsc, // The decode already resolved the head's partiality from the wire // form and the response-level value — see the head read in // createNavigationSeed. - navigationSeed.isHeadPartial, + headData.isPartial, headStaleAt, - navigationSeed.headVaryParams, + headData.varyParams, metadataTree, spawnedEntries, contentFetchStrategy, diff --git a/packages/next/src/client/components/segment-cache/decode-server-response.ts b/packages/next/src/client/components/segment-cache/decode-server-response.ts index 3fa7a383eadc..d9d02ea30f7e 100644 --- a/packages/next/src/client/components/segment-cache/decode-server-response.ts +++ b/packages/next/src/client/components/segment-cache/decode-server-response.ts @@ -7,7 +7,6 @@ import type { FlightRouterState, - HeadData, Segment as FlightRouterStateSegment, } from '../../../shared/lib/app-router-types' import { @@ -21,10 +20,7 @@ import type { TransportSegment, } from '../../../shared/lib/rsc-transport' import { readFulfilledValue } from '../../../shared/lib/rsc-transport' -import type { - VaryParams, - VaryParamsIterable, -} from '../../../shared/lib/segment-cache/vary-params-decoding' +import type { VaryParamsIterable } from '../../../shared/lib/segment-cache/vary-params-decoding' import { decodeVaryParams } from '../../../shared/lib/segment-cache/vary-params-decoding' import { type SegmentRequestKey, @@ -48,39 +44,31 @@ import { splitPathnameIntoParts } from './cache-key' import type { PartialVaryPath, VaryPath } from './vary-path' import { appendLayoutVaryPath, - finalizeMetadataVaryPath, finalizeVaryPath, getPartialVaryPath, getShellSegmentVaryPath, } from './vary-path' import { type RouteTree, + type RootRouteTree, type RSCSegmentData, type RefreshState, type RouteTreeAccumulator, convertFlightRouterStateToRouteTree, convertRootFlightRouterStateToRouteTree, + createMetadataRouteTree, + createRootRouteTree, + getHeadRequestKey, } from './cache' import { computeDynamicStaleAt } from './bfcache' export type NavigationSeed = { - renderedSearch: string - routeTree: RouteTree - metadataVaryPath: VaryPath | null - head: HeadData | null - isHeadPartial: boolean - /** - * The source of the params the head's output depends on (root params - * included). Null means unknown — tracking wasn't enabled, or the decode - * had no root params to union in — so consumers key on all params. - */ - headVaryParams: VaryParams | null + renderedSearch: NormalizedSearch /** - * The head's own staleTime in seconds, when the response carries one - * (per-segment prefetch responses only — see TransportSegmentData['s']). - * Null means the response-level staleness governs the head. + * The decoded response. The head's `data` is decoded exactly like a segment + * node's: null when the response carries no head. */ - headStaleTimeSeconds: number | null + root: RootRouteTree dynamicStaleAt: number // Whether the response rendered a segment whose identity differs from the // base tree's at the same position (inactive parallel route branches are @@ -137,18 +125,23 @@ export function createNavigationSeed( // decodeTransportTreeIntoRouteTree. Callers whose responses always carry // concrete values (navigation responses) may pass null. renderedPathname: string | null, + // Already normalized by the response reader (see getRenderedSearch); the + // router state stores it as a plain string, so it is re-branded here. renderedSearch: string, + // Where to key the head. Null derives it from the route's own first page + // node (see createRouteTreeNode). Per-segment prefetch payloads pass the + // route's own metadata vary path instead: a standalone head response's tree + // is a bare root identity with no page node. + metadataVaryPath: VaryPath | null, dynamicStaleTimeSeconds: number ): NavigationSeed { + const normalizedRenderedSearch = renderedSearch as NormalizedSearch const acc: RouteTreeAccumulator = { metadataVaryPath: null, treeDivergedFromBase: false, } let routeTree: RouteTree - let head: HeadData | null = null - let isHeadPartial = true - let headVaryParams: VaryParams | null = null - let headStaleTimeSeconds: number | null = null + let headData: RSCSegmentData | null = null if (transportData !== null) { routeTree = decodeTransportTreeIntoRouteTree( transportData.t, @@ -156,12 +149,11 @@ export function createNavigationSeed( rootVaryParams, isResponsePartial, renderedPathname, - renderedSearch as NormalizedSearch, + normalizedRenderedSearch, acc ) const transportHead = transportData.h if (transportHead !== undefined) { - head = transportHead.r // The wire form of `p` determines which signal is authoritative for // the head's partiality, mirroring the per-node rule in // decodeTransportNode: @@ -183,17 +175,20 @@ export function createNavigationSeed( // carries a complete head; a partial (postponed) one does not. // Without Cache Components, the server sends the correct // isHeadPartial, so the wire boolean is used as-is. - isHeadPartial = - typeof transportHead.p === 'boolean' - ? process.env.__NEXT_CACHE_COMPONENTS - ? isResponsePartial - : transportHead.p - : readFulfilledIsPartial(transportHead.p) - headVaryParams = decodeVaryParams(transportHead.v, rootVaryParams) - headStaleTimeSeconds = - transportHead.s !== undefined - ? readFulfilledStaleTimeSeconds(transportHead.s) - : null + headData = { + rsc: transportHead.r, + isPartial: + typeof transportHead.p === 'boolean' + ? process.env.__NEXT_CACHE_COMPONENTS + ? isResponsePartial + : transportHead.p + : readFulfilledIsPartial(transportHead.p), + varyParams: decodeVaryParams(transportHead.v, rootVaryParams), + staleTimeSeconds: + transportHead.s !== undefined + ? readFulfilledStaleTimeSeconds(transportHead.s) + : null, + } } } else { if (currentTree === null) { @@ -204,19 +199,33 @@ export function createNavigationSeed( } routeTree = convertRootFlightRouterStateToRouteTree( currentTree, - renderedSearch as NormalizedSearch, + normalizedRenderedSearch, acc ) } + if (metadataVaryPath === null) { + metadataVaryPath = acc.metadataVaryPath + if (metadataVaryPath === null) { + // Every route renders a page, so a rendered tree always has a node to + // key the head under. + throw new InvariantError( + 'Cannot key the head of a server response: its tree has no page ' + + 'segment.' + ) + } + } + return { - routeTree, - metadataVaryPath: acc.metadataVaryPath, - renderedSearch, - head, - isHeadPartial, - headVaryParams, - headStaleTimeSeconds, + root: createRootRouteTree( + routeTree, + createMetadataRouteTree( + metadataVaryPath, + routeTree.prefetchHints, + headData + ) + ), + renderedSearch: normalizedRenderedSearch, dynamicStaleAt: computeDynamicStaleAt(now, dynamicStaleTimeSeconds), treeDivergedFromBase: acc.treeDivergedFromBase, } @@ -224,7 +233,7 @@ export function createNavigationSeed( /** * Creates a RouteTree node for a segment, with its identity and cache-key - * information (vary paths, the normalized segment value) + * information (vary paths, the normalized segment value, the refresh state) * initialized, and the remaining fields set to their defaults. The caller * finishes initializing those in place after recursing into the children. * Shared by FlightRouterState conversion, transport decoding, and subtree @@ -236,6 +245,7 @@ export function createRouteTreeNode( requestKey: SegmentRequestKey, parentPartialVaryPath: PartialVaryPath | null, renderedSearch: NormalizedSearch, + refreshState: RefreshState | null, acc: RouteTreeAccumulator ): RouteTree { let segment: FlightRouterStateSegment @@ -270,15 +280,12 @@ export function createRouteTreeNode( // them entirely on the client, similar to our plan for dynamic params. segment = PAGE_SEGMENT_KEY varyPath = finalizeVaryPath(requestKey, renderedSearch, partialVaryPath) - // The metadata "segment" is not part the route tree, but it has the same - // conceptual params as a page segment. Write the vary path into the - // accumulator object. If there are multiple parallel pages, we use the - // first one. Which page we choose is arbitrary as long as it's - // consistently the same one every time every time. See - // finalizeMetadataVaryPath for more details. - if (acc.metadataVaryPath === null) { - acc.metadataVaryPath = finalizeMetadataVaryPath( - requestKey, + // The head is keyed under the route's own first page and varies on the + // same params as that page (see getHeadRequestKey). A page reused from + // another URL carries a refresh state and never keys it. + if (refreshState === null && acc.metadataVaryPath === null) { + acc.metadataVaryPath = finalizeVaryPath( + getHeadRequestKey(requestKey), renderedSearch, partialVaryPath ) @@ -293,7 +300,7 @@ export function createRouteTreeNode( requestKey, segment, shellVaryPath: getShellSegmentVaryPath(varyPath), - refreshState: null, + refreshState, data: null, varyPath, slots: null, @@ -492,9 +499,9 @@ function decodeTransportNode( requestKey, parentPartialVaryPath, renderedSearch, + refreshState, acc ) - tree.refreshState = refreshState const partialVaryPath = getPartialVaryPath(tree.varyPath) let slots: Map> | null = null diff --git a/packages/next/src/client/components/segment-cache/optimistic-routes.ts b/packages/next/src/client/components/segment-cache/optimistic-routes.ts index 4a5cc077d6e6..74ea32179e3b 100644 --- a/packages/next/src/client/components/segment-cache/optimistic-routes.ts +++ b/packages/next/src/client/components/segment-cache/optimistic-routes.ts @@ -48,6 +48,7 @@ import { PrefetchHint } from '../../../shared/lib/app-router-types' import { PAGE_SEGMENT_KEY } from '../../../shared/lib/segment' import type { RouteTree, + RootRouteTree, RSCSegmentData, FulfilledRouteCacheEntry, } from './cache' @@ -58,6 +59,8 @@ import { getCurrentRouteCacheVersion, type PendingRouteCacheEntry, createMetadataRouteTree, + createRootRouteTree, + getHeadRequestKey, } from './cache' import { isValueExpired } from './cache-map' import { @@ -68,7 +71,6 @@ import type { NormalizedPathname, NormalizedSearch } from './cache-key' import { splitPathnameIntoParts } from './cache-key' import { appendLayoutVaryPath, - finalizeMetadataVaryPath, finalizeVaryPath, getShellSegmentVaryPath, type PartialVaryPath, @@ -238,14 +240,14 @@ export function discoverKnownRoute( search: NormalizedSearch, nextUrl: string | null, pendingEntry: PendingRouteCacheEntry | null, - routeTree: RouteTree, - metadataVaryPath: VaryPath, + root: RootRouteTree, couldBeIntercepted: boolean, canonicalUrl: string, + renderedSearch: NormalizedSearch, supportsPerSegmentPrefetching: boolean, hasDynamicRewrite: boolean ): FulfilledRouteCacheEntry { - const tree = routeTree + const tree = root.tree const pathnameParts = splitPathnameIntoParts(pathname) @@ -254,10 +256,10 @@ export function discoverKnownRoute( const fulfilledEntry = fulfillRouteCacheEntry( now, pendingEntry, - tree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) // Populate the known route tree (handles rewrite detection internally). @@ -273,10 +275,10 @@ export function discoverKnownRoute( pathname, search, nextUrl, - tree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching, hasDynamicRewrite ) @@ -295,10 +297,10 @@ export function discoverKnownRoute( pathname, search, nextUrl, - tree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching, hasDynamicRewrite ) @@ -316,10 +318,10 @@ function handleMismatchDueToRewrite( pathname: string, search: NormalizedSearch, nextUrl: string | null, - fullTree: RouteTree, - metadataVaryPath: VaryPath, + root: RootRouteTree, couldBeIntercepted: boolean, canonicalUrl: string, + renderedSearch: NormalizedSearch, supportsPerSegmentPrefetching: boolean ): FulfilledRouteCacheEntry { if (existingEntry !== null) { @@ -330,10 +332,10 @@ function handleMismatchDueToRewrite( pathname as NormalizedPathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -388,10 +390,10 @@ function discoverKnownRoutePart( pathname: string, search: NormalizedSearch, nextUrl: string | null, - fullTree: RouteTree, - metadataVaryPath: VaryPath, + root: RootRouteTree, couldBeIntercepted: boolean, canonicalUrl: string, + renderedSearch: NormalizedSearch, supportsPerSegmentPrefetching: boolean, hasDynamicRewrite: boolean ): FulfilledRouteCacheEntry { @@ -415,10 +417,10 @@ function discoverKnownRoutePart( pathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -456,10 +458,10 @@ function discoverKnownRoutePart( pathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -477,10 +479,10 @@ function discoverKnownRoutePart( pathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -506,10 +508,10 @@ function discoverKnownRoutePart( pathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -532,10 +534,10 @@ function discoverKnownRoutePart( pathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -573,10 +575,10 @@ function discoverKnownRoutePart( pathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -640,10 +642,10 @@ function discoverKnownRoutePart( pathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching, hasDynamicRewrite ) @@ -662,10 +664,10 @@ function discoverKnownRoutePart( pathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -680,10 +682,10 @@ function discoverKnownRoutePart( pathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -713,10 +715,10 @@ function discoverKnownRoutePart( pathname as NormalizedPathname, search, nextUrl, - fullTree, - metadataVaryPath, + root, couldBeIntercepted, canonicalUrl, + renderedSearch, supportsPerSegmentPrefetching ) } @@ -774,7 +776,7 @@ export function matchKnownRoute( // components read them. Resolve the target URL on the server instead of // combining a predicted tree with UI cached for an allowed parameter value. if ( - pattern.tree.prefetchHints & + pattern.root.tree.prefetchHints & (PrefetchHint.IsClosedParam | PrefetchHint.SubtreeHasClosedParams) ) { return null @@ -785,7 +787,7 @@ export function matchKnownRoute( // segments and recomputes vary paths for correct segment cache keying. const acc: ReifyAccumulator = { metadataVaryPath: null } const reifiedTree = reifyRouteTree( - pattern.tree, + pattern.root.tree, resolvedParams, search, null, // Start with null partial vary path at the root @@ -802,7 +804,8 @@ export function matchKnownRoute( } const reifiedMetadata = createMetadataRouteTree( metadataVaryPath, - reifiedTree.prefetchHints + reifiedTree.prefetchHints, + null ) // Create a synthetic (predicted) entry. It's not inserted into the route @@ -815,8 +818,7 @@ export function matchKnownRoute( canonicalUrl: pathname + search, status: EntryStatus.Fulfilled, blockedTasks: null, - tree: reifiedTree, - metadata: reifiedMetadata, + root: createRootRouteTree(reifiedTree, reifiedMetadata), couldBeIntercepted: pattern.couldBeIntercepted, supportsPerSegmentPrefetching: pattern.supportsPerSegmentPrefetching, predictedFrom: matchedPart, @@ -1091,10 +1093,10 @@ function reifyRouteTree( if (originalSegment === PAGE_SEGMENT_KEY) { // Page segment: finalize with search params newVaryPath = finalizeVaryPath(pattern.requestKey, search, partialVaryPath) - // Collect metadata vary path (first page wins, same as original algorithm) + // Collect metadata vary path (first page wins; see getHeadRequestKey) if (acc.metadataVaryPath === null) { - acc.metadataVaryPath = finalizeMetadataVaryPath( - pattern.requestKey, + acc.metadataVaryPath = finalizeVaryPath( + getHeadRequestKey(pattern.requestKey), search, partialVaryPath ) diff --git a/packages/next/src/client/components/segment-cache/scheduler.ts b/packages/next/src/client/components/segment-cache/scheduler.ts index aec472fe958f..838e599b89e2 100644 --- a/packages/next/src/client/components/segment-cache/scheduler.ts +++ b/packages/next/src/client/components/segment-cache/scheduler.ts @@ -18,6 +18,7 @@ import { type FulfilledRouteCacheEntry, type RouteCacheEntry, type RouteTree, + type RootRouteTree, fetchSegmentPrefetchesUsingRuntimeRequest, type PendingSegmentCacheEntry, type SegmentCacheEntry, @@ -44,7 +45,10 @@ import { import type { CacheMap } from './cache-map' import type { NavigationLockPrefetch } from './navigation-testing-lock' import { PAGE_SEGMENT_KEY } from '../../../shared/lib/segment' -import type { SegmentRequestKey } from '../../../shared/lib/segment-cache/segment-value-encoding' +import { + HEAD_REQUEST_KEY, + type SegmentRequestKey, +} from '../../../shared/lib/segment-cache/segment-value-encoding' import { cleanup } from './lru' const scheduleMicrotask = @@ -62,8 +66,12 @@ const scheduleMicrotask = export type PrefetchTask = { key: RouteCacheKey - // The active render tree when this task was scheduled. - renderTreeAtTimeOfPrefetch: RouteTree + // The active render tree and head when this task was scheduled. The walks + // compare the target route against these to decide which segments the + // navigation would keep and which it would fetch. Compared by identity in + // isPrefetchTaskDirty, which relies on the router state holding one + // RootRouteTree object per committed navigation (see AppRouterState.root). + renderTreeAtTimeOfPrefetch: RootRouteTree /** * The cache versions at the time the task was initiated. Used to determine @@ -300,7 +308,8 @@ export type IncludeDynamicData = null | 'full' | 'dynamic' * expected to be validated and normalized. * * @param key The RouteCacheKey to prefetch. - * @param renderTreeAtTimeOfPrefetch The active render tree and its vary paths + * @param renderTreeAtTimeOfPrefetch The active render tree and head, and + * their vary paths * @param fetchStrategy Whether to prefetch dynamic data, in addition to * static data. This is used by ``. * @param navigationLockPrefetch Testing API only. Non-null when this prefetch @@ -309,7 +318,7 @@ export type IncludeDynamicData = null | 'full' | 'dynamic' */ export function schedulePrefetchTask( key: RouteCacheKey, - renderTreeAtTimeOfPrefetch: RouteTree, + renderTreeAtTimeOfPrefetch: RootRouteTree, fetchStrategy: PrefetchTaskFetchStrategy, priority: PrefetchPriority, onInvalidate: null | (() => void), @@ -383,7 +392,7 @@ export function cancelPrefetchTask(task: PrefetchTask): void { export function reschedulePrefetchTask( task: PrefetchTask, - renderTreeAtTimeOfPrefetch: RouteTree, + renderTreeAtTimeOfPrefetch: RootRouteTree, fetchStrategy: PrefetchTaskFetchStrategy, priority: PrefetchPriority ): void { @@ -428,7 +437,7 @@ export function reschedulePrefetchTask( export function isPrefetchTaskDirty( task: PrefetchTask, nextUrl: string | null, - cache: RouteTree + root: RootRouteTree ): boolean { // This is used to quickly bail out of a prefetch task if the result is // guaranteed to not have changed since the task was initiated. This is @@ -438,7 +447,7 @@ export function isPrefetchTaskDirty( return ( task.routeCacheVersion !== getCurrentRouteCacheVersion() || task.segmentCacheVersion !== getCurrentSegmentCacheVersion() || - task.renderTreeAtTimeOfPrefetch !== cache || + task.renderTreeAtTimeOfPrefetch !== root || task.key.nextUrl !== nextUrl ) } @@ -626,7 +635,7 @@ function processQueueInMicrotask() { const routeHasPartialPrefetching = route !== null && route.status === EntryStatus.Fulfilled && - (route.tree.prefetchHints & + (route.root.tree.prefetchHints & PrefetchHint.SubtreeHasPartialPrefetching) !== 0 task.phase = routeHasPartialPrefetching @@ -846,7 +855,7 @@ function pingRootRouteTree( // Stop prefetching segments until there's more bandwidth. return PrefetchTaskExitStatus.InProgress } - const tree = route.tree + const tree = route.root.tree // A task's fetch strategy gets set to `PPR` for any "auto" prefetch. // If it turned out that the route isn't PPR-enabled, we need to use `LoadingBoundary` instead. @@ -903,7 +912,7 @@ function pingRootRouteTree( staticWalkStrategy === FetchStrategy.PPR && !needsSpeculativePrefetch( task.fetchStrategy, - route.tree.prefetchHints + route.root.tree.prefetchHints ) ) { return PrefetchTaskExitStatus.Done @@ -915,7 +924,7 @@ function pingRootRouteTree( now, task, route, - task.renderTreeAtTimeOfPrefetch, + task.renderTreeAtTimeOfPrefetch.tree, tree, null, staticWalkStrategy @@ -999,7 +1008,7 @@ function pingRootRouteTree( now, task, route, - task.renderTreeAtTimeOfPrefetch, + task.renderTreeAtTimeOfPrefetch.tree, tree, spawnedEntries, fetchStrategy @@ -1061,7 +1070,7 @@ type SegmentBundle = { * path either when it requires runtime completeness and no static attempt is * happening, or when a fulfilled static head entry reported that a runtime * request would return more content than the entry contains. Deopting - * registers the head under its metadata request key, which makes the runtime + * registers the head under its wire request key, which makes the runtime * gate in pingRootRouteTree fire even when every tree segment was * sufficient; pingRuntimeHead performs the actual head work. */ @@ -1082,10 +1091,10 @@ function pingStaticHead( // The head is not a tree node — it hangs off the route root — so the // static-attempt hints are read from the root's node. (Segments read the // hints from their own node; see `pingNewPartOfCacheComponentsTree.`) - !shouldSegmentAttemptStaticRequest(fetchStrategy, route.tree) + !shouldSegmentAttemptStaticRequest(fetchStrategy, route.root.tree) ) { // No static attempt: the head arrives via the runtime request instead. - addSpawnedRuntimePrefetch(task, route.metadata.requestKey) + addSpawnedRuntimePrefetch(task, HEAD_REQUEST_KEY) return } @@ -1095,18 +1104,18 @@ function pingStaticHead( // as part of that page's response, and its runtime-completeness signal // is carried by that page's own entries. process.env.__NEXT_PREFETCH_INLINING && - !(route.tree.prefetchHints & PrefetchHint.HeadOutlined) + !(route.root.tree.prefetchHints & PrefetchHint.HeadOutlined) ) { return } const segments: SegmentBundle = { - tree: route.metadata, + tree: route.root.head, entry: readOrCreateSegmentCacheEntry( now, task.segmentCacheMap, fetchStrategy, - route.metadata + route.root.head ), parent: null, } @@ -1115,7 +1124,7 @@ function pingStaticHead( task, route, task.key, - route.metadata, + route.root.head, segments, fetchStrategy, true @@ -1125,7 +1134,7 @@ function pingStaticHead( // runtime prefetch. (Outside of runtime-completeness contexts the // head's signal is unused — a partial static head is filled in by the // navigation-time request, as with any other static segment.) - addSpawnedRuntimePrefetch(task, route.metadata.requestKey) + addSpawnedRuntimePrefetch(task, HEAD_REQUEST_KEY) } } @@ -1155,7 +1164,9 @@ function walkCanUseRuntimeRequests( } // `FetchStrategy.PPR` can only use runtime requests if PPF is enabled on the route. return ( - (route.tree.prefetchHints & PrefetchHint.SubtreeHasPartialPrefetching) !== 0 + (route.root.tree.prefetchHints & + PrefetchHint.SubtreeHasPartialPrefetching) !== + 0 ) } @@ -1270,7 +1281,7 @@ function isShellEntryEligibleForStaticAttempt( } /** - * Register a subtree root (or the head's metadata key) for the batched + * Register a subtree root (or the head's wire key) for the batched * runtime request issued by the gate at the end of pingRootRouteTree. */ function addSpawnedRuntimePrefetch( @@ -1299,7 +1310,7 @@ function pingRuntimeHead( now, task, route, - route.metadata, + route.root.head, false, spawnedEntries, // When prefetching the head, there's no difference between Full @@ -1455,7 +1466,7 @@ function pingNewPartOfCacheComponentsTree( // In PPF, links may skip speculative prefetching if they only need a shell. if ( fetchStrategy === FetchStrategy.PPR && - !needsSpeculativePrefetch(task.fetchStrategy, route.tree.prefetchHints) + !needsSpeculativePrefetch(task.fetchStrategy, route.root.tree.prefetchHints) ) { return PrefetchTaskExitStatus.Done } @@ -2453,12 +2464,12 @@ function accumulateSegmentBundle( tree.prefetchHints & PrefetchHint.HeadInlinedIntoSelf ) { effectiveParent = { - tree: route.metadata, + tree: route.root.head, entry: readOrCreateSegmentCacheEntry( now, task.segmentCacheMap, fetchStrategy, - route.metadata + route.root.head ), parent: parentBundle, } diff --git a/packages/next/src/client/components/segment-cache/vary-path.ts b/packages/next/src/client/components/segment-cache/vary-path.ts index 093a37845e91..619abf0c2a25 100644 --- a/packages/next/src/client/components/segment-cache/vary-path.ts +++ b/packages/next/src/client/components/segment-cache/vary-path.ts @@ -6,10 +6,7 @@ import type { } from './cache-key' import type { RouteTree } from './cache' import { Fallback, type FallbackType } from './cache-map' -import { - HEAD_REQUEST_KEY, - type SegmentRequestKey, -} from '../../../shared/lib/segment-cache/segment-value-encoding' +import type { SegmentRequestKey } from '../../../shared/lib/segment-cache/segment-value-encoding' import { SEARCH_PARAMS_VARY_ID, type VaryParamId, @@ -210,47 +207,6 @@ export function getPartialVaryPath( return parent as PartialVaryPath | null } -export function finalizeMetadataVaryPath( - pageRequestKey: SegmentRequestKey, - renderedSearch: NormalizedSearch, - varyPath: PartialVaryPath | null -): VaryPath { - // The metadata "segment" is not a real segment because it doesn't exist in - // the normal structure of the route tree, but in terms of caching, it - // behaves like a page segment because it varies by all the same params as - // a page. - // - // To keep the protocol for querying the server simple, the request key for - // the metadata does not include any path information. It's unnecessary from - // the server's perspective, because unlike page segments, there's only one - // metadata response per URL, i.e. there's no need to distinguish multiple - // parallel pages. - // - // However, this means the metadata request key is insufficient for - // caching the the metadata in the client cache, because on the client we - // use the request key to distinguish the metadata entry from all other - // page's metadata entries. - // - // So instead we create a simulated request key based on the page segment. - // Conceptually this is equivalent to the request key the server would have - // assigned the metadata segment if it treated it as part of the actual - // route structure. - - // If there are multiple parallel pages, we use whichever is the first one. - // This is fine because the only difference between request keys for - // different parallel pages are things like route groups and parallel - // route slots. As long as it's always the same one, it doesn't matter. - // - // Append the actual metadata request key to the page request key. Note - // that we're not using a separate vary path part; it's unnecessary because - // these are not conceptually separate inputs. - return finalizeVaryPath( - (pageRequestKey + HEAD_REQUEST_KEY) as SegmentRequestKey, - renderedSearch, - varyPath - ) -} - export function getSegmentVaryPathForRequest( fetchStrategy: FetchStrategy, tree: RouteTree diff --git a/packages/next/src/shared/lib/app-router-types.ts b/packages/next/src/shared/lib/app-router-types.ts index 19aebfdae9c5..28613c1790bb 100644 --- a/packages/next/src/shared/lib/app-router-types.ts +++ b/packages/next/src/shared/lib/app-router-types.ts @@ -57,10 +57,6 @@ export type CacheNode = { */ varyParams: VaryParams | null - prefetchHead: HeadData | null - - head: HeadData - /** * A shared mutable ref that tracks whether this segment should be scrolled * to. All new segments created during a single navigation share the same diff --git a/test/e2e/app-dir/segment-cache/metadata/app/page-with-per-tenant-head/[locale]/[tenant]/page.tsx b/test/e2e/app-dir/segment-cache/metadata/app/page-with-per-tenant-head/[locale]/[tenant]/page.tsx new file mode 100644 index 000000000000..e42e2cbd1305 --- /dev/null +++ b/test/e2e/app-dir/segment-cache/metadata/app/page-with-per-tenant-head/[locale]/[tenant]/page.tsx @@ -0,0 +1,30 @@ +import { Metadata } from 'next' + +type Params = { locale: string; tenant: string } + +// Only one tenant is prerendered. Any other tenant is rendered on demand, so +// its head is missing from the cache while the body, which reads only the +// locale, can be reused from the prerendered one. +export function generateStaticParams(): Params[] { + return [{ locale: 'en', tenant: 'acme' }] +} + +export async function generateMetadata({ + params, +}: { + params: Promise +}): Promise { + const { tenant } = await params + return { + title: `Tenant: ${tenant}`, + } +} + +export default async function PageWithPerTenantHead({ + params, +}: { + params: Promise +}) { + const { locale } = await params + return
{`Locale: ${locale}`}
+} diff --git a/test/e2e/app-dir/segment-cache/metadata/app/page.tsx b/test/e2e/app-dir/segment-cache/metadata/app/page.tsx index 9841c98fcd7d..57533bd984dd 100644 --- a/test/e2e/app-dir/segment-cache/metadata/app/page.tsx +++ b/test/e2e/app-dir/segment-cache/metadata/app/page.tsx @@ -37,6 +37,22 @@ export default function Page() { +
+
    +
  • + + Page with per-tenant head (en/acme) + +
  • +
  • + + Page with per-tenant head (en/initech, prefetch=false) + +
  • +
) } diff --git a/test/e2e/app-dir/segment-cache/metadata/segment-cache-metadata.test.ts b/test/e2e/app-dir/segment-cache/metadata/segment-cache-metadata.test.ts index 8d34b1a05001..d4973acbca7f 100644 --- a/test/e2e/app-dir/segment-cache/metadata/segment-cache-metadata.test.ts +++ b/test/e2e/app-dir/segment-cache/metadata/segment-cache-metadata.test.ts @@ -1,5 +1,19 @@ import { nextTestSetup } from 'e2e-utils' +import { retry } from 'next-test-utils' import { createRouterAct } from 'router-act' +import type * as Playwright from 'playwright' + +const NEXT_ROUTER_STATE_TREE_HEADER = 'next-router-state-tree' +const NEXT_ROUTER_PREFETCH_HEADER = 'next-router-prefetch' + +// The request tree the client sends when every segment is cached and only the +// head is missing. Mirrors MetadataOnlyRequestTree in the segment cache. +const METADATA_ONLY_REQUEST_TREE = JSON.stringify([ + '', + {}, + null, + 'metadata-only', +]) describe('segment cache (metadata)', () => { const { next, isNextDev } = nextTestSetup({ @@ -129,4 +143,72 @@ describe('segment cache (metadata)', () => { }, 'no-requests') }) }) + + // On the platform a cold ISR path is served from a completed static + // prerender that cannot vary on the request tree, so the page body is sent + // along with the head. + // @gate !deploy + it('requests only the head when the page segments are cached', async () => { + let act: ReturnType + // The request tree of every navigation request (RSC requests that are + // not prefetches), decoded from its header. + const navigationRequestTrees: Array = [] + const browser = await next.browser('/', { + beforePageLoad(p: Playwright.Page) { + act = createRouterAct(p) + p.on('request', (request) => { + const headers = request.headers() + if ( + headers['rsc'] === '1' && + headers[NEXT_ROUTER_PREFETCH_HEADER] === undefined + ) { + const encoded = headers[NEXT_ROUTER_STATE_TREE_HEADER] + navigationRequestTrees.push( + encoded === undefined ? null : decodeURIComponent(encoded) + ) + } + }) + }, + }) + + // Prefetch the prerendered tenant. The body reads only the locale, so + // this caches it for every tenant in "en"; the head reads the tenant, so + // only acme's head is cached. + await act(async () => { + const checkbox = await browser.elementByCss( + 'input[data-link-accordion="/page-with-per-tenant-head/en/acme"]' + ) + await checkbox.click() + }, [{ includes: 'Locale: en' }, { includes: 'Tenant: acme' }]) + + // Reveal a link to a tenant that is not prerendered, without prefetching + // it. Its head is the only thing missing from the cache. + await act(async () => { + const checkbox = await browser.elementByCss( + 'input[data-link-accordion="/page-with-per-tenant-head/en/initech"]' + ) + await checkbox.click() + }, 'no-requests') + + // The navigation asks the server for the head alone. The response carries + // the new tenant's title and no page body. + await act(async () => { + const link = await browser.elementByCss( + 'a[href="/page-with-per-tenant-head/en/initech"]' + ) + await link.click() + }, [ + { includes: 'Tenant: initech' }, + { includes: 'Locale: en', block: 'reject' }, + ]) + expect(navigationRequestTrees).toEqual([METADATA_ONLY_REQUEST_TREE]) + + // The body is the cached one, rendered under the new tenant's title. + const pageContent = await browser.elementById('target-page') + expect(await pageContent.text()).toBe('Locale: en') + await retry(async () => { + const title = await browser.eval(() => document.title) + expect(title).toBe('Tenant: initech') + }) + }) }) From b3512c2ea3d672ec07bf5568bbf126786a13241a Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 25 Sep 2026 00:25:28 -0400 Subject: [PATCH 10/13] Separate route structure comparison from param comparison (#98974) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changing a page’s search params doesn’t change which page it is, but it may change the data that page needs. The router currently mixes these concerns by appending search params to `__PAGE__` segments. Remove that encoding so we can compare which route is being rendered separately from the param values used to render it. Param values are now compared through the existing VaryPath data structure, while route structure is compared during the existing tree traversals. Each traversal can decide which comparisons matter for the work it’s doing. Client-side comparisons and history restoration still need the search params, so store them in a separate FlightRouterState slot for now. As before, previous-page search params are stripped from normal request headers. This is a temporary step that lets us migrate incrementally. Eventually, FlightRouterState will be replaced by a type that represents the route and its params more directly. This is mostly a refactor, though it fixes an accidental inconsistency in search-param handling compared with regular route params, avoiding some redundant prefetch work. Fully cached pages already track their vary params correctly. This separation prepares us to do the same for partially dynamic segments during navigation, so changing an unrelated param won’t require rendering their dynamic content again. --- .../src/client/components/layout-router.tsx | 8 +- .../src/client/components/match-segments.ts | 20 -- .../next/src/client/components/render-tree.ts | 335 ++++++++---------- .../router-reducer/compute-changed-path.ts | 24 +- .../create-initial-router-state.ts | 9 +- .../router-reducer/create-router-cache-key.ts | 12 +- .../router-reducer/create-segment-key.test.ts | 85 ----- .../router-reducer/create-segment-key.ts | 8 +- .../client/components/segment-cache/cache.ts | 32 +- .../segment-cache/decode-server-response.ts | 47 ++- .../segment-cache/optimistic-routes.ts | 6 +- .../components/segment-cache/scheduler.ts | 36 +- .../components/segment-cache/vary-path.ts | 84 +++++ .../src/client/flight-data-helpers.test.ts | 41 ++- .../next/src/client/flight-data-helpers.ts | 21 +- packages/next/src/client/route-params.ts | 39 +- .../instant-navs/instant-nav-cookie.ts | 2 +- .../next/src/server/app-render/app-render.tsx | 36 +- .../app-render/create-component-tree.tsx | 11 +- .../create-transport-tree-from-loader-tree.ts | 16 +- .../instant-validation/instant-config.tsx | 6 +- .../instant-validation/instant-validation.tsx | 10 +- packages/next/src/server/app-render/types.ts | 3 +- .../walk-tree-with-flight-router-state.tsx | 44 ++- .../src/server/dev/on-demand-entry-handler.ts | 2 +- .../next/src/shared/lib/app-router-types.ts | 11 +- .../shared/lib/router/utils/querystring.ts | 33 ++ packages/next/src/shared/lib/rsc-transport.ts | 23 +- .../segment-cache/segment-value-encoding.ts | 13 - packages/next/src/shared/lib/segment.ts | 18 +- .../e2e/app-dir/app-static/app-static.test.ts | 6 +- .../app/search-params/target-page/page.tsx | 10 + .../segment-cache-search-params.test.ts | 73 ++++ .../metadata-query/layout.tsx | 21 ++ .../metadata-query/loading.tsx | 3 + .../navigation-reuse/metadata-query/page.tsx | 17 + .../vary-params/vary-params.test.ts | 35 ++ 37 files changed, 631 insertions(+), 569 deletions(-) delete mode 100644 packages/next/src/client/components/match-segments.ts delete mode 100644 packages/next/src/client/components/router-reducer/create-segment-key.test.ts create mode 100644 test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/layout.tsx create mode 100644 test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/loading.tsx create mode 100644 test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/page.tsx diff --git a/packages/next/src/client/components/layout-router.tsx b/packages/next/src/client/components/layout-router.tsx index 117dacb0fc16..3d18d7191273 100644 --- a/packages/next/src/client/components/layout-router.tsx +++ b/packages/next/src/client/components/layout-router.tsx @@ -563,10 +563,8 @@ export default function OuterLayoutRouter({ // On the server, the key describes the segment's structure instead, so it // stays the same when unknown params become known during HTML resume. // - // The "cache" key of a segment, however, *does* include the search params, if - // it's possible that the segment accessed the search params on the server. - // (This only applies to page segments; layout segments cannot access search - // params on the server.) + // Whether the data can be reused is tracked separately, by the segment + // cache's vary paths. const activeTree = parentTree[1][parallelRouterKey] const activeRenderTree = parentRenderTree.slots?.get(parallelRouterKey) if (activeTree === undefined || activeRenderTree === undefined) { @@ -583,7 +581,7 @@ export default function OuterLayoutRouter({ } const activeSegment = activeTree[0] - const activeStateKey = createSegmentKey(activeSegment, true) // no search params + const activeStateKey = createSegmentKey(activeSegment) // At each level of the route tree, not only do we render the currently // active segment — we also render the last N segments that were active at diff --git a/packages/next/src/client/components/match-segments.ts b/packages/next/src/client/components/match-segments.ts deleted file mode 100644 index 77cfcbc9d0a6..000000000000 --- a/packages/next/src/client/components/match-segments.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Segment } from '../../shared/lib/app-router-types' - -export const matchSegment = ( - existingSegment: Segment, - segment: Segment -): boolean => { - // segment is either Array or string - if (typeof existingSegment === 'string') { - if (typeof segment === 'string') { - // Common case: segment is just a string - return existingSegment === segment - } - return false - } - - if (typeof segment === 'string') { - return false - } - return existingSegment[0] === segment[0] && existingSegment[1] === segment[1] -} diff --git a/packages/next/src/client/components/render-tree.ts b/packages/next/src/client/components/render-tree.ts index 69ed2aa6d0c2..0715846d2c29 100644 --- a/packages/next/src/client/components/render-tree.ts +++ b/packages/next/src/client/components/render-tree.ts @@ -1,16 +1,12 @@ -import type { - FlightRouterState, - Segment, -} from '../../shared/lib/app-router-types' +import type { FlightRouterState } from '../../shared/lib/app-router-types' import type { CacheNode } from '../../shared/lib/app-router-types' import type { ScrollRef } from '../../shared/lib/app-router-types' import { PrefetchHint } from '../../shared/lib/app-router-types' import { - PAGE_SEGMENT_KEY, DEFAULT_SEGMENT_KEY, NOT_FOUND_SEGMENT_KEY, + PAGE_SEGMENT_KEY, } from '../../shared/lib/segment' -import { matchSegment } from './match-segments' import { HEAD_REQUEST_KEY } from '../../shared/lib/segment-cache/segment-value-encoding' import { createHrefFromUrl } from './router-reducer/create-href-from-url' import { fetchServerResponse } from './router-reducer/fetch-server-response' @@ -34,6 +30,7 @@ import { type RefreshState, type FulfilledRouteCacheEntry, rebaseInactiveRouteTree, + doesRouteStructureMatch, readSegmentCacheEntryForNavigation, waitForSegmentCacheEntry, invalidateRouteCacheEntries, @@ -43,11 +40,14 @@ import { MetadataOnlyRequestTree, } from './segment-cache/cache' import { discoverKnownRoute } from './segment-cache/optimistic-routes' -import { urlSearchParamsToParsedUrlQuery } from '../route-params' import type { NormalizedSearch } from './segment-cache/cache-key' import type { CacheMap } from './segment-cache/cache-map' -import { getRenderedSearchFromVaryPath } from './segment-cache/vary-path' -import type { VaryPathNode } from './segment-cache/vary-path' +import { + getRenderedSearchFromVaryPath, + compareParams, + ParamsChange, + didReadChangedParam, +} from './segment-cache/vary-path' import { readFromBFCache, readFromBFCacheDuringRegularNavigation, @@ -274,69 +274,24 @@ export function startPPRNavigation( // navigation. return null } - - // The head has no position in the route tree, so there is nothing to - // traverse: either reuse the current head or create a new one, on the same - // terms that decide whether the page it belongs to is reused. - const oldHead = oldRoot.head - const newHead = newRoot.head - switch (freshness) { - case FreshnessPolicy.Default: - case FreshnessPolicy.HistoryTraversal: - case FreshnessPolicy.Gesture: { - if (isSamePageNavigation) { - // During a same-page navigation, we always refetch the page segments - break - } - // The head is the one node that is not compared as part of a tree walk - // (each tree node's params are compared where the walk visits it), so - // it compares its whole vary path here, entry by entry. The head's vary - // path is the page position it's keyed under, the rendered search, and - // every path param, so this covers the same changes that recreate a - // page node. - let oldEntry: VaryPathNode | null = oldHead.varyPath - let newEntry: VaryPathNode | null = newHead.varyPath - while ( - oldEntry !== null && - newEntry !== null && - oldEntry.value === newEntry.value - ) { - oldEntry = oldEntry.parent - newEntry = newEntry.parent - } - if (oldEntry !== null || newEntry !== null) { - // An entry differs, so a new head is created below. - break - } - return createRootNavigationTask( - tree, - createNavigationTask( - NavigationTaskStatus.Fulfilled, - createRouterStateForSegment(newHead, {}, null), - createRenderTree(newHead, oldHead.data), - null, - null - ) - ) - } - case FreshnessPolicy.Hydration: - case FreshnessPolicy.RefreshAll: - case FreshnessPolicy.HMRRefresh: - break - default: - freshness satisfies never - break - } - const head = createRenderTreeOnNavigation( + const head = updateRenderTreeOnNavigation( navigatedAt, - newHead, + oldRoot.head, + newRoot.head, freshness, seedDynamicStaleAt, + isSamePageNavigation, parentNeedsDynamicRequest, + oldRootRefreshState, + parentRefreshState, accumulation, map, restrictToShell ) + if (head === null) { + // Unreachable: a one-node tree has no root layout to change and no slots. + return null + } return createRootNavigationTask(tree, head) } @@ -373,14 +328,10 @@ function updateRenderTreeOnNavigation( // entries. Always false outside the testing API. See navigation-testing-lock. restrictToShell: boolean ): NavigationTask | null { - // Check if this segment matches the one in the previous route. A - // search-param-only difference at a page segment falls through to the - // matched branch — the render tree is rebuilt (so data refetches), but the - // bfcacheId carries forward as if the segment had matched. - const oldSegment = createSegmentFromRouteTree(oldRenderTree) - const newSegment = createSegmentFromRouteTree(newRouteTree) - const segmentMatchKind = compareSegments(newSegment, oldSegment) - if (segmentMatchKind === SegmentMatchKind.Change) { + // Check if the route structure changed. If only the params changed, that's + // handled further down. + const newSegment = newRouteTree.segment + if (!doesRouteStructureMatch(oldRenderTree, newRouteTree)) { // This segment does not match the previous route. We're now entering the // new part of the target route. Switch to the "create" path. if ( @@ -453,38 +404,94 @@ function updateRenderTreeOnNavigation( break } - // TODO: We're not consistent about how we do this check. Some places - // check if the segment starts with PAGE_SEGMENT_KEY, but most seem to - // check if there any any children, which is why I'm doing it here. We - // should probably encode an empty children set as `null` though. Either - // way, we should update all the checks to be consistent. const isLeafSegment = newSlots === null // Get the data for this segment. Since it was part of the previous route, - // usually we just reuse the data from the old render tree. During a refresh - // or revalidation, consult the prefetch cache or response seed instead. + // usually we just reuse the data from the old render tree. If the params + // changed, or during a refresh or revalidation, consult the prefetch cache + // or response seed instead. let newRenderTree: RouteTree let needsDynamicRequest: boolean - if ( - !shouldRefreshDynamicData && + const paramsChange = compareParams( + oldRenderTree.varyPath, + newRouteTree.varyPath + ) + if (paramsChange !== ParamsChange.None) { + // Path params are part of LayoutRouter's React key, so changing one + // remounts this segment and everything below it. Generate a new bfcacheId + // to match. Search params aren't part of the key, so a page whose search + // params changed keeps its existing id. + let bfcacheId: number + if (paramsChange === ParamsChange.PathParam) { + bfcacheId = generateBFCacheId(freshness) + } else { + bfcacheId = oldRenderTree.data.bfcacheId + } + switch (freshness) { + case FreshnessPolicy.Default: + case FreshnessPolicy.Gesture: { + // If the existing data didn't read any of the params that changed, we + // can keep using it. Refreshes always fetch new data, and back/forward + // navigations restore the entry from the BFCache instead. + const oldCacheNode = oldRenderTree.data + if ( + !didReadChangedParam( + oldRenderTree.varyPath, + newRouteTree.varyPath, + oldCacheNode.varyParams + ) + ) { + const cacheNode = createCacheNode( + oldCacheNode.rsc, + oldCacheNode.prefetchRsc, + oldCacheNode.varyParams, + bfcacheId + ) + if (freshness !== FreshnessPolicy.Gesture) { + writeToBFCache( + navigatedAt, + newRouteTree.varyPath, + cacheNode, + seedDynamicStaleAt + ) + } + newRenderTree = createRenderTree(newRouteTree, cacheNode) + needsDynamicRequest = false + break + } + // Intentional fallthrough + } + case FreshnessPolicy.Hydration: + case FreshnessPolicy.HistoryTraversal: + case FreshnessPolicy.RefreshAll: + case FreshnessPolicy.HMRRefresh: { + const result = createRenderTreeForSegment( + navigatedAt, + newRouteTree, + freshness, + seedDynamicStaleAt, + bfcacheId, + map, + restrictToShell + ) + newRenderTree = result.node + needsDynamicRequest = result.needsDynamicRequest + break + } + } + + // A param change mostly acts the same as a refresh, except it does + // trigger a scroll. + if (isLeafSegment) { + accumulateScrollRef(freshness, newRenderTree.data, accumulation) + } + } else if ( + shouldRefreshDynamicData || // During a same-page navigation, we always refetch the page segments - !(isLeafSegment && isSamePageNavigation) && - // A search-param-only change is treated as a refresh of the page segment. - // The internal cache key of the data is different, but the identity of - // the node in the route tree is the same. - segmentMatchKind !== SegmentMatchKind.SearchParamOnlyChange + (isLeafSegment && isSamePageNavigation) ) { - // This segment appears in both the old and new routes. Reuse the existing - // data without triggering a request. - // TODO: Consider adding a fast path where if this segment is unchanged and - // all of its children are unchanged, we return the exact same RenderTree - // object. Reusing the exact previous object gives React more of a chance to - // bail out of rendering. - newRenderTree = createRenderTree(newRouteTree, oldRenderTree.data) - needsDynamicRequest = false - } else { - // If this is part of a refresh, ignore the existing render tree and create a - // new one. + // This is a refresh of an existing segment. Ignore the existing render + // tree and create a new one. const result = createRenderTreeForSegment( navigatedAt, newRouteTree, @@ -498,22 +505,20 @@ function updateRenderTreeOnNavigation( newRenderTree = result.node needsDynamicRequest = result.needsDynamicRequest - // Scroll handling - if ( - isLeafSegment && - segmentMatchKind === SegmentMatchKind.SearchParamOnlyChange - ) { - // Special case: A search param change mostly acts the same as a - // refresh, except it does trigger a scroll. - accumulateScrollRef(freshness, newRenderTree.data, accumulation) - } else { - // Normal case: This is a refresh of an existing segment. Carry forward - // the old node's scrollRef. This preserves scroll intent when a prior - // navigation's render tree is replaced by a refresh before the scroll - // handler has had a chance to fire — e.g. when router.push() and - // router.refresh() are called in the same startTransition batch. - newRenderTree.data.scrollRef = oldRenderTree.data.scrollRef - } + // Carry forward the old node's scrollRef. This preserves scroll intent + // when a prior navigation's render tree is replaced by a refresh before + // the scroll handler has had a chance to fire — e.g. when router.push() + // and router.refresh() are called in the same startTransition batch. + newRenderTree.data.scrollRef = oldRenderTree.data.scrollRef + } else { + // This segment appears in both the old and new routes. Reuse the existing + // data without triggering a request. + // TODO: Consider adding a fast path where if this segment is unchanged and + // all of its children are unchanged, we return the exact same RenderTree + // object. Reusing the exact previous object gives React more of a chance to + // bail out of rendering. + newRenderTree = createRenderTree(newRouteTree, oldRenderTree.data) + needsDynamicRequest = false } // During a refresh navigation, there's a special case that happens when @@ -584,13 +589,17 @@ function updateRenderTreeOnNavigation( } const oldSegmentChild = oldRenderTreeChild.segment - const newSegmentChild = createSegmentFromRouteTree(newRouteTreeChild) + const newSegmentChild = newRouteTreeChild.segment if ( // Skip this branch during a history traversal. We restore the tree that // was stashed in the history entry as-is. freshness !== FreshnessPolicy.HistoryTraversal && newSegmentChild === DEFAULT_SEGMENT_KEY && - oldSegmentChild !== DEFAULT_SEGMENT_KEY + oldSegmentChild !== DEFAULT_SEGMENT_KEY && + // The active segment was rendered with this layout's params. If a + // path param changed, we can't keep it. Use the default segment from + // the server instead. + paramsChange !== ParamsChange.PathParam ) { // This is a "default" segment. These are never sent by the server during // a soft navigation; instead, the client reuses whatever segment was @@ -673,13 +682,8 @@ function updateRenderTreeOnNavigation( * navigation share the same ScrollRef — the first segment to scroll * consumes it, preventing others from also scrolling. * - * This is only called inside `createRenderTreeOnNavigation`, which only - * runs when segments diverge from the previous route. So for a refresh - * where the route structure stays the same, segments match, the update - * path is taken, and this function is never called — no scroll ref is - * assigned. A scroll ref is only assigned when the route actually - * changed (e.g. a redirect, or a dynamic condition on the server that - * produces a different route). + * Called for newly entered segments, and for segments whose params changed + * (even if their data was reused). Refreshes keep the existing scroll ref. * * Skipped during hydration (initial render should not scroll) and * history traversal (scroll restoration is handled separately). @@ -825,42 +829,15 @@ function createRenderTreeOnNavigation( ) } -function createSegmentFromRouteTree( - newRouteTree: RouteTree -): Segment { - if (newRouteTree.segment === PAGE_SEGMENT_KEY) { - // In a dynamic server response, the server embeds the search params into - // the segment key, but in a static one it's omitted. The client handles - // this inconsistency by adding the search params back right at the end. - // - // As an incremental step, we can grab the search params from the varyPath. - // - // TODO: Remove the search params from the segment key entirely. - const renderedSearch = getRenderedSearchFromVaryPath(newRouteTree.varyPath) - if (renderedSearch === null) { - return PAGE_SEGMENT_KEY - } - // This is based on equivalent logic in addSearchParamsIfPageSegment, used - // on the server. - const stringifiedQuery = JSON.stringify( - urlSearchParamsToParsedUrlQuery(new URLSearchParams(renderedSearch)) - ) - return stringifiedQuery !== '{}' - ? PAGE_SEGMENT_KEY + '?' + stringifiedQuery - : PAGE_SEGMENT_KEY - } - return newRouteTree.segment -} - // Converts a route tree node into the router state the client sends back to -// the server. +// the server. Page nodes carry their rendered search in its own slot. function createRouterStateForSegment( routeTree: RouteTree, children: { [parallelRouteKey: string]: FlightRouterState }, refreshState: RefreshState | null ): FlightRouterState { - return [ - createSegmentFromRouteTree(routeTree), + const routerState: FlightRouterState = [ + routeTree.segment, children, refreshState !== null ? [refreshState.canonicalUrl, refreshState.renderedSearch] @@ -868,6 +845,13 @@ function createRouterStateForSegment( null, routeTree.prefetchHints, ] + if (routeTree.segment === PAGE_SEGMENT_KEY) { + const renderedSearch = getRenderedSearchFromVaryPath(routeTree.varyPath) + if (renderedSearch !== null) { + routerState[5] = renderedSearch + } + } + return routerState } function patchRouterStateWithNewChildren( @@ -887,6 +871,9 @@ function patchRouterStateWithNewChildren( if (4 in baseRouterState) { clone[4] = baseRouterState[4] } + if (5 in baseRouterState) { + clone[5] = baseRouterState[5] + } return clone } @@ -1318,38 +1305,6 @@ function generateBFCacheId(freshness: FreshnessPolicy): number { return ++nextBFCacheId } -const enum SegmentMatchKind { - // Two segments are equivalent: the render tree can be reused as-is. - Match, - // The segments differ in the parts that determine the route (segment kind, - // dynamic param value, etc.). The render tree must be created fresh. - Change, - // Two page segments differ only in their search params. Conceptually this - // is a refresh of the current page rather than a navigation to a new - // route — search params don't contribute to the LayoutRouter state key, - // and they shouldn't change the bfcacheId either. The render tree is rebuilt - // (so data refetches) but the bfcacheId carries forward. - SearchParamOnlyChange, -} - -function compareSegments( - newSegment: Segment, - oldSegment: Segment -): SegmentMatchKind { - if (matchSegment(newSegment, oldSegment)) { - return SegmentMatchKind.Match - } - if ( - typeof newSegment === 'string' && - typeof oldSegment === 'string' && - newSegment.startsWith(PAGE_SEGMENT_KEY) && - oldSegment.startsWith(PAGE_SEGMENT_KEY) - ) { - return SegmentMatchKind.SearchParamOnlyChange - } - return SegmentMatchKind.Change -} - // Represents whether the previuos navigation resulted in a route tree mismatch. // A mismatch results in a refresh of the page. If there are two successive // mismatches, we will fall back to an MPA navigation, to prevent a retry loop. @@ -1993,11 +1948,15 @@ function writeDynamicDataIntoNavigationTask( // path. But as an extra precaution, we validate in prod, too. didReceiveUnknownParallelRoute = true } else { - const taskSegment = createSegmentFromRouteTree(taskChild.node) - const serverSegment = createSegmentFromRouteTree(serverRouteTreeChild) + // Check that the response is for the route we expected: same route + // structure and same params, including the page's search params. if ( - matchSegment(serverSegment, taskSegment) && - serverRouteTreeChild.data !== null + doesRouteStructureMatch(taskChild.node, serverRouteTreeChild) && + serverRouteTreeChild.data !== null && + compareParams( + taskChild.node.varyPath, + serverRouteTreeChild.varyPath + ) === ParamsChange.None ) { // Found a match for this task. Keep traversing down the task tree. const childDidReceiveUnknownParallelRoute = diff --git a/packages/next/src/client/components/router-reducer/compute-changed-path.ts b/packages/next/src/client/components/router-reducer/compute-changed-path.ts index d64d9aa1347c..8bbbd88fbcff 100644 --- a/packages/next/src/client/components/router-reducer/compute-changed-path.ts +++ b/packages/next/src/client/components/router-reducer/compute-changed-path.ts @@ -9,7 +9,6 @@ import { DEFAULT_SEGMENT_KEY, PAGE_SEGMENT_KEY, } from '../../../shared/lib/segment' -import { matchSegment } from '../match-segments' const removeLeadingSlash = (segment: string): string => { return segment[0] === '/' ? segment.slice(1) : segment @@ -30,7 +29,7 @@ const segmentToPathname = (segment: Segment): string => { export const segmentToSourcePagePathname = (segment: Segment): string => { if (typeof segment === 'string') { if (segment === 'children') return '' - if (segment.startsWith(PAGE_SEGMENT_KEY)) return 'page' + if (segment === PAGE_SEGMENT_KEY) return 'page' return segment } @@ -91,7 +90,7 @@ export function extractPathFromFlightRouterState( ) return undefined - if (segment.startsWith(PAGE_SEGMENT_KEY)) return '' + if (segment === PAGE_SEGMENT_KEY) return '' const segments = [segmentToPathname(segment)] const parallelRoutes = flightRouterState[1] ?? {} @@ -185,7 +184,22 @@ function computeChangedPathImpl( return '' } - if (!matchSegment(segmentA, segmentB)) { + // Param types and static sibling hints don't affect the resulting pathname, + // so only compare the segment name and param value. + // TODO: computeChangedPath is only used to compute the Next-Url header. + // We should refactor this data structure to be lower cardinality. For example + // it doesn't need to include any of the concrete param values, just the + // route structure. + const didPathChange = + typeof segmentA === 'string' || typeof segmentB === 'string' + ? segmentA !== segmentB + : segmentA[0] !== segmentB[0] || segmentA[1] !== segmentB[1] + if ( + didPathChange || + // A change to the page's search params counts, too. (They used to be part + // of the page segment string.) + (segmentA === PAGE_SEGMENT_KEY && treeA[5] !== treeB[5]) + ) { // once we find where the tree changed, we compute the rest of the path by traversing the tree return extractPathFromFlightRouterState(treeB) ?? '' } @@ -232,7 +246,7 @@ export function getSelectedParams( const segment = parallelRoute[0] const isDynamicParameter = Array.isArray(segment) const segmentValue = isDynamicParameter ? segment[1] : segment - if (!segmentValue || segmentValue.startsWith(PAGE_SEGMENT_KEY)) continue + if (!segmentValue || segmentValue === PAGE_SEGMENT_KEY) continue // Ensure catchAll and optional catchall are turned into an array const isCatchAll = diff --git a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts index 4b9691727003..ace762cd9c9d 100644 --- a/packages/next/src/client/components/router-reducer/create-initial-router-state.ts +++ b/packages/next/src/client/components/router-reducer/create-initial-router-state.ts @@ -49,10 +49,11 @@ export function createInitialRouterState({ // as a URL that should be crawled. const initialCanonicalUrl = initialCanonicalUrlParts.join('/') - // The initial router state tree, derived from the transport tree. Page - // segments keep their search params, which travel inside the segment - // string. - const initialTree = transportNodeToFlightRouterState(initialTransportData.t) + // The initial router state tree, derived from the transport tree. + const initialTree = transportNodeToFlightRouterState( + initialTransportData.t, + initialRenderedSearch + ) const canonicalUrl = // location.href is read as the initial value for canonicalUrl in the browser diff --git a/packages/next/src/client/components/router-reducer/create-router-cache-key.ts b/packages/next/src/client/components/router-reducer/create-router-cache-key.ts index 70377e0ea5fa..b38611bbe8a2 100644 --- a/packages/next/src/client/components/router-reducer/create-router-cache-key.ts +++ b/packages/next/src/client/components/router-reducer/create-router-cache-key.ts @@ -1,21 +1,11 @@ import type { Segment } from '../../../shared/lib/app-router-types' -import { PAGE_SEGMENT_KEY } from '../../../shared/lib/segment' -export function createRouterCacheKey( - segment: Segment, - withoutSearchParameters: boolean = false -) { +export function createRouterCacheKey(segment: Segment) { // if the segment is an array, it means it's a dynamic segment // for example, ['lang', 'en', 'd']. We need to convert it to a string to store it as a cache node key. if (Array.isArray(segment)) { return `${segment[0]}|${segment[1]}|${segment[2]}` } - // Page segments might have search parameters, ie __PAGE__?foo=bar - // When `withoutSearchParameters` is true, we only want to return the page segment - if (withoutSearchParameters && segment.startsWith(PAGE_SEGMENT_KEY)) { - return PAGE_SEGMENT_KEY - } - return segment } diff --git a/packages/next/src/client/components/router-reducer/create-segment-key.test.ts b/packages/next/src/client/components/router-reducer/create-segment-key.test.ts deleted file mode 100644 index 21258e220fc7..000000000000 --- a/packages/next/src/client/components/router-reducer/create-segment-key.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { createRouterCacheKey } from './create-router-cache-key' -import { createSegmentKey } from './create-segment-key' -import { createSegmentKey as createBrowserSegmentKey } from './create-segment-key.browser' - -describe('createSegmentKey on the server', () => { - it.each(['d', 'c', 'oc', 'di(.)', 'ci(.)'] as const)( - 'keeps %s keys stable between prerendering and resuming', - (paramType) => { - const fallbackKey = createSegmentKey([ - 'slug', - '%%drp:slug:abc123%%', - paramType, - null, - ]) - - expect(fallbackKey).toBe(`slug|${paramType}`) - expect(createSegmentKey(['slug', 'first', paramType, null])).toBe( - fallbackKey - ) - expect(createSegmentKey(['slug', 'second/third', paramType, null])).toBe( - fallbackKey - ) - } - ) - - it('distinguishes param names and segment types', () => { - const keys = [ - createSegmentKey(['slug', 'value', 'd', null]), - createSegmentKey(['other', 'value', 'd', null]), - createSegmentKey(['slug', 'value', 'c', null]), - createSegmentKey(['slug', 'value', 'oc', null]), - createSegmentKey(['slug', 'value', 'di(.)', null]), - createSegmentKey(['slug', 'value', 'ci(.)', null]), - ] - - expect(new Set(keys).size).toBe(keys.length) - }) - - it.each(['', 'catalog', '__DEFAULT__', '__PAGE__'])( - 'preserves the static segment %j', - (segment) => { - expect(createSegmentKey(segment)).toBe(segment) - } - ) - - it.each([undefined, false, true])( - 'always omits search params, even with withoutSearchParameters=%s', - (withoutSearchParameters) => { - expect( - createSegmentKey('__PAGE__?{"q":"first"}', withoutSearchParameters) - ).toBe('__PAGE__') - expect( - createSegmentKey('__PAGE__?{"q":"second"}', withoutSearchParameters) - ).toBe('__PAGE__') - } - ) -}) - -describe('createSegmentKey in the browser', () => { - it('re-exports the router cache key implementation', () => { - expect(createBrowserSegmentKey).toBe(createRouterCacheKey) - }) - - it.each(['d', 'c', 'oc', 'di(.)', 'ci(.)'] as const)( - 'includes concrete param values in %s keys', - (paramType) => { - expect(createBrowserSegmentKey(['slug', 'first', paramType, null])).toBe( - `slug|first|${paramType}` - ) - expect( - createBrowserSegmentKey(['slug', 'second/third', paramType, null]) - ).toBe(`slug|second/third|${paramType}`) - } - ) - - it.each([undefined, false, true])( - 'respects withoutSearchParameters=%s', - (withoutSearchParameters) => { - const segment = '__PAGE__?{"q":"first"}' - expect(createBrowserSegmentKey(segment, withoutSearchParameters)).toBe( - withoutSearchParameters ? '__PAGE__' : segment - ) - } - ) -}) diff --git a/packages/next/src/client/components/router-reducer/create-segment-key.ts b/packages/next/src/client/components/router-reducer/create-segment-key.ts index e250153f8475..e86d15c9f31c 100644 --- a/packages/next/src/client/components/router-reducer/create-segment-key.ts +++ b/packages/next/src/client/components/router-reducer/create-segment-key.ts @@ -7,16 +7,12 @@ import { createRouterCacheKey } from './create-router-cache-key' // The browser uses the concrete keys instead, so navigation still resets or // preserves state as appropriate. Its bundle uses create-segment-key.browser.ts. // React keys are not embedded in the HTML. -export function createSegmentKey( - segment: Segment, - // Server keys always omit search params, regardless of this browser option. - _withoutSearchParameters?: boolean -): string { +export function createSegmentKey(segment: Segment): string { if (Array.isArray(segment)) { return `${segment[0]}|${segment[2]}` } - return createRouterCacheKey(segment, true) + return createRouterCacheKey(segment) } // TODO: To model this more accurately, we should use React.optimisticKey diff --git a/packages/next/src/client/components/segment-cache/cache.ts b/packages/next/src/client/components/segment-cache/cache.ts index fd65a7223e4a..ad4aadac3836 100644 --- a/packages/next/src/client/components/segment-cache/cache.ts +++ b/packages/next/src/client/components/segment-cache/cache.ts @@ -60,6 +60,7 @@ import { getPathnameFromRequestURL, getRenderedPathname, getRenderedSearch, + normalizeRenderedSearch, } from '../../route-params' import { createCacheMap, @@ -244,6 +245,22 @@ export function createRootRouteTree( return { tree, head } } +export function doesRouteStructureMatch( + currentTree: RouteTree, + nextTree: RouteTree +): boolean { + // The request key includes the param names and types, but not the values. + if (currentTree.requestKey !== nextTree.requestKey) { + return false + } + // The root request key is always empty (even for global Not Found), so we + // have to compare the root segment directly. + return ( + nextTree.requestKey !== ROOT_SEGMENT_REQUEST_KEY || + currentTree.segment === nextTree.segment + ) +} + type RouteCacheEntryShared = { // This is false only if we're certain the route cannot be intercepted. It's // true in all other cases, including on initialization when we haven't yet @@ -813,7 +830,7 @@ export function deprecated_requestOptimisticRouteCacheEntry( routeWithNoSearchParams.renderedSearch !== '' ? // Base route was rewritten. Reuse the same rewritten search string. routeWithNoSearchParams.renderedSearch - : requestedSearch + : normalizeRenderedSearch(requestedSearch) const optimisticUrl = new URL( routeWithNoSearchParams.canonicalUrl, @@ -1418,11 +1435,11 @@ function pingBlockedTasks(entry: { * The head's request key on the client. The server's own key for the head, * HEAD_REQUEST_KEY, carries no path information: there is only one head per * URL, so the server has no need to distinguish parallel pages. On the client - * the request key is the head's cache identity, so the head takes its page's - * request key with HEAD_REQUEST_KEY appended — the key the server would have - * assigned had the head been a segment below the page — and two pages' heads - * never share a key. The head varies on the same params as its page, so the - * rest of its vary path is the page's. + * the request key is the head's cache identity and what doesRouteStructureMatch + * compares, so the head takes its page's request key with HEAD_REQUEST_KEY + * appended — the key the server would have assigned had the head been a + * segment below the page — and two pages' heads never match. The head varies + * on the same params as its page, so the rest of its vary path is the page's. * The page must be the route's own: a page in a slot retained from another * URL (one with a refresh state) belongs to that URL's head. When a route has * multiple parallel pages of its own, the first one is used; the keys only @@ -1732,6 +1749,9 @@ export function convertFlightRouterStateToRouteTree( renderedSearch: compressedRefreshState[1] as NormalizedSearch, } : null + // Use the incoming search params, even if the response has no new data for + // this segment. The base tree may still have the previous URL's. History + // restores pass in their saved search params instead. const renderedSearch = refreshState !== null ? refreshState.renderedSearch : parentRenderedSearch diff --git a/packages/next/src/client/components/segment-cache/decode-server-response.ts b/packages/next/src/client/components/segment-cache/decode-server-response.ts index d9d02ea30f7e..baa93f9392de 100644 --- a/packages/next/src/client/components/segment-cache/decode-server-response.ts +++ b/packages/next/src/client/components/segment-cache/decode-server-response.ts @@ -32,7 +32,6 @@ import { DEFAULT_SEGMENT_KEY, PAGE_SEGMENT_KEY, } from '../../../shared/lib/segment' -import { matchSegment } from '../match-segments' import { InvariantError } from '../../../shared/lib/invariant-error' import { doesStaticSegmentAppearInURL, @@ -268,16 +267,6 @@ export function createRouteTreeNode( partialVaryPath = parentPartialVaryPath if (requestKey.endsWith(PAGE_SEGMENT_KEY)) { // This is a page segment. - - // The navigation implementation expects the search params to be included - // in the segment. However, in the case of a static response, the search - // params are omitted. So the client needs to add them back in when reading - // from the Segment Cache. - // - // For consistency, we'll do this for live-render responses, too. - // - // TODO: We should move search params out of FlightRouterState and handle - // them entirely on the client, similar to our plan for dynamic params. segment = PAGE_SEGMENT_KEY varyPath = finalizeVaryPath(requestKey, renderedSearch, partialVaryPath) // The head is keyed under the route's own first page and varies on the @@ -405,17 +394,33 @@ function resolveTransportSegment( pathnameParts, pathnamePartsIndex ) - // TODO: We're intentionally not adding the search param to page segments - // here; it's tracked separately and added back during a read from the - // Segment Cache. return [ transportSegment.n, - getCacheKeyForDynamicParam(paramValue, '' as NormalizedSearch), + getCacheKeyForDynamicParam(paramValue), transportSegment.t, transportSegment.s, ] } +function doSegmentsMatch( + baseSegment: FlightRouterStateSegment, + segment: FlightRouterStateSegment +): boolean { + if (typeof baseSegment === 'string' || typeof segment === 'string') { + // Static segments have to match exactly. + return baseSegment === segment + } + // Both segments are dynamic. The static sibling hints aren't part of the + // segment's identity, so only compare the param name, type, and value. + const [baseParamName, baseParamValue, baseParamType] = baseSegment + const [paramName, paramValue, paramType] = segment + return ( + baseParamName === paramName && + baseParamType === paramType && + baseParamValue === paramValue + ) +} + function decodeTransportNode( node: PartialTransportNode, // The node's identity, already resolved by the caller (the parent's child @@ -453,18 +458,10 @@ function decodeTransportNode( // are still checked. } else { const baseSegment = compareBase[0] - if ( - typeof originalSegment === 'string' && - typeof baseSegment === 'string' && - originalSegment.startsWith(PAGE_SEGMENT_KEY) && - baseSegment.startsWith(PAGE_SEGMENT_KEY) - ) { - // Page segments match modulo embedded search params, which are - // validated separately (see getRenderedSearch). - } else if (originalSegment === DEFAULT_SEGMENT_KEY) { + if (originalSegment === DEFAULT_SEGMENT_KEY) { // A default filled in by the server is not a claim about the // position's identity. - } else if (!matchSegment(baseSegment, originalSegment)) { + } else if (!doSegmentsMatch(baseSegment, originalSegment)) { acc.treeDivergedFromBase = true } } diff --git a/packages/next/src/client/components/segment-cache/optimistic-routes.ts b/packages/next/src/client/components/segment-cache/optimistic-routes.ts index 74ea32179e3b..a75ce9f675a4 100644 --- a/packages/next/src/client/components/segment-cache/optimistic-routes.ts +++ b/packages/next/src/client/components/segment-cache/optimistic-routes.ts @@ -65,6 +65,7 @@ import { import { isValueExpired } from './cache-map' import { canonicalizeURLPart, + normalizeRenderedSearch, doesStaticSegmentAppearInURL, } from '../../route-params' import type { NormalizedPathname, NormalizedSearch } from './cache-key' @@ -785,11 +786,12 @@ export function matchKnownRoute( // "Reify" the pattern: clone the template tree with concrete param values. // This substitutes resolved params (e.g., slug: "hello") into dynamic // segments and recomputes vary paths for correct segment cache keying. + const renderedSearch = normalizeRenderedSearch(search) const acc: ReifyAccumulator = { metadataVaryPath: null } const reifiedTree = reifyRouteTree( pattern.root.tree, resolvedParams, - search, + renderedSearch, null, // Start with null partial vary path at the root acc ) @@ -822,7 +824,7 @@ export function matchKnownRoute( couldBeIntercepted: pattern.couldBeIntercepted, supportsPerSegmentPrefetching: pattern.supportsPerSegmentPrefetching, predictedFrom: matchedPart, - renderedSearch: search, + renderedSearch, ref: null, size: pattern.size, staleAt: pattern.staleAt, diff --git a/packages/next/src/client/components/segment-cache/scheduler.ts b/packages/next/src/client/components/segment-cache/scheduler.ts index 838e599b89e2..1b93c04fe951 100644 --- a/packages/next/src/client/components/segment-cache/scheduler.ts +++ b/packages/next/src/client/components/segment-cache/scheduler.ts @@ -1,5 +1,4 @@ -import { matchSegment } from '../match-segments' -import { getRenderedSearchFromVaryPath } from './vary-path' +import { compareParams, ParamsChange } from './vary-path' import type { FlightRouterState, CacheNode, @@ -23,6 +22,7 @@ import { type PendingSegmentCacheEntry, type SegmentCacheEntry, convertRouteTreeToFlightRouterState, + doesRouteStructureMatch, readOrCreateRevalidatingSegmentEntry, upgradeToPendingSegment, overwriteRevalidatingSegmentCacheEntry, @@ -30,7 +30,7 @@ import { attemptToFulfillDynamicSegmentFromBFCache, attemptToUpgradeSegmentFromBFCache, } from './cache' -import type { NormalizedSearch, RouteCacheKey } from './cache-key' +import type { RouteCacheKey } from './cache-key' import { createCacheKey } from './cache-key' import { FetchStrategy, @@ -44,7 +44,6 @@ import { } from './cache' import type { CacheMap } from './cache-map' import type { NavigationLockPrefetch } from './navigation-testing-lock' -import { PAGE_SEGMENT_KEY } from '../../../shared/lib/segment' import { HEAD_REQUEST_KEY, type SegmentRequestKey, @@ -1382,7 +1381,9 @@ function pingSharedPartOfCacheComponentsTree( let childExitStatus if ( oldTreeChild !== undefined && - doesCurrentSegmentMatchCachedSegment(route, oldTreeChild, newTreeChild) + doesRouteStructureMatch(oldTreeChild, newTreeChild) && + compareParams(oldTreeChild.varyPath, newTreeChild.varyPath) === + ParamsChange.None ) { // We're still in the "shared" part of the tree. childExitStatus = pingSharedPartOfCacheComponentsTree( @@ -1592,7 +1593,9 @@ function diffRouteTreeAgainstCurrent( const oldTreeChild = oldSlots?.get(parallelRouteKey) if ( oldTreeChild !== undefined && - doesCurrentSegmentMatchCachedSegment(route, oldTreeChild, newTreeChild) + doesRouteStructureMatch(oldTreeChild, newTreeChild) && + compareParams(oldTreeChild.varyPath, newTreeChild.varyPath) === + ParamsChange.None ) { // This segment is already part of the current route. Keep traversing. const requestTreeChild = diffRouteTreeAgainstCurrent( @@ -2610,27 +2613,6 @@ function pingFullSegmentRevalidation( } } -// TODO: Removed in a later change, which compares route structure by -// request key. -function doesCurrentSegmentMatchCachedSegment( - route: FulfilledRouteCacheEntry, - currentTree: RouteTree, - cachedTree: RouteTree -): boolean { - if (!matchSegment(currentTree.segment, cachedTree.segment)) { - return false - } - if (cachedTree.segment === PAGE_SEGMENT_KEY) { - // The render tree stores the page's rendered search on its vary path; the - // route cache stores it on the route entry. - const currentSearch = - getRenderedSearchFromVaryPath(currentTree.varyPath) ?? - ('' as NormalizedSearch) - return currentSearch === route.renderedSearch - } - return true -} - /** * Decides whether to speculatively prefetch a subtree. Under Partial * Prefetching we only do this if the Link's prefetch prop is set to true — diff --git a/packages/next/src/client/components/segment-cache/vary-path.ts b/packages/next/src/client/components/segment-cache/vary-path.ts index 619abf0c2a25..f6bc56f77f99 100644 --- a/packages/next/src/client/components/segment-cache/vary-path.ts +++ b/packages/next/src/client/components/segment-cache/vary-path.ts @@ -8,8 +8,10 @@ import type { RouteTree } from './cache' import { Fallback, type FallbackType } from './cache-map' import type { SegmentRequestKey } from '../../../shared/lib/segment-cache/segment-value-encoding' import { + readVaryParams, SEARCH_PARAMS_VARY_ID, type VaryParamId, + type VaryParams, } from '../../../shared/lib/segment-cache/vary-params-decoding' type Opaque = T & { __brand: K } @@ -324,6 +326,88 @@ export function getRenderedSearchFromVaryPath( return null } +/** + * The kind of param change between two vary paths for the same segment. A path + * param change takes precedence, because path params are part of + * LayoutRouter's React key: the segment remounts either way. + */ +export const enum ParamsChange { + None, + SearchParams, + PathParam, +} + +export function compareParams( + currentVaryPath: VaryPath, + nextVaryPath: VaryPath +): ParamsChange { + // Both vary paths are for the same segment, so they list the same params in + // the same order. Walk them together. This includes params inherited from + // parent layouts, since those may have changed, too. + let current: VaryPathNode | null = currentVaryPath + let next: VaryPathNode | null = nextVaryPath + let change = ParamsChange.None + while (current !== null && next !== null) { + if (current.value !== next.value) { + const id = current.id + if (id === null) { + // The request key. Callers check that the route structure matches + // first, so it's always the same. + } else if (id === SEARCH_PARAMS_VARY_ID) { + change = ParamsChange.SearchParams + } else { + return ParamsChange.PathParam + } + } + current = current.parent + next = next.parent + } + return change +} + +export function didReadChangedParam( + currentVaryPath: VaryPath, + nextVaryPath: VaryPath, + varyParams: VaryParams | null +): boolean { + // Returns true if the output rendered with `currentVaryPath` read a param + // whose value is different in `nextVaryPath`. `varyParams` is the set of + // params the output read, or null if we don't know. + // + // Same traversal as compareParams. Only read the set if a param changed. + let current: VaryPathNode | null = currentVaryPath + let next: VaryPathNode | null = nextVaryPath + let total: Set | null = null + while (current !== null && next !== null) { + if (current.value !== next.value) { + const id = current.id + if (id === null) { + // The request key. Callers check that the route structure matches + // first, so it's always the same. + } else { + if (total === null) { + if (varyParams === null) { + // We don't know. Assume it read the param. + return true + } + total = readVaryParams(varyParams) + if (total === null) { + // The render hasn't finished, or it aborted. Assume it read the + // param. + return true + } + } + if (total.has(id)) { + return true + } + } + } + current = current.parent + next = next.parent + } + return false +} + export function getFulfilledSegmentVaryPath( original: VaryPathNode, varyParams: Set diff --git a/packages/next/src/client/flight-data-helpers.test.ts b/packages/next/src/client/flight-data-helpers.test.ts index 2c3901a4b5cf..a9dbf98a2ca3 100644 --- a/packages/next/src/client/flight-data-helpers.test.ts +++ b/packages/next/src/client/flight-data-helpers.test.ts @@ -8,11 +8,12 @@ describe('prepareFlightRouterStateForRequest', () => { describe('HMR refresh handling', () => { it('should preserve complete state for HMR refresh requests', () => { const flightRouterState: FlightRouterState = [ - '__PAGE__?{"sensitive":"data"}', + '__PAGE__', {}, ['/some/url', ''], 'refetch', PrefetchHint.IsRootLayoutOrAbove | 1, + '?sensitive=data', ] const result = prepareFlightRouterStateForRequest(flightRouterState, true) @@ -22,19 +23,28 @@ describe('prepareFlightRouterStateForRequest', () => { }) }) - describe('__PAGE__ segment handling', () => { - it('should strip search params from __PAGE__ segments', () => { + describe('page search handling', () => { + it('should strip the page search (index 5)', () => { const flightRouterState: FlightRouterState = [ - '__PAGE__?{"param":"value","foo":"bar"}', + '__PAGE__', {}, + ['/some/url', ''], + 'refetch', + PrefetchHint.IsRootLayoutOrAbove | 1, + '?sensitive=data', ] - const result = prepareFlightRouterStateForRequest(flightRouterState) + const result = prepareFlightRouterStateForRequest( + flightRouterState, + false + ) const decoded = JSON.parse(decodeURIComponent(result)) - expect(decoded[0]).toBe('__PAGE__') + expect(decoded[5]).toBeUndefined() }) + }) + describe('__PAGE__ segment handling', () => { it('should preserve non-page segments', () => { const flightRouterState: FlightRouterState = ['regular-segment', {}] @@ -174,7 +184,7 @@ describe('prepareFlightRouterStateForRequest', () => { const flightRouterState: FlightRouterState = [ 'parent', { - children: ['__PAGE__?{"nested":"param"}', {}, ['/nested/url', '']], + children: ['__PAGE__', {}, ['/nested/url', '']], modal: ['modal-segment', {}, ['/modal/url', ''], 'refetch'], }, ['/parent/url', ''], @@ -188,7 +198,7 @@ describe('prepareFlightRouterStateForRequest', () => { 'parent', { children: [ - '__PAGE__', // search params stripped + '__PAGE__', {}, // URL stripped // 'refresh' marker stripped @@ -212,12 +222,7 @@ describe('prepareFlightRouterStateForRequest', () => { children: [ 'level1', { - children: [ - '__PAGE__?{"deep":"nesting"}', - {}, - ['/deep/url', ''], - 'refetch', - ], + children: ['__PAGE__', {}, ['/deep/url', ''], 'refetch'], }, ], }, @@ -235,13 +240,13 @@ describe('prepareFlightRouterStateForRequest', () => { describe('real-world scenarios', () => { it('should handle complex FlightRouterState with all features', () => { const complexState: FlightRouterState = [ - '__PAGE__?{"userId":"123"}', + '__PAGE__', { children: [ 'dashboard', { modal: [ - '__PAGE__?{"modalParam":"data"}', + '__PAGE__', {}, ['/modal/path', ''], null, @@ -270,7 +275,7 @@ describe('prepareFlightRouterStateForRequest', () => { const decoded = JSON.parse(decodeURIComponent(result)) // Root level checks - expect(decoded[0]).toBe('__PAGE__') // search params stripped + expect(decoded[0]).toBe('__PAGE__') expect(decoded[2]).toBeNull() // URL stripped expect(decoded[3]).toBe('inside-shared-layout') // server marker preserved expect(decoded[4]).toBe( @@ -289,7 +294,7 @@ describe('prepareFlightRouterStateForRequest', () => { // Modal route checks const modalRoute = childrenRoute[1].modal - expect(modalRoute[0]).toBe('__PAGE__') // search params stripped + expect(modalRoute[0]).toBe('__PAGE__') expect(modalRoute[2]).toBeNull() // URL stripped expect(modalRoute[3]).toBeNull() // 'refresh' marker stripped expect(modalRoute[4]).toBe(PrefetchHint.SegmentHasLoadingBoundary) // prefetchHints preserved diff --git a/packages/next/src/client/flight-data-helpers.ts b/packages/next/src/client/flight-data-helpers.ts index e1c32f2dfb15..3214d8bea777 100644 --- a/packages/next/src/client/flight-data-helpers.ts +++ b/packages/next/src/client/flight-data-helpers.ts @@ -8,8 +8,6 @@ import type { FullTransportNode, TransportSegment, } from '../shared/lib/rsc-transport' -import { PAGE_SEGMENT_KEY } from '../shared/lib/segment' -import type { NormalizedSearch } from './components/segment-cache/cache-key' import { getCacheKeyForDynamicParam, parseDynamicParamFromURLPart, @@ -57,8 +55,7 @@ export function createInitialRSCPayloadFromFallbackPrerender( t: fillInFallbackTransportTree( fallbackTransportData.t, renderedPathname.split('/').filter((part) => part !== ''), - 0, - renderedSearch as NormalizedSearch + 0 ), h: fallbackTransportData.h, }, @@ -80,8 +77,7 @@ export function createInitialRSCPayloadFromFallbackPrerender( function fillInFallbackTransportTree( node: FullTransportNode, pathnameParts: Array, - pathnamePartsIndex: number, - renderedSearch: NormalizedSearch + pathnamePartsIndex: number ): FullTransportNode { const originalSegment = node.s let newSegment: TransportSegment @@ -98,7 +94,7 @@ function fillInFallbackTransportTree( newSegment = { n: originalSegment.n, t: originalSegment.t, - k: getCacheKeyForDynamicParam(paramValue, renderedSearch), + k: getCacheKeyForDynamicParam(paramValue), s: originalSegment.s, } doesAppearInURL = true @@ -120,8 +116,7 @@ function fillInFallbackTransportTree( fillInFallbackTransportTree( childNode, pathnameParts, - childPathnamePartsIndex, - renderedSearch + childPathnamePartsIndex ) ) } @@ -206,22 +201,18 @@ function stripClientOnlyDataFromFlightRouterState( result[4] = prefetchHints } - // Everything else is used only by the client and is not needed for requests. + // Leave out the page's search params. They're only used by the client, and + // request headers may be forwarded through redirects. return result } /** * Strips client-only data from segments: - * - Search parameters from __PAGE__ segments * - staticSiblings from dynamic segment tuples (only needed for client-side * prefetch reuse decisions) */ function stripClientOnlyDataFromSegment(segment: Segment): Segment { if (typeof segment === 'string') { - // Strip search params from __PAGE__ segments - if (segment.startsWith(PAGE_SEGMENT_KEY + '?')) { - return PAGE_SEGMENT_KEY - } return segment } // Dynamic segment tuple: [paramName, paramCacheKey, paramType, staticSiblings] diff --git a/packages/next/src/client/route-params.ts b/packages/next/src/client/route-params.ts index 90a6d75a93a5..5762196a49f3 100644 --- a/packages/next/src/client/route-params.ts +++ b/packages/next/src/client/route-params.ts @@ -1,9 +1,5 @@ import type { DynamicParamTypesShort } from '../shared/lib/app-router-types' -import { - addSearchParamsIfPageSegment, - DEFAULT_SEGMENT_KEY, - PAGE_SEGMENT_KEY, -} from '../shared/lib/segment' +import { DEFAULT_SEGMENT_KEY, PAGE_SEGMENT_KEY } from '../shared/lib/segment' import { ROOT_SEGMENT_REQUEST_KEY } from '../shared/lib/segment-cache/segment-value-encoding' import { NEXT_REWRITTEN_PATH_HEADER, @@ -19,9 +15,19 @@ import type { } from './components/segment-cache/cache-key' import type { RSCResponse } from './components/router-reducer/fetch-server-response' import type { ParsedUrlQuery } from 'querystring' +import { getRenderedSearch as getRenderedSearchFromQuery } from '../shared/lib/router/utils/querystring' export type RouteParamValue = string | Array | null +export function normalizeRenderedSearch(search: string): NormalizedSearch { + // The same search params should produce the same string whether they came + // from the URL, a rewrite header, or the response (e.g. '+' vs '%20'). Only + // used for the rendered search. Request URLs keep their original form, + // since middleware and rewrites may depend on it. + const query = urlSearchParamsToParsedUrlQuery(new URLSearchParams(search)) + return getRenderedSearchFromQuery(query) as NormalizedSearch +} + export function getRenderedSearch( response: RSCResponse | Response ): NormalizedSearch { @@ -30,14 +36,13 @@ export function getRenderedSearch( // the response will include a header that gives the rewritten search query. const rewrittenQuery = response.headers.get(NEXT_REWRITTEN_QUERY_HEADER) if (rewrittenQuery !== null) { - return ( - rewrittenQuery === '' ? '' : '?' + rewrittenQuery - ) as NormalizedSearch + return normalizeRenderedSearch(rewrittenQuery) } // If the header is not present, there was no rewrite, so we use the search // query of the response URL. - return urlToUrlWithoutFlightMarker(new URL(response.url)) - .search as NormalizedSearch + return normalizeRenderedSearch( + urlToUrlWithoutFlightMarker(new URL(response.url)).search + ) } export function getRenderedPathname( @@ -181,7 +186,7 @@ export function doesStaticSegmentAppearInURL(segment: string): boolean { // Otherwise, we wouldn't need this special case because pages are // always leaf nodes. // TODO: Investigate why the loader produces these fake page segments. - segment.startsWith(PAGE_SEGMENT_KEY) || + segment === PAGE_SEGMENT_KEY || // Route groups. (segment[0] === '(' && segment.endsWith(')')) || segment === DEFAULT_SEGMENT_KEY || @@ -195,21 +200,13 @@ export function doesStaticSegmentAppearInURL(segment: string): boolean { } export function getCacheKeyForDynamicParam( - paramValue: RouteParamValue, - renderedSearch: NormalizedSearch + paramValue: RouteParamValue ): string { // This needs to match the logic in get-dynamic-param.ts, until we're able to // unify the various implementations so that these are always computed on // the client. if (typeof paramValue === 'string') { - // TODO: Refactor or remove this helper function to accept a string rather - // than the whole segment type. Also we can probably just append the - // search string instead of turning it into JSON. - const pageSegmentWithSearchParams = addSearchParamsIfPageSegment( - paramValue, - urlSearchParamsToParsedUrlQuery(new URLSearchParams(renderedSearch)) - ) as string - return pageSegmentWithSearchParams + return paramValue } else if (paramValue === null) { return '' } else { diff --git a/packages/next/src/next-devtools/dev-overlay/components/instant-navs/instant-nav-cookie.ts b/packages/next/src/next-devtools/dev-overlay/components/instant-navs/instant-nav-cookie.ts index a455b619ad4c..0e5cb58d7083 100644 --- a/packages/next/src/next-devtools/dev-overlay/components/instant-navs/instant-nav-cookie.ts +++ b/packages/next/src/next-devtools/dev-overlay/components/instant-navs/instant-nav-cookie.ts @@ -50,7 +50,7 @@ export function formatRoutePattern(tree: FlightRouterState): string { // and route groups (parenthesized segments like "(marketing)") if ( segment !== '' && - !segment.startsWith('__PAGE__') && + segment !== '__PAGE__' && segment !== '__DEFAULT__' && !(segment.startsWith('(') && segment.endsWith(')')) ) { diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 854e9a7c6cfc..3062e321bcb5 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -73,6 +73,7 @@ import type { AnyStream } from './stream-ops' import { createRenderInBrowserAbortSignal } from './render-in-browser' import { getInstantTestBootstrapScriptContent } from './instant-test-bootstrap' import { stripInternalQueries } from '../internal-utils' +import { getRenderedSearch } from '../../shared/lib/router/utils/querystring' import { NEXT_HMR_REFRESH_HEADER, NEXT_ROUTER_PREFETCH_HEADER, @@ -2157,39 +2158,6 @@ function prepareInitialCanonicalUrl(url: RequestStore['url']) { return (url.pathname + url.search).split('/') } -function getRenderedSearch(query: NextParsedUrlQuery): string { - // Inlined implementation of querystring.encode, which is not available in - // the Edge runtime. - const pairs = [] - for (const key in query) { - const value = query[key] - if (value == null) continue - if (Array.isArray(value)) { - for (const v of value) { - pairs.push( - `${encodeURIComponent(key)}=${encodeURIComponent(String(v))}` - ) - } - } else { - pairs.push( - `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}` - ) - } - } - - // The result should match the format of a web URL's `search` property, since - // this is the format that's stored in the App Router state. - // TODO: We're a bit inconsistent about this. The x-nextjs-rewritten-query - // header omits the leading question mark. Should refactor to always do - // that instead. - if (pairs.length === 0) { - // If the search string is empty, return an empty string. - return '' - } - // Prepend '?' to the search params string. - return '?' + pairs.join('&') -} - // This is the data necessary to render when no SSR errors are encountered async function getRSCPayload( tree: LoaderTree, @@ -2456,7 +2424,6 @@ async function getErrorRSCPayload( ctx.missingPrefetchHintPolicy, partialPrefetching, getDynamicParamFromSegment, - query, ctx.renderOpts.notFoundParams ) // Attach the error shell as the root's render output. Vary params are not @@ -7818,7 +7785,6 @@ async function validateInstantConfigs( cache, loaderTree, ctx.getDynamicParamFromSegment, - ctx.query, depth, groupDepthForValidation, extraChunksSignal, diff --git a/packages/next/src/server/app-render/create-component-tree.tsx b/packages/next/src/server/app-render/create-component-tree.tsx index c7134cc5f1c2..8b535880257d 100644 --- a/packages/next/src/server/app-render/create-component-tree.tsx +++ b/packages/next/src/server/app-render/create-component-tree.tsx @@ -47,10 +47,7 @@ import type { UseCacheLayoutProps, UseCachePageProps, } from '../use-cache/use-cache-wrapper' -import { - addSearchParamsIfPageSegment, - DEFAULT_SEGMENT_KEY, -} from '../../shared/lib/segment' +import { DEFAULT_SEGMENT_KEY } from '../../shared/lib/segment' import { BOUNDARY_PREFIX, BOUNDARY_SUFFIX, @@ -470,10 +467,7 @@ async function createComponentTreeInternal( // The segment's identity on the wire. const transportSegment = segmentToTransportSegment( - addSearchParamsIfPageSegment( - segmentParam ? segmentParam.treeSegment : segment, - query - ) + segmentParam ? segmentParam.treeSegment : segment ) // Create object holding the parent params and current params @@ -600,7 +594,6 @@ async function createComponentTreeInternal( ctx.missingPrefetchHintPolicy, partialPrefetching, getDynamicParamFromSegment, - query, ctx.renderOpts.notFoundParams, rootLayoutIncludedAtThisLevelOrAbove ) diff --git a/packages/next/src/server/app-render/create-transport-tree-from-loader-tree.ts b/packages/next/src/server/app-render/create-transport-tree-from-loader-tree.ts index 0769a0bd41f5..9dbe805ac13b 100644 --- a/packages/next/src/server/app-render/create-transport-tree-from-loader-tree.ts +++ b/packages/next/src/server/app-render/create-transport-tree-from-loader-tree.ts @@ -13,7 +13,6 @@ import { segmentToTransportSegment, } from '../../shared/lib/rsc-transport' import type { GetDynamicParamFromSegment } from './app-render' -import { addSearchParamsIfPageSegment } from '../../shared/lib/segment' import type { AppSegmentConfig } from '../../build/segment-config/app/app-segment-config' import { getSegmentParam } from '../../shared/lib/router/utils/get-segment-param' @@ -168,7 +167,6 @@ async function createTransportTreeFromLoaderTreeImpl( missingPrefetchHintPolicy: MissingPrefetchHintPolicy, partialPrefetching: boolean, getDynamicParamFromSegment: GetDynamicParamFromSegment, - searchParams: any, didFindRootLayout: boolean, notFoundParams: readonly string[] | undefined ): Promise { @@ -205,7 +203,6 @@ async function createTransportTreeFromLoaderTreeImpl( missingPrefetchHintPolicy, partialPrefetching, getDynamicParamFromSegment, - searchParams, didFindRootLayout, notFoundParams ) @@ -220,9 +217,7 @@ async function createTransportTreeFromLoaderTreeImpl( } const node: PartialTransportNode = { - s: segmentToTransportSegment( - addSearchParamsIfPageSegment(treeSegment, searchParams) - ), + s: segmentToTransportSegment(treeSegment), } if (prefetchHints !== 0) { node.h = prefetchHints @@ -249,7 +244,6 @@ export async function createTransportTreeFromLoaderTree( missingPrefetchHintPolicy: MissingPrefetchHintPolicy, partialPrefetching: boolean, getDynamicParamFromSegment: GetDynamicParamFromSegment, - searchParams: any, notFoundParams: readonly string[] | undefined, // Whether a root layout was already found above this loader tree slice, so a // slice that starts below the root layout doesn't mark a sub-layout as the @@ -264,7 +258,6 @@ export async function createTransportTreeFromLoaderTree( missingPrefetchHintPolicy, partialPrefetching, getDynamicParamFromSegment, - searchParams, didFindRootLayout, notFoundParams ) @@ -282,7 +275,6 @@ export async function createFullTransportTreeFromLoaderTree( missingPrefetchHintPolicy: MissingPrefetchHintPolicy, partialPrefetching: boolean, getDynamicParamFromSegment: GetDynamicParamFromSegment, - searchParams: any, notFoundParams: readonly string[] | undefined ): Promise { // With emitSkippedData, every node carries data, which is what @@ -296,7 +288,6 @@ export async function createFullTransportTreeFromLoaderTree( missingPrefetchHintPolicy, partialPrefetching, getDynamicParamFromSegment, - searchParams, false, notFoundParams ) as Promise @@ -317,10 +308,6 @@ export async function createRouteTreePrefetch( // See note on createTransportTreeFromLoaderTree's didFindRootLayout. didFindRootLayout: boolean = false ): Promise { - // Search params should not be added to page segment's cache key during a - // route tree prefetch request, because they do not affect the structure of - // the route. The client cache has its own logic to handle search params. - const searchParams = {} return createTransportTreeFromLoaderTreeImpl( loaderTree, false, @@ -329,7 +316,6 @@ export async function createRouteTreePrefetch( missingPrefetchHintPolicy, partialPrefetching, getDynamicParamFromSegment, - searchParams, didFindRootLayout, notFoundParams ) diff --git a/packages/next/src/server/app-render/instant-validation/instant-config.tsx b/packages/next/src/server/app-render/instant-validation/instant-config.tsx index aec3dd4b06c5..a246dce24b3e 100644 --- a/packages/next/src/server/app-render/instant-validation/instant-config.tsx +++ b/packages/next/src/server/app-render/instant-validation/instant-config.tsx @@ -27,11 +27,7 @@ import { InvariantError } from '../../../shared/lib/invariant-error' */ export function isImplicitValidationSegment(segment: Segment): boolean { const key = typeof segment === 'string' ? segment : segment[0] - return ( - key === PAGE_SEGMENT_KEY || - key.startsWith(PAGE_SEGMENT_KEY) || - key === DEFAULT_SEGMENT_KEY - ) + return key === PAGE_SEGMENT_KEY || key === DEFAULT_SEGMENT_KEY } /** diff --git a/packages/next/src/server/app-render/instant-validation/instant-validation.tsx b/packages/next/src/server/app-render/instant-validation/instant-validation.tsx index 1b06f00062a6..fdf073c52934 100644 --- a/packages/next/src/server/app-render/instant-validation/instant-validation.tsx +++ b/packages/next/src/server/app-render/instant-validation/instant-validation.tsx @@ -54,7 +54,6 @@ import type { FlightComponentMod } from '../stream-ops' // eslint-disable-next-line import/no-extraneous-dependencies import { createFromNodeStream } from 'react-server-dom-webpack/client' import { - addSearchParamsIfPageSegment, isGroupSegment, PAGE_SEGMENT_KEY, DEFAULT_SEGMENT_KEY, @@ -64,7 +63,6 @@ import { isFrameworkErrorRoute, isImplicitValidationSegment, } from './instant-config' -import type { NextParsedUrlQuery } from '../../request-meta' const filterStackFrame = process.env.NODE_ENV !== 'production' @@ -134,8 +132,6 @@ function traverseTransportNodeSegments( return } for (const [parallelRouteKey, childNode] of children) { - // NOTE: if this is a __PAGE__ segment, it might have search params appended. - // Whoever reads from the cache needs to append them as well. const childPath = createChildSegmentPath( path, parallelRouteKey, @@ -893,7 +889,7 @@ function segmentConsumesURLDepth(segment: Segment): boolean { if (typeof segment !== 'string') return true // Route groups, pages, defaults, and not-found don't consume a depth. if ( - segment.startsWith(PAGE_SEGMENT_KEY) || + segment === PAGE_SEGMENT_KEY || isGroupSegment(segment) || segment === DEFAULT_SEGMENT_KEY || segment === NOT_FOUND_SEGMENT_KEY @@ -1021,7 +1017,6 @@ export async function createCombinedPayloadAtDepth( cache: SegmentCache, initialLoaderTree: LoaderTree, getDynamicParamFromSegment: GetDynamicParamFromSegment, - query: NextParsedUrlQuery | null, depth: number, groupDepth: number, releaseSignal: AbortSignal, @@ -1074,8 +1069,7 @@ export async function createCombinedPayloadAtDepth( if (dynamicParam) { return dynamicParam.treeSegment } - const segment = loaderTree[0] - return query ? addSearchParamsIfPageSegment(segment, query) : segment + return loaderTree[0] } async function buildSharedTransportTree( diff --git a/packages/next/src/server/app-render/types.ts b/packages/next/src/server/app-render/types.ts index 90ebb79bcc36..ecffc3476415 100644 --- a/packages/next/src/server/app-render/types.ts +++ b/packages/next/src/server/app-render/types.ts @@ -79,7 +79,8 @@ export const flightRouterStateSchema: s.Describe = s.tuple([ ]) ) ), - s.optional(s.number()), + s.optional(s.nullable(s.number())), + s.optional(s.string()), ]) export type ServerOnInstrumentationRequestError = ( diff --git a/packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx b/packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx index 9b10ff0450ff..b056daafe669 100644 --- a/packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx +++ b/packages/next/src/server/app-render/walk-tree-with-flight-router-state.tsx @@ -1,3 +1,5 @@ +import { PAGE_SEGMENT_KEY } from '../../shared/lib/segment' +import { getRenderedSearch } from '../../shared/lib/router/utils/querystring' import type { FlightRouterState, PrefetchHints, @@ -10,7 +12,6 @@ import { segmentToTransportSegment, } from '../../shared/lib/rsc-transport' import type { PreloadCallbacks } from './types' -import { matchSegment } from '../../client/components/match-segments' import type { LoaderTree } from '../lib/app-dir-module' import { getLinkAndScriptTags } from './get-css-inlined-link-tags' import { getPreloadableFonts } from './get-preloadable-fonts' @@ -21,7 +22,6 @@ import { } from './create-transport-tree-from-loader-tree' import type { AppRenderContext } from './app-render' import { hasLoadingComponentInTree } from './has-loading-component-in-tree' -import { addSearchParamsIfPageSegment } from '../../shared/lib/segment' import { createComponentTree } from './create-component-tree' /** @@ -37,6 +37,28 @@ export type NavigationResponseTree = { isHeadPartial: boolean } +function didRouteOrPathParamChange( + actualSegment: Segment, + requestedSegment: Segment +): boolean { + // The caller handles the page's search params separately. + if ( + typeof actualSegment === 'string' || + typeof requestedSegment === 'string' + ) { + // Static segments have to match exactly. + return actualSegment !== requestedSegment + } + + // Both segments are dynamic. Compare the param name, type, and value. The + // static sibling hints (index 3) aren't part of the segment's identity. + return ( + actualSegment[0] !== requestedSegment[0] || + actualSegment[2] !== requestedSegment[2] || + actualSegment[1] !== requestedSegment[1] + ) +} + /** * Use router state to decide at what common layout to render the page. * This can either be the common layout between two pages or a specific place to start rendering from using the "refetch" marker in the tree. @@ -111,10 +133,9 @@ export async function walkTreeWithFlightRouterState({ [segmentParam.param]: segmentParam.value, } : parentParams - const actualSegment: Segment = addSearchParamsIfPageSegment( - segmentParam ? segmentParam.treeSegment : segment, - query - ) + const actualSegment: Segment = segmentParam + ? segmentParam.treeSegment + : segment /** * Decide if the current segment is where rendering has to start. @@ -122,8 +143,13 @@ export async function walkTreeWithFlightRouterState({ const renderComponentsOnThisLevel = // No further router state available !flightRouterState || - // Segment in router state does not match current segment - !matchSegment(actualSegment, flightRouterState[0]) || + // Route structure or path param changed + didRouteOrPathParamChange(actualSegment, flightRouterState[0]) || + // Normal requests leave out the page's search params, so treat a missing + // value as empty. HMR requests include them. + (actualSegment === PAGE_SEGMENT_KEY && + getRenderedSearch(query) !== + (flightRouterState[5] === undefined ? '' : flightRouterState[5])) || // Explicit refresh flightRouterState[3] === 'refetch' @@ -182,7 +208,6 @@ export async function walkTreeWithFlightRouterState({ ctx.missingPrefetchHintPolicy, partialPrefetching, getDynamicParamFromSegment, - query, ctx.renderOpts.notFoundParams, rootLayoutIncluded ) @@ -214,7 +239,6 @@ export async function walkTreeWithFlightRouterState({ ctx.missingPrefetchHintPolicy, partialPrefetching, getDynamicParamFromSegment, - query, ctx.renderOpts.notFoundParams, rootLayoutIncluded ) diff --git a/packages/next/src/server/dev/on-demand-entry-handler.ts b/packages/next/src/server/dev/on-demand-entry-handler.ts index a7a4d1167c6d..1e5cb1e49fe0 100644 --- a/packages/next/src/server/dev/on-demand-entry-handler.ts +++ b/packages/next/src/server/dev/on-demand-entry-handler.ts @@ -147,7 +147,7 @@ function getEntrypointsFromTree( ? convertDynamicParamTypeToSyntax(segment[2], segment[0]) : segment - const isPageSegment = currentSegment.startsWith(PAGE_SEGMENT_KEY) + const isPageSegment = currentSegment === PAGE_SEGMENT_KEY const currentPath = [...parentPath, isPageSegment ? '' : currentSegment] diff --git a/packages/next/src/shared/lib/app-router-types.ts b/packages/next/src/shared/lib/app-router-types.ts index 28613c1790bb..2a8950ff3f40 100644 --- a/packages/next/src/shared/lib/app-router-types.ts +++ b/packages/next/src/shared/lib/app-router-types.ts @@ -170,9 +170,16 @@ export type FlightRouterState = [ /** * Bitmask of PrefetchHint flags. Encodes route structure metadata: * root layout, loading boundaries, instant configs, and prefetch strategy - * hints. Only set when non-zero. + * hints. Only set when non-zero. JSON encodes an omitted slot as null when + * a later slot is present. */ - prefetchHints?: number, + prefetchHints?: number | null, + /** + * The rendered search params of a page segment. An empty string means there + * are none. Only set on page nodes, and left out of request headers. + * TODO: Revisit this as part of the larger FlightRouterState refactor. + */ + renderedSearch?: string, ] /** diff --git a/packages/next/src/shared/lib/router/utils/querystring.ts b/packages/next/src/shared/lib/router/utils/querystring.ts index f4e9a036e071..a96ab0daa9f4 100644 --- a/packages/next/src/shared/lib/router/utils/querystring.ts +++ b/packages/next/src/shared/lib/router/utils/querystring.ts @@ -1,5 +1,38 @@ import type { ParsedUrlQuery } from 'querystring' +export function getRenderedSearch(query: ParsedUrlQuery): string { + // Inlined implementation of querystring.encode, which is not available in + // the Edge runtime. + const pairs = [] + for (const key in query) { + const value = query[key] + if (value == null) continue + if (Array.isArray(value)) { + for (const v of value) { + pairs.push( + `${encodeURIComponent(key)}=${encodeURIComponent(String(v))}` + ) + } + } else { + pairs.push( + `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}` + ) + } + } + + // The result should match the format of a web URL's `search` property, since + // this is the format that's stored in the App Router state. + // TODO: We're a bit inconsistent about this. The x-nextjs-rewritten-query + // header omits the leading question mark. Should refactor to always do + // that instead. + if (pairs.length === 0) { + // If the search string is empty, return an empty string. + return '' + } + // Prepend '?' to the search params string. + return '?' + pairs.join('&') +} + export function searchParamsToUrlQuery( searchParams: URLSearchParams ): ParsedUrlQuery { diff --git a/packages/next/src/shared/lib/rsc-transport.ts b/packages/next/src/shared/lib/rsc-transport.ts index ed541106f4aa..ade64df921af 100644 --- a/packages/next/src/shared/lib/rsc-transport.ts +++ b/packages/next/src/shared/lib/rsc-transport.ts @@ -1,3 +1,4 @@ +import { PAGE_SEGMENT_KEY } from './segment' /** * The transport format for RSC responses. * @@ -26,11 +27,8 @@ import type { VaryParamsIterable } from './segment-cache/vary-params-decoding' * Segment identity on the wire. A string for static segments; an object for * dynamic (parameterized) segments. * - * TODO: Page segments currently smuggle search params inside the string - * (`__PAGE__?{...}`, see addSearchParamsIfPageSegment). This convention is - * carried over as-is for now. Consider giving search params a dedicated - * field (or removing them from the response entirely, since the client - * already knows the rendered search from the response-level `q`). + * Page segments are always `__PAGE__`. The search params are sent separately, + * in the response's `q` field. */ export type TransportSegment = string | TransportDynamicSegment @@ -300,18 +298,20 @@ export function transportSegmentToSegment( /** * Derives a FlightRouterState from a transport tree. Used where the client * needs a router-state representation of a full response (e.g. the initial - * hydration payload). Render output (`d`) is not carried over; page segments - * keep their search params, which travel inside the segment string. + * hydration payload). Render output (`d`) is not carried over. */ export function transportNodeToFlightRouterState( - node: TransportNode + node: TransportNode, + renderedSearch: string ): FlightRouterState { const parallelRoutes: Record = {} const children = node.c if (children !== undefined) { for (const [parallelRouteKey, childNode] of children) { - parallelRoutes[parallelRouteKey] = - transportNodeToFlightRouterState(childNode) + parallelRoutes[parallelRouteKey] = transportNodeToFlightRouterState( + childNode, + renderedSearch + ) } } const flightRouterState: FlightRouterState = [ @@ -321,5 +321,8 @@ export function transportNodeToFlightRouterState( if (node.h !== undefined) { flightRouterState[4] = node.h } + if (flightRouterState[0] === PAGE_SEGMENT_KEY) { + flightRouterState[5] = renderedSearch + } return flightRouterState } diff --git a/packages/next/src/shared/lib/segment-cache/segment-value-encoding.ts b/packages/next/src/shared/lib/segment-cache/segment-value-encoding.ts index 54e0ba95f0c6..bc73f2274341 100644 --- a/packages/next/src/shared/lib/segment-cache/segment-value-encoding.ts +++ b/packages/next/src/shared/lib/segment-cache/segment-value-encoding.ts @@ -1,4 +1,3 @@ -import { PAGE_SEGMENT_KEY } from '../segment' import type { Segment as FlightRouterStateSegment } from '../app-router-types' // TypeScript trick to simulate opaque types, like in Flow. @@ -15,18 +14,6 @@ export function createSegmentRequestKeyPart( segment: FlightRouterStateSegment ): SegmentRequestKeyPart { if (typeof segment === 'string') { - if (segment.startsWith(PAGE_SEGMENT_KEY)) { - // The Flight Router State type sometimes includes the search params in - // the page segment. However, the Segment Cache tracks this as a separate - // key. So, we strip the search params here, and then add them back when - // the cache entry is turned back into a FlightRouterState. This is an - // unfortunate consequence of the FlightRouteState being used both as a - // transport type and as a cache key; we'll address this once more of the - // Segment Cache implementation has settled. - // TODO: We should hoist the search params out of the FlightRouterState - // type entirely, This is our plan for dynamic route params, too. - return PAGE_SEGMENT_KEY as SegmentRequestKeyPart - } const safeName = // TODO: FlightRouterState encodes Not Found routes as "/_not-found". // But params typically don't include the leading slash. We should use diff --git a/packages/next/src/shared/lib/segment.ts b/packages/next/src/shared/lib/segment.ts index 8d8117f598e6..4421892e3ba2 100644 --- a/packages/next/src/shared/lib/segment.ts +++ b/packages/next/src/shared/lib/segment.ts @@ -13,22 +13,6 @@ export function isParallelRouteSegment(segment: string) { return segment.startsWith('@') && segment !== '@children' } -export function addSearchParamsIfPageSegment( - segment: Segment, - searchParams: Record -) { - const isPageSegment = segment.includes(PAGE_SEGMENT_KEY) - - if (isPageSegment) { - const stringifiedQuery = JSON.stringify(searchParams) - return stringifiedQuery !== '{}' - ? PAGE_SEGMENT_KEY + '?' + stringifiedQuery - : PAGE_SEGMENT_KEY - } - - return segment -} - export function computeSelectedLayoutSegment( segments: string[] | null, parallelRouteKey: string @@ -70,7 +54,7 @@ export function getSelectedLayoutSegmentPath( let segmentValue = getSegmentValue(segment) - if (!segmentValue || segmentValue.startsWith(PAGE_SEGMENT_KEY)) { + if (!segmentValue || segmentValue === PAGE_SEGMENT_KEY) { return segmentPath } diff --git a/test/e2e/app-dir/app-static/app-static.test.ts b/test/e2e/app-dir/app-static/app-static.test.ts index c3eb319849d1..64ff0729a2ad 100644 --- a/test/e2e/app-dir/app-static/app-static.test.ts +++ b/test/e2e/app-dir/app-static/app-static.test.ts @@ -785,10 +785,8 @@ describe('app-dir static/dynamic handling', () => { ? await next.readFile('.next/server/app/blog/seb.prefetch.rsc') : await next.readFile('.next/server/app/blog/seb.rsc') - // During SSG, pages that correspond with dynamic routes shouldn't have any search - // parameters in the `__PAGE__` segment string. The only time we expect to see - // search parameters in the `__PAGE__` segment string is when the RSC data is - // requested from the client with search parameters. + // Page segments contain only structural identity. Search params are + // carried separately, including in responses for dynamic routes. expect(data).not.toContain('__PAGE__?') expect(data).toContain('__PAGE__') }) diff --git a/test/e2e/app-dir/segment-cache/search-params/app/search-params/target-page/page.tsx b/test/e2e/app-dir/segment-cache/search-params/app/search-params/target-page/page.tsx index b8829770a640..4d32ba27d8aa 100644 --- a/test/e2e/app-dir/segment-cache/search-params/app/search-params/target-page/page.tsx +++ b/test/e2e/app-dir/segment-cache/search-params/app/search-params/target-page/page.tsx @@ -1,4 +1,5 @@ import { Suspense } from 'react' +import { LinkAccordion } from '../../../components/link-accordion' async function Content({ searchParams }) { const { searchParam } = await searchParams @@ -11,6 +12,15 @@ export default async function Target({ searchParams }) {
+ + Change search params + + + Navigate with encoded and repeated search params +
) } diff --git a/test/e2e/app-dir/segment-cache/search-params/segment-cache-search-params.test.ts b/test/e2e/app-dir/segment-cache/search-params/segment-cache-search-params.test.ts index 420c024f3c95..57d15597eb08 100644 --- a/test/e2e/app-dir/segment-cache/search-params/segment-cache-search-params.test.ts +++ b/test/e2e/app-dir/segment-cache/search-params/segment-cache-search-params.test.ts @@ -11,6 +11,79 @@ describe('segment cache (search params)', () => { return } + it('prefetches new vary data for a query-only navigation from the active page', async () => { + let act: ReturnType + const browser = await next.browser( + '/search-params/target-page?searchParam=initial', + { + beforePageLoad(page) { + act = createRouterAct(page) + }, + } + ) + expect( + await browser.elementById('target-page-with-search-param').text() + ).toContain('Search param: initial') + await act( + async () => { + await browser + .elementByCss( + 'input[data-link-accordion="/search-params/target-page?searchParam=query_only"]' + ) + .click() + }, + { includes: 'Search param: query_only' } + ) + await act(async () => { + await browser + .elementByCss( + 'a[href="/search-params/target-page?searchParam=query_only"]' + ) + .click() + }, 'no-requests') + expect( + await browser.elementById('target-page-with-search-param').text() + ).toContain('Search param: query_only') + }) + + it('navigates to a prefetched page with encoded and repeated search params', async () => { + let act: ReturnType + const browser = await next.browser( + '/search-params/target-page?searchParam=initial', + { + beforePageLoad(page) { + act = createRouterAct(page) + }, + } + ) + + // Prefetch the shell before navigating. This URL uses both space encodings, + // unescaped punctuation, a bare key, and interleaved repeated values. + await act(async () => { + await browser + .elementByCss( + 'input[data-link-accordion="/search-params/target-page?searchParam=hello+world,/x&unused&searchParam=another%20value"]' + ) + .click() + }) + await act( + async () => { + await browser + .elementByCss( + 'a[href="/search-params/target-page?searchParam=hello+world,/x&unused&searchParam=another%20value"]' + ) + .click() + }, + { includes: 'Search param: hello world,/x,another value' } + ) + expect( + await browser.elementById('target-page-with-search-param').text() + ).toBe('Search param: hello world,/x,another value') + expect(new URL(await browser.url()).search).toBe( + '?searchParam=hello+world,/x&unused&searchParam=another%20value' + ) + }) + it('when fetching with PPR, does not include search params in the cache key', async () => { let act: ReturnType const browser = await next.browser('/search-params', { diff --git a/test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/layout.tsx b/test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/layout.tsx new file mode 100644 index 000000000000..ce7eff5165aa --- /dev/null +++ b/test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/layout.tsx @@ -0,0 +1,21 @@ +import { LinkAccordion } from '../../../../components/link-accordion' + +/** + * Navigation reuse: the page does not read searchParams but its metadata + * does. A query-only navigation must still fetch the head, because it depends + * on the query. + */ +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + <> +
    +
  • + + x=2 + +
  • +
+ {children} + + ) +} diff --git a/test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/loading.tsx b/test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/loading.tsx new file mode 100644 index 000000000000..c443db0168cf --- /dev/null +++ b/test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return
Loading metadata-query page...
+} diff --git a/test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/page.tsx b/test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/page.tsx new file mode 100644 index 000000000000..cd732fffecdf --- /dev/null +++ b/test/e2e/app-dir/segment-cache/vary-params/app/(main)/navigation-reuse/metadata-query/page.tsx @@ -0,0 +1,17 @@ +import { connection } from 'next/server' + +type SearchParams = { x?: string } + +export async function generateMetadata({ + searchParams, +}: { + searchParams: Promise +}) { + const { x } = await searchParams + return { title: `Query title: ${x}` } +} + +export default async function Page() { + await connection() + return

{`Server token: ${Math.random()}`}

+} diff --git a/test/e2e/app-dir/segment-cache/vary-params/vary-params.test.ts b/test/e2e/app-dir/segment-cache/vary-params/vary-params.test.ts index 12416c179a6a..2323cbf1648f 100644 --- a/test/e2e/app-dir/segment-cache/vary-params/vary-params.test.ts +++ b/test/e2e/app-dir/segment-cache/vary-params/vary-params.test.ts @@ -888,4 +888,39 @@ describe('segment cache - vary params', () => { ) } ) + + it('still fetches the head on a query-only navigation when the metadata read searchParams', async () => { + let act: ReturnType + const browser = await next.browser('/navigation-reuse/metadata-query?x=1', { + beforePageLoad(page: Playwright.Page) { + act = createRouterAct(page) + }, + }) + expect(await browser.eval('document.title')).toBe('Query title: 1') + const initialToken = await browser.elementById('server-token').text() + + await act(async () => { + await browser + .elementByCss( + 'input[data-link-accordion="/navigation-reuse/metadata-query?x=2"]' + ) + .click() + }, 'no-requests') + + // The head read the query, so it's fetched again. + await act( + async () => { + await browser + .elementByCss('a[href="/navigation-reuse/metadata-query?x=2"]') + .click() + }, + { includes: 'Query title: 2' } + ) + expect(await browser.eval('document.title')).toBe('Query title: 2') + // A dynamic render reports no dependency information, so the page is + // re-rendered along with the head. + expect(await browser.elementById('server-token').text()).not.toBe( + initialToken + ) + }) }) From 34fdbdccd1c314a2c7663459de51bbd376061f99 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 25 Sep 2026 00:25:28 -0400 Subject: [PATCH 11/13] Prefetch scheduler mirrors the navigation's tree walk (#98975) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prefetch scheduler's static walk decided at the parent which child was still part of the current page and which began the new part of the route, and it fetched the head through two functions of its own that copied the per-segment decision (a static attempt, then a runtime deopt if that wasn't enough) for a node that hangs off the route root instead of sitting in the tree. Give the walk the same shape as the navigation's. The walk over the part of the route the current page also has owns the comparison for its own node — first whether the route position still matches, then whether any of its params changed — and hands off to the walk over the new part of the route when either fails, so the root enters the shared walk like every other node. The per-segment decision, a static attempt or a runtime deopt, lives in one function that the new-part walk calls for every segment, and the runtime request's walk gets the same split between iterating the children and deciding for one node. The head is a one-node route tree like any other segment now, so it goes through the same functions: the new-part static walk and the direct runtime fetch it already had. This is a pure restructuring and issues the same requests as before: a segment the navigation keeps is still prefetched at the ordinary static tier without a runtime deopt, the head is still fetched whenever anything is, and a chain of inlined segments is still dropped where a Shell-phase walk crosses from a kept node into the new part. A later change uses the shape to prefetch only what the navigation would fetch. --- .../client/components/segment-cache/cache.ts | 5 +- .../components/segment-cache/scheduler.ts | 725 ++++++++++-------- 2 files changed, 393 insertions(+), 337 deletions(-) diff --git a/packages/next/src/client/components/segment-cache/cache.ts b/packages/next/src/client/components/segment-cache/cache.ts index ad4aadac3836..4cfc9325bfd5 100644 --- a/packages/next/src/client/components/segment-cache/cache.ts +++ b/packages/next/src/client/components/segment-cache/cache.ts @@ -3242,8 +3242,9 @@ function writeServerResponseIntoCache( ? now + getStaleTimeMs(headData.staleTimeSeconds) : staleAt - // A head has no loading boundary. Match pingRuntimeHead, which spawns - // LoadingBoundary head entries using the concrete Full strategy. + // A head has no loading boundary. Match the scheduler, which spawns + // LoadingBoundary head entries using the concrete Full strategy (see the + // head's runtime fetch in pingRootRouteTree). const headFetchStrategy = fetchStrategy === FetchStrategy.LoadingBoundary ? FetchStrategy.Full diff --git a/packages/next/src/client/components/segment-cache/scheduler.ts b/packages/next/src/client/components/segment-cache/scheduler.ts index 1b93c04fe951..8ff82ba25449 100644 --- a/packages/next/src/client/components/segment-cache/scheduler.ts +++ b/packages/next/src/client/components/segment-cache/scheduler.ts @@ -44,10 +44,7 @@ import { } from './cache' import type { CacheMap } from './cache-map' import type { NavigationLockPrefetch } from './navigation-testing-lock' -import { - HEAD_REQUEST_KEY, - type SegmentRequestKey, -} from '../../../shared/lib/segment-cache/segment-value-encoding' +import type { SegmentRequestKey } from '../../../shared/lib/segment-cache/segment-value-encoding' import { cleanup } from './lru' const scheduleMicrotask = @@ -917,7 +914,36 @@ function pingRootRouteTree( return PrefetchTaskExitStatus.Done } - pingStaticHead(now, task, route, staticWalkStrategy) + // The head is a one-node tree beside the route tree (see + // createMetadataRouteTree in cache.ts); it takes the same walk as + // a segment the current page doesn't have. If the head was inlined + // into a page's bundle (HeadOutlined is NOT set on the root), skip + // the standalone walk — the head data will arrive as part of that + // page's response, and its runtime-completeness signal is carried + // by that page's own entries. + const head = route.root.head + if ( + !process.env.__NEXT_PREFETCH_INLINING || + (route.root.tree.prefetchHints & PrefetchHint.HeadOutlined) !== 0 || + // An inlined head that can't attempt a static fetch still deopts + // to the runtime request (the first check of the decision point + // in pingSegmentInCacheComponentsTree); only the static fetch + // itself is skipped for an inlined head. + (walkCanUseRuntimeRequests(staticWalkStrategy, route) && + !shouldSegmentAttemptStaticRequest(staticWalkStrategy, head)) + ) { + const headExitStatus = pingNewPartOfCacheComponentsTree( + now, + task, + route, + head, + null, + staticWalkStrategy + ) + if (headExitStatus === PrefetchTaskExitStatus.InProgress) { + return PrefetchTaskExitStatus.InProgress + } + } const exitStatus = pingSharedPartOfCacheComponentsTree( now, @@ -933,7 +959,7 @@ function pingRootRouteTree( return PrefetchTaskExitStatus.InProgress } - // `pingNewPartOfCacheComponentsTree` may have determined that + // `pingSegmentInCacheComponentsTree` may have determined that // we need to do a runtime prefetch for one or more segments. // Bail out early if runtime prefetches are not permitted for this route. if (walkCanUseRuntimeRequests(staticWalkStrategy, route)) { @@ -944,19 +970,32 @@ function pingRootRouteTree( // spawnedRuntimePrefetches was populated during the traversal // above: every subtree in the new part of the tree that needs a - // runtime prefetch — plus, during the Shell phase, the head, if - // its static attempt was insufficient (see above). + // runtime prefetch, the head included — it registers under its + // own request key, like any segment, when its own static attempt + // was insufficient or never happened. // // If it's null, nothing in the new part of the tree is a candidate // for runtime prefetching, and we don't fetch the head, either — - // the head is runtime prefetched only if one of the segments is. + // the head is runtime prefetched only if something is. const spawnedRuntimePrefetches = task.spawnedRuntimePrefetches if (spawnedRuntimePrefetches !== null) { const spawnedEntries = new Map< SegmentRequestKey, PendingSegmentCacheEntry >() - pingRuntimeHead(now, task, route, spawnedEntries, runtimeStrategy) + // The head has no position in the request tree — a runtime + // response carries it beside the segments (see + // writeServerResponseIntoCache in cache.ts) — so the head's + // own request tree is discarded. + pingRouteTreeAndIncludeDynamicData( + now, + task, + route, + head, + false, + spawnedEntries, + runtimeStrategy + ) const requestTree = pingRuntimePrefetches( now, task, @@ -1002,7 +1041,24 @@ function pingRootRouteTree( SegmentRequestKey, PendingSegmentCacheEntry >() - pingRuntimeHead(now, task, route, spawnedEntries, fetchStrategy) + // The head has no position in the request tree — a runtime response + // carries it beside the segments (see writeServerResponseIntoCache + // in cache.ts) — so the head's own request tree is discarded. + const head = route.root.head + pingRouteTreeAndIncludeDynamicData( + now, + task, + route, + head, + false, + spawnedEntries, + // When prefetching the head, there's no difference between Full + // and LoadingBoundary: the head has no loading boundary, so a + // LoadingBoundary request would skip it. + fetchStrategy === FetchStrategy.LoadingBoundary + ? FetchStrategy.Full + : fetchStrategy + ) const dynamicRequestTree = diffRouteTreeAgainstCurrent( now, task, @@ -1059,84 +1115,6 @@ type SegmentBundle = { parent: SegmentBundle | null } -/** - * Prefetches the Head data for a page (metadata, viewport). The Head is not - * really a route segment, in the sense that it doesn't appear in the route - * tree, but we store it in the cache as if it were, using a special key. - * - * Symmetric with the per-segment decision point in - * pingNewPartOfCacheComponentsTree: the head deopts to the runtime prefetch - * path either when it requires runtime completeness and no static attempt is - * happening, or when a fulfilled static head entry reported that a runtime - * request would return more content than the entry contains. Deopting - * registers the head under its wire request key, which makes the runtime - * gate in pingRootRouteTree fire even when every tree segment was - * sufficient; pingRuntimeHead performs the actual head work. - */ -function pingStaticHead( - now: number, - task: PrefetchTask, - route: FulfilledRouteCacheEntry, - // The per-pass static walk strategy; see pingRootRouteTree where - // it's derived. - fetchStrategy: FetchStrategy.PPR | FetchStrategy.StaticShell -): void { - const headCanUseRuntimeRequests = walkCanUseRuntimeRequests( - fetchStrategy, - route - ) - if ( - headCanUseRuntimeRequests && - // The head is not a tree node — it hangs off the route root — so the - // static-attempt hints are read from the root's node. (Segments read the - // hints from their own node; see `pingNewPartOfCacheComponentsTree.`) - !shouldSegmentAttemptStaticRequest(fetchStrategy, route.root.tree) - ) { - // No static attempt: the head arrives via the runtime request instead. - addSpawnedRuntimePrefetch(task, HEAD_REQUEST_KEY) - return - } - - if ( - // If the head was inlined into a page's bundle (HeadOutlined is NOT set - // on the root), skip the standalone fetch — the head data will arrive - // as part of that page's response, and its runtime-completeness signal - // is carried by that page's own entries. - process.env.__NEXT_PREFETCH_INLINING && - !(route.root.tree.prefetchHints & PrefetchHint.HeadOutlined) - ) { - return - } - - const segments: SegmentBundle = { - tree: route.root.head, - entry: readOrCreateSegmentCacheEntry( - now, - task.segmentCacheMap, - fetchStrategy, - route.root.head - ), - parent: null, - } - const needsRuntimeRequest = pingSegmentBundle( - now, - task, - route, - task.key, - route.root.head, - segments, - fetchStrategy, - true - ) - if (headCanUseRuntimeRequests && needsRuntimeRequest) { - // The static attempt was insufficient for the head. Deopt to a - // runtime prefetch. (Outside of runtime-completeness contexts the - // head's signal is unused — a partial static head is filled in by the - // navigation-time request, as with any other static segment.) - addSpawnedRuntimePrefetch(task, HEAD_REQUEST_KEY) - } -} - /** * Whether the task can use runtime requests to prefetch the content. * @@ -1280,8 +1258,9 @@ function isShellEntryEligibleForStaticAttempt( } /** - * Register a subtree root (or the head's wire key) for the batched - * runtime request issued by the gate at the end of pingRootRouteTree. + * Register a subtree root (the head is one, under its own request key) for + * the batched runtime request issued by the gate at the end of + * pingRootRouteTree. */ function addSpawnedRuntimePrefetch( task: PrefetchTask, @@ -1294,62 +1273,85 @@ function addSpawnedRuntimePrefetch( } } -function pingRuntimeHead( - now: number, - task: PrefetchTask, - route: FulfilledRouteCacheEntry, - spawnedEntries: Map, - fetchStrategy: - | FetchStrategy.Full - | FetchStrategy.PPRRuntime - | FetchStrategy.RuntimeShell - | FetchStrategy.LoadingBoundary -): void { - pingRouteTreeAndIncludeDynamicData( - now, - task, - route, - route.root.head, - false, - spawnedEntries, - // When prefetching the head, there's no difference between Full - // and LoadingBoundary - fetchStrategy === FetchStrategy.LoadingBoundary - ? FetchStrategy.Full - : fetchStrategy - ) -} - // TODO: Rename dynamic -> runtime throughout this module +/** + * The static walk over the part of the target route that also exists on the + * current page: the current page's node and the target route's node at the + * same position in the tree. It mirrors the navigation's traversal order (see + * updateRenderTreeOnNavigation in render-tree.ts): first whether the route + * position still matches — if not, this node begins the new part of the route + * (pingNewPartOfCacheComponentsTree) — and then whether any of the node's + * param values changed — if so, the node and everything below it begin the + * new part of the route too. The walk does not consult which params a + * segment read, so it prefetches more than the navigation replaces: the + * navigation keeps data whose read params did not change and decides each + * descendant on its own. A node the walk keeps is prefetched at the ordinary + * static tier. + * Its children continue here wherever the current page has a child in the + * same slot; a child in a slot the current page doesn't have enters the new + * part of the route directly. + * + * Bundle chains must not cross the strategy boundary: a kept node walks at + * PPR while a Shell-phase new part walks at StaticShell, and a chain spanning + * both would fulfill the kept node's concrete-path entry with shell-variant + * data. Nor may the chain be finished by fetching the new-part node at PPR + * — that would prefetch new-part segments at the concrete tier during the + * Shell phase, which only the Speculative phase is allowed to do. So the + * chain is dropped wherever the walk hands off to the new part during a + * StaticShell walk, exactly like the drop sites in + * pingSegmentInCacheComponentsTree: nothing in a dropped chain was upgraded + * to Pending, so no entry is stranded, and the inlined kept data is fetched + * by the Speculative pass whenever its walk of the new part permits the + * child fetch. + */ function pingSharedPartOfCacheComponentsTree( now: number, task: PrefetchTask, route: FulfilledRouteCacheEntry, - oldTree: RouteTree, + currentTree: RouteTree, newTree: RouteTree, parentBundle: SegmentBundle | null, // The per-pass static walk strategy; see pingRootRouteTree where // it's derived. fetchStrategy: FetchStrategy.PPR | FetchStrategy.StaticShell ): PrefetchTaskExitStatus.InProgress | PrefetchTaskExitStatus.Done { - // When Cache Components is enabled (or PPR, or a fully static route when PPR - // is disabled; those cases are treated equivalently to Cache Components), we - // start by prefetching each segment individually. Once we reach the "new" - // part of the tree — the part that doesn't exist on the current page — we - // may choose to switch to a runtime prefetch instead, based on the - // information sent by the server in the route tree. - // - // The traversal starts in the "shared" part of the tree. Once we reach the - // "new" part of the tree, we switch to a different traversal, - // pingNewPartOfCacheComponentsTree. - - // The shared part of the tree always performs the ordinary static (PPR) - // prefetch, regardless of phase. Phase-specific strategies — the runtime - // shell request and the Shell phase's StaticShell walk — apply only to the - // new part of the tree, so the per-pass walk strategy is irrelevant here. - // (The needs-runtime signal is ignored: shared segments are already - // rendered on the current page, so a runtime prefetch has nothing to add.) + if (!doesRouteStructureMatch(currentTree, newTree)) { + // We're entering the part of the target route that doesn't exist on the + // current page. + return pingNewPartOfCacheComponentsTree( + now, + task, + route, + newTree, + fetchStrategy === FetchStrategy.StaticShell ? null : parentBundle, + fetchStrategy + ) + } + if ( + compareParams(currentTree.varyPath, newTree.varyPath) !== ParamsChange.None + ) { + // A param changed. The navigation replaces only the data that read it and + // decides each descendant on its own (see updateRenderTreeOnNavigation in + // render-tree.ts); this walk doesn't know what each segment read, so it + // prefetches the whole subtree. + return pingNewPartOfCacheComponentsTree( + now, + task, + route, + newTree, + fetchStrategy === FetchStrategy.StaticShell ? null : parentBundle, + fetchStrategy + ) + } + + // The navigation keeps this segment's current data. A kept segment always + // performs the ordinary static (PPR) prefetch, regardless of phase. + // Phase-specific strategies — the runtime shell request and the Shell + // phase's StaticShell walk — apply only to the new part of the tree, so the + // per-pass walk strategy is irrelevant here. (The needs-runtime signal is + // ignored: kept segments are already rendered on the current page, so a + // runtime prefetch has nothing to add.) const bundleInProgress = accumulateSegmentBundle( now, task, @@ -1360,16 +1362,16 @@ function pingSharedPartOfCacheComponentsTree( true ).bundle - // Recursively ping the children. - const oldSlots = oldTree.slots + // Recursively ping the children, continuing in lockstep with the current + // page wherever it has a child in the same slot. const newTreeChildren = newTree.slots if (newTreeChildren !== null) { + const currentSlots = currentTree.slots for (const [parallelRouteKey, newTreeChild] of newTreeChildren) { if (!hasNetworkBandwidth(task)) { // Stop prefetching segments until there's more bandwidth. return PrefetchTaskExitStatus.InProgress } - const oldTreeChild = oldSlots?.get(parallelRouteKey) // Only pass the bundle to the child that accepts it. A parent is // only ever bundled into one child. const bundleForChild = @@ -1378,48 +1380,30 @@ function pingSharedPartOfCacheComponentsTree( newTreeChild.prefetchHints & PrefetchHint.ParentInlinedIntoSelf ? bundleInProgress : null - let childExitStatus - if ( - oldTreeChild !== undefined && - doesRouteStructureMatch(oldTreeChild, newTreeChild) && - compareParams(oldTreeChild.varyPath, newTreeChild.varyPath) === - ParamsChange.None - ) { - // We're still in the "shared" part of the tree. + let currentTreeChild: RouteTree | undefined = undefined + if (currentSlots !== null) { + currentTreeChild = currentSlots.get(parallelRouteKey) + } + let childExitStatus: + | PrefetchTaskExitStatus.InProgress + | PrefetchTaskExitStatus.Done + if (currentTreeChild !== undefined) { childExitStatus = pingSharedPartOfCacheComponentsTree( now, task, route, - oldTreeChild, + currentTreeChild, newTreeChild, bundleForChild, fetchStrategy ) } else { - // We've entered the "new" part of the tree. Switch - // traversal functions. - // - // Bundle chains must not cross the strategy boundary: the shared - // part walks at PPR while a Shell-phase new part walks at - // StaticShell, and a chain spanning both would fulfill the shared - // parent's concrete-path entry with shell-variant data. Nor may we - // finish the chain by fetching the new-part child at PPR here — - // that would prefetch new-part segments at the concrete tier - // during the Shell phase, which only the Speculative phase is - // allowed to do. So drop the bundle instead, exactly like the - // Speculative walk's subtree bail does when a chain crosses into a - // subtree it skips: nothing in a dropped chain was upgraded to - // Pending, so no entry is stranded, and the inlined shared data is - // fetched by the Speculative pass whenever its walk of the new - // part permits the child fetch. - const bundleForNewPart = - fetchStrategy === FetchStrategy.StaticShell ? null : bundleForChild childExitStatus = pingNewPartOfCacheComponentsTree( now, task, route, newTreeChild, - bundleForNewPart, + fetchStrategy === FetchStrategy.StaticShell ? null : bundleForChild, fetchStrategy ) } @@ -1430,9 +1414,18 @@ function pingSharedPartOfCacheComponentsTree( } } + // The static attempt was sufficient for this segment (each child is its + // own decision point) — or parts of it are still in flight, in which case + // the task is blocked and the decision re-runs against the received + // responses. return PrefetchTaskExitStatus.Done } +/** + * The static walk over the part of the target route that doesn't exist on + * the current page. Nothing here is compared against the current tree: every + * segment goes through the per-segment decision point. + */ function pingNewPartOfCacheComponentsTree( now: number, task: PrefetchTask, @@ -1443,33 +1436,104 @@ function pingNewPartOfCacheComponentsTree( // it's derived. fetchStrategy: FetchStrategy.PPR | FetchStrategy.StaticShell ): PrefetchTaskExitStatus.InProgress | PrefetchTaskExitStatus.Done { - // We're now prefetching in the "new" part of the tree, the part that - // doesn't exist on the current page. (In other words, we're deeper than - // the shared layouts.) Segments in here default to being prefetched - // statically, at the per-pass strategy derived in pingRootRouteTree. - // - // This is where we decide whether we should use runtime requests, if the walk - // is allowed to do so (see `walkCanUseRuntimeRequests`). - // - // If runtime requests are allowed, but the segment's node has one of the - // `ShouldAttemptStatic{Shell,Prefetch}` hints set (either because the build-time - // prerender accessed no runtime data, or because of `ensureStatic`), then its - // subtree should be prefetched statically first. - // However, the hint may be stale after a revalidation, so we'll also check the - // `needsRuntimeRequest` promise on the static response, and will follow up with - // a runtime request if needed. - // Pending responses block the task, so the attempt is serial, never raced: - // static attempt → observe → runtime (if needed). - // - // The static hints and `needsRuntimeRequest` have no effect if runtime requests - // are not allowed (i.e. outside of Partial Prefetching). + const accumulation = pingSegmentInCacheComponentsTree( + now, + task, + route, + tree, + parentBundle, + fetchStrategy + ) + if (accumulation === null) { + return PrefetchTaskExitStatus.Done + } + const bundleInProgress = accumulation.bundle + + // Recursively ping the children. + const treeChildren = tree.slots + if (treeChildren !== null) { + for (const treeChild of treeChildren.values()) { + if (!hasNetworkBandwidth(task)) { + // Stop prefetching segments until there's more bandwidth. + return PrefetchTaskExitStatus.InProgress + } + // Only pass the bundle to the child that accepts it. A parent is + // only ever bundled into one child. + const bundleForChild = + process.env.__NEXT_PREFETCH_INLINING && + bundleInProgress !== null && + treeChild.prefetchHints & PrefetchHint.ParentInlinedIntoSelf + ? bundleInProgress + : null + const childExitStatus = pingNewPartOfCacheComponentsTree( + now, + task, + route, + treeChild, + bundleForChild, + fetchStrategy + ) + if (childExitStatus === PrefetchTaskExitStatus.InProgress) { + // Child yielded without finishing. + return PrefetchTaskExitStatus.InProgress + } + } + } + // The static attempt was sufficient for this segment (each child is its + // own decision point) — or parts of it are still in flight, in which case + // the task is blocked and the decision re-runs against the received + // responses. + return PrefetchTaskExitStatus.Done +} + +/** + * The per-segment decision point of the static walk: the one place that + * decides how a segment in the new part of the route — one the navigation + * won't keep — is prefetched (pingNewPartOfCacheComponentsTree). + * + * When Cache Components is enabled (or PPR, or a fully static route when PPR + * is disabled; those cases are treated equivalently to Cache Components), we + * prefetch each segment individually, statically, at the per-pass strategy + * derived in pingRootRouteTree. + * + * This is where we decide whether we should use runtime requests, if the walk + * is allowed to do so (see `walkCanUseRuntimeRequests`). + * + * If runtime requests are allowed, but the segment's node has one of the + * `ShouldAttemptStatic{Shell,Prefetch}` hints set (either because the build-time + * prerender accessed no runtime data, or because of `ensureStatic`), then its + * subtree should be prefetched statically first. + * However, the hint may be stale after a revalidation, so we'll also check the + * `needsRuntimeRequest` promise on the static response, and will follow up with + * a runtime request if needed. + * Pending responses block the task, so the attempt is serial, never raced: + * static attempt → observe → runtime (if needed). + * + * The static hints and `needsRuntimeRequest` have no effect if runtime requests + * are not allowed (i.e. outside of Partial Prefetching). + * + * Returns the segment's bundle accumulation when the walk should continue + * into its children, and null when the walk stops at this segment: the link + * needs no speculative prefetch, or the segment deopted and the batched + * runtime request covers the whole subtree. + */ +function pingSegmentInCacheComponentsTree( + now: number, + task: PrefetchTask, + route: FulfilledRouteCacheEntry, + tree: RouteTree, + parentBundle: SegmentBundle | null, + // The per-pass static walk strategy; see pingRootRouteTree where + // it's derived. + fetchStrategy: FetchStrategy.PPR | FetchStrategy.StaticShell +): { bundle: SegmentBundle | null; needsRuntimeRequest: boolean } | null { // In PPF, links may skip speculative prefetching if they only need a shell. if ( fetchStrategy === FetchStrategy.PPR && !needsSpeculativePrefetch(task.fetchStrategy, route.root.tree.prefetchHints) ) { - return PrefetchTaskExitStatus.Done + return null } // Constant for the whole pass; recomputed here only because the walk is @@ -1500,7 +1564,7 @@ function pingNewPartOfCacheComponentsTree( fetchStrategy ) } - return PrefetchTaskExitStatus.Done + return null } // Prefetch this segment and its subtree statically, using the normal @@ -1514,7 +1578,6 @@ function pingNewPartOfCacheComponentsTree( fetchStrategy, true ) - const bundleInProgress = accumulation.bundle if (canUseRuntimeRequests && accumulation.needsRuntimeRequest) { // The static attempt for this segment was insufficient. Stop the walk @@ -1525,44 +1588,10 @@ function pingNewPartOfCacheComponentsTree( // upgraded to Pending, so no entry is stranded blocking the task, and // Empty entries in the dropped chain are re-fetched by a later pass.) addSpawnedRuntimePrefetch(task, tree.requestKey) - return PrefetchTaskExitStatus.Done - } - - if (tree.slots !== null) { - if (!hasNetworkBandwidth(task)) { - // Stop prefetching segments until there's more bandwidth. - return PrefetchTaskExitStatus.InProgress - } - // Recursively ping the children. - for (const childTree of tree.slots.values()) { - // Only pass the bundle to the child that accepts it. A parent is - // only ever bundled into one child. - const bundleForChild = - process.env.__NEXT_PREFETCH_INLINING && - bundleInProgress !== null && - childTree.prefetchHints & PrefetchHint.ParentInlinedIntoSelf - ? bundleInProgress - : null - const childResult = pingNewPartOfCacheComponentsTree( - now, - task, - route, - childTree, - bundleForChild, - fetchStrategy - ) - if (childResult === PrefetchTaskExitStatus.InProgress) { - // Child yielded without finishing. - return PrefetchTaskExitStatus.InProgress - } - } + return null } - // The static attempt was sufficient for this segment (each child is its - // own decision point) — or parts of it are still in flight, in which case - // the task is blocked and the decision re-runs against the received - // responses. - return PrefetchTaskExitStatus.Done + return accumulation } function diffRouteTreeAgainstCurrent( @@ -1579,7 +1608,9 @@ function diffRouteTreeAgainstCurrent( ): FlightRouterState { // This is a single recursive traversal that does multiple things: // - Finds the segments that differ from the current route, comparing each - // segment's identity as we traverse. + // segment the same way the navigation will (see + // updateRenderTreeOnNavigation in render-tree.ts): its route position, + // then whether any of its param values changed. // - Constructs a request tree (FlightRouterState) that describes which // segments need to be prefetched and which ones are already cached. // - Creates a set of pending cache entries for the segments that need to @@ -1591,109 +1622,15 @@ function diffRouteTreeAgainstCurrent( if (newTreeChildren !== null) { for (const [parallelRouteKey, newTreeChild] of newTreeChildren) { const oldTreeChild = oldSlots?.get(parallelRouteKey) - if ( - oldTreeChild !== undefined && - doesRouteStructureMatch(oldTreeChild, newTreeChild) && - compareParams(oldTreeChild.varyPath, newTreeChild.varyPath) === - ParamsChange.None - ) { - // This segment is already part of the current route. Keep traversing. - const requestTreeChild = diffRouteTreeAgainstCurrent( - now, - task, - route, - oldTreeChild, - newTreeChild, - spawnedEntries, - fetchStrategy - ) - requestTreeChildren[parallelRouteKey] = requestTreeChild - } else { - // This segment is not part of the current route. We're entering a - // part of the tree that we need to prefetch (unless everything is - // already cached). - switch (fetchStrategy) { - case FetchStrategy.LoadingBoundary: { - // When PPR is disabled, we can't prefetch per segment. We must - // fallback to the old prefetch behavior and send a runtime request. - // Only routes that include a loading boundary can be prefetched in - // this way. - // - // This is simlar to a "full" prefetch, but we're much more - // conservative about which segments to include in the request. - // - // The server will only render up to the first loading boundary - // inside new part of the tree. If there's no loading boundary - // anywhere in the tree, the server will never return any data, so - // we can skip the request. - const subtreeHasLoadingBoundary = - (newTreeChild.prefetchHints & - (PrefetchHint.SegmentHasLoadingBoundary | - PrefetchHint.SubtreeHasLoadingBoundary)) !== - 0 - const requestTreeChild = subtreeHasLoadingBoundary - ? pingPPRDisabledRouteTreeUpToLoadingBoundary( - now, - task, - route, - newTreeChild, - null, - spawnedEntries - ) - : // There's no loading boundary within this tree. Bail out. - convertRouteTreeToFlightRouterState(newTreeChild) - requestTreeChildren[parallelRouteKey] = requestTreeChild - break - } - case FetchStrategy.PPRRuntime: { - // This is a runtime prefetch. Fetch all cacheable data in the tree, - // not just the static PPR shell. - const requestTreeChild = pingRouteTreeAndIncludeDynamicData( - now, - task, - route, - newTreeChild, - false, - spawnedEntries, - fetchStrategy - ) - requestTreeChildren[parallelRouteKey] = requestTreeChild - break - } - case FetchStrategy.Full: { - // This is a "full" prefetch. Fetch all the data in the tree, both - // static and dynamic. We issue roughly the same request that we - // would during a real navigation. The goal is that once the - // navigation occurs, the router should not have to fetch any - // additional data. - // - // Although the response will include dynamic data, opting into a - // Full prefetch — via — implicitly - // instructs the cache to treat the response as "static", or non- - // dynamic, since the whole point is to cache it for - // future navigations. - // - // Construct a tree (currently a FlightRouterState) that represents - // which segments need to be prefetched and which ones are already - // cached. If the tree is empty, then we can exit. Otherwise, we'll - // send the request tree to the server and use the response to - // populate the segment cache. - const requestTreeChild = pingRouteTreeAndIncludeDynamicData( - now, - task, - route, - newTreeChild, - false, - spawnedEntries, - fetchStrategy - ) - requestTreeChildren[parallelRouteKey] = requestTreeChild - break - } - default: - fetchStrategy satisfies never - } - } + requestTreeChildren[parallelRouteKey] = diffSegmentAgainstCurrent( + now, + task, + route, + oldTreeChild, + newTreeChild, + spawnedEntries, + fetchStrategy + ) } } const requestTree: FlightRouterState = [ @@ -1708,6 +1645,124 @@ function diffRouteTreeAgainstCurrent( return requestTree } +/** + * The per-segment decision of a runtime request's tree walk + * (diffRouteTreeAgainstCurrent): the segment at this position of the target + * route, and the current page's segment at the same position, if it has one. + * A segment the navigation keeps — same route position, and none of its param + * values changed — is omitted from the request and the walk continues into + * its children. Otherwise this segment begins a part of the tree that needs + * to be prefetched (unless everything is already cached), requested according + * to the strategy. + */ +function diffSegmentAgainstCurrent( + now: number, + task: PrefetchTask, + route: FulfilledRouteCacheEntry, + oldTree: RouteTree | undefined, + newTree: RouteTree, + spawnedEntries: Map, + fetchStrategy: + | FetchStrategy.Full + | FetchStrategy.PPRRuntime + | FetchStrategy.LoadingBoundary +): FlightRouterState { + if (oldTree !== undefined && doesRouteStructureMatch(oldTree, newTree)) { + // This segment is already part of the current route. + if ( + compareParams(oldTree.varyPath, newTree.varyPath) === ParamsChange.None + ) { + // The navigation keeps its data. Keep traversing. + return diffRouteTreeAgainstCurrent( + now, + task, + route, + oldTree, + newTree, + spawnedEntries, + fetchStrategy + ) + } + } + // This segment is not part of the current route, or the navigation + // replaces its data. We're entering a part of the tree that we need to + // prefetch (unless everything is already cached). + switch (fetchStrategy) { + case FetchStrategy.LoadingBoundary: { + // When PPR is disabled, we can't prefetch per segment. We must + // fallback to the old prefetch behavior and send a runtime request. + // Only routes that include a loading boundary can be prefetched in + // this way. + // + // This is simlar to a "full" prefetch, but we're much more + // conservative about which segments to include in the request. + // + // The server will only render up to the first loading boundary + // inside new part of the tree. If there's no loading boundary + // anywhere in the tree, the server will never return any data, so + // we can skip the request. + const subtreeHasLoadingBoundary = + (newTree.prefetchHints & + (PrefetchHint.SegmentHasLoadingBoundary | + PrefetchHint.SubtreeHasLoadingBoundary)) !== + 0 + if (subtreeHasLoadingBoundary) { + return pingPPRDisabledRouteTreeUpToLoadingBoundary( + now, + task, + route, + newTree, + null, + spawnedEntries + ) + } + // There's no loading boundary within this tree. Bail out. + return convertRouteTreeToFlightRouterState(newTree) + } + case FetchStrategy.PPRRuntime: { + // This is a runtime prefetch. Fetch all cacheable data in the tree, + // not just the static PPR shell. + return pingRouteTreeAndIncludeDynamicData( + now, + task, + route, + newTree, + false, + spawnedEntries, + fetchStrategy + ) + } + case FetchStrategy.Full: { + // This is a "full" prefetch. Fetch all the data in the tree, both + // static and dynamic. We issue roughly the same request that we + // would during a real navigation. The goal is that once the + // navigation occurs, the router should not have to fetch any + // additional data. + // + // Although the response will include dynamic data, opting into a + // Full prefetch — via — implicitly + // instructs the cache to treat the response as "static", or non- + // dynamic, since the whole point is to cache it for + // future navigations. + // + // Construct a tree (currently a FlightRouterState) that represents + // which segments need to be prefetched and which ones are already + // cached. If the tree is empty, then we can exit. Otherwise, we'll + // send the request tree to the server and use the response to + // populate the segment cache. + return pingRouteTreeAndIncludeDynamicData( + now, + task, + route, + newTree, + false, + spawnedEntries, + fetchStrategy + ) + } + } +} + function pingPPRDisabledRouteTreeUpToLoadingBoundary( now: number, task: PrefetchTask, @@ -2101,11 +2156,11 @@ function pingRuntimePrefetches( * request would return more content than the entry contains * (needsRuntimeRequest, derived at write time from the response that * produced the entry). The callers surface this signal to the per-segment - * decision point in pingNewPartOfCacheComponentsTree (and its analog for - * the head in pingStaticHead), which uses it during a static attempt to - * decide whether to fall back to a runtime prefetch. One exception withholds - * the signal: a shell-tier entry whose segment carries the static-attempt - * hint spawns a concrete static attempt first — see the Fulfilled case. + * decision point in pingSegmentInCacheComponentsTree, which uses it during + * a static attempt to decide whether to fall back to a runtime prefetch. One + * exception withholds the signal: a shell-tier entry whose segment carries + * the static-attempt hint spawns a concrete static attempt first — see the + * Fulfilled case. */ function pingSegmentBundle( now: number, @@ -2426,7 +2481,7 @@ function accumulateSegmentBundle( ) { if (segment.status === EntryStatus.Pending) { // The chain this entry joins may be dropped before it's ever pinged - // (see the drop sites in pingNewPartOfCacheComponentsTree), and only + // (see the drop sites in pingSegmentInCacheComponentsTree), and only // the ping blocks on Pending entries. Register on the in-flight // response at read time instead, so the pass observes it before the // phase can complete even if the chain is dropped. When the chain does From 10c5a6ac7a078288ba9de5dbfb3a8c69f2ec98a6 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 25 Sep 2026 07:11:39 +0200 Subject: [PATCH 12/13] Sequence CI for branch-based PR stacks (#99086) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What? Replace the Graphite-specific CI optimizer with a **read-only TypeScript gate** for PR stacks formed by ordinary GitHub base/head branch links. This PR includes the work previously reviewed in #99116 and #99122. ### Why? Rebasing a large stack starts expensive `build_and_test` jobs on every PR at once. Let the first three PRs and the top PR run immediately, but defer middle PRs until nearby CI provides a useful signal. A deferred PR must **not** appear mergeable just because its jobs have not started. ### How? - **Find the stack from branch relationships**, not GitHub stack metadata. Middle PRs poll their three nearest predecessors every **five minutes**. Any predecessor with a successful, current-head/current-base `thank you, next` check releases full CI; three unsuccessful results **fail the gate and required aggregate**. While waiting, the gate stays in progress. After five hours without a decision, it releases full CI rather than stranding the PR. - **Revalidate before deciding.** Refresh PR and check data before release/failure so rebases, retargeting and reruns cannot make a stale check decisive. Transient GitHub 5xx/429 errors retry; permanent errors or ambiguous topology start full CI rather than claiming CI passed. - **Keep polling cheap and read-only.** Reuse PR head/base data from `pulls.list` (11 → **8 REST reads** per ordinary waiting poll), while still checking *all three* predecessors. The bundled action is checked out at the current test-merge `${{ github.sha }}` without persisted credentials; forks bypass the checkout/gate and run full CI. The six-hour gate stays on `ubuntu-latest` because `ubuntu-slim` has a 15-minute limit.
Implementation and review tradeoffs - The local action bundles a pinned Octokit. Isolated Jest tests use fake timers and a real check-run response fixture. CI runs the existing Next.js lint/examples/externals/browser checks before action-specific install, typecheck, build and Jest; a generated-diff guard catches stale bundles. - `uses: ./...` requires action files already in the job workspace; it does not fetch them like `owner/repo@ref`. The sparse checkout fetches only `action.yml` and `dist/index.js`. Caller and reusable workflow grant read-only `checks`, `contents` and `pull-requests` permissions; the gate inherits no secrets. - Cheap-check graph splitting was deliberately deferred. Job-level concurrency does not enforce predecessor *success*; stopping at the first pending predecessor misses an older success. GraphQL batching needs a verified check-association projection and measured cost before replacing the current REST lookup. - When the child commits became reachable from this branch, GitHub automatically marked #99116 and #99122 merged; no PR merge operation was used. [Action README](https://github.com/vercel/next.js/blob/ci/branch-stack-polling-gate/.github/actions/pr-stack-ci-gate/README.md) documents the contract.
### Verification - **Focused checks:** TypeScript, Prettier, deterministic bundle/diff checks and **24/24 Jest** passed. The final comment-only change in `src/gate.ts` produced a byte-identical bundle. The [current root run](https://github.com/vercel/next.js/actions/runs/35967795126) passed its gate, lint and required `thank you, next` on **attempt 2** (same SHA); attempt 1 failed an intermittent redbox test. - **Live six-PR test on the compiled action:** the first three and top passed the gate immediately. Middle #99097 and #99098 **waited**. On initial attempts, both **failed closed** when their three predecessors were unsuccessful, with no expensive jobs started. On rerun, #99097 **released on #99086's success** despite #99096 failing and #99095 still pending; full CI ran but failed unrelated tests. #99098 waited for *its own* three predecessors, then failed closed again. Gate release never marked full CI successful. - **Known limitation:** product-test failures—not the gate—left four immediate/released PRs with failed full CI on various attempts. All five test drafts remain open for reuse. Workflow reruns in this experiment were initiated externally; this agent's GitHub integration received 403 (`actions=write` required) when attempting a rerun.
Current compiled-action six-PR results, with workflow and gate logs (2026-09-24) Each draft is one Jest assertion commit over its signed parent; the cumulative focused suite passes **24/24**. Times below are UTC. “Required” refers to `thank you, next`, not the gate check. | Position | PR / signed head / workflow | Gate outcome | Required check | Expensive work | | :-- | :-- | :-- | :-- | :-- | | 1 | [#99086](https://github.com/vercel/next.js/actions/runs/35967795126) `85444c9e` | immediate pass | failed attempt 1; **passed attempt 2** | ran twice | | 2 | [#99095](https://github.com/vercel/next.js/actions/runs/35969902709) `bc19787a` | immediate pass | failed attempts 1 and 2 | ran twice | | 3 | [#99096](https://github.com/vercel/next.js/actions/runs/35970016367) `98bfab58` | immediate pass | failed | ran | | 4 | [#99097](https://github.com/vercel/next.js/actions/runs/35970109573) `e260bf96` | [attempt 1](https://github.com/vercel/next.js/actions/runs/35970109573/job/107537584401): waited → failed 08:02; [attempt 2](https://github.com/vercel/next.js/actions/runs/35970109573/job/107609256427): **released 11:19** | failed both attempts (attempt 2: product tests) | none on attempt 1; full CI ran on attempt 2 | | 5 | [#99098](https://github.com/vercel/next.js/actions/runs/35970206069) `01222d4a` | [attempt 1](https://github.com/vercel/next.js/actions/runs/35970206069/job/107537887530): waited → failed 08:03; [attempt 2](https://github.com/vercel/next.js/actions/runs/35970206069/job/107609367420): waited → failed 11:50 | failed both attempts | **never started** | | 6 (top) | [#99099](https://github.com/vercel/next.js/actions/runs/35970312588) `c6f3cf4a` | immediate pass | failed attempts 1 and 2 | ran twice | **Decision evidence:** On attempt 1, #99097 saw #99096/#99095/#99086 all unsuccessful, and #99098 saw #99097/#99096/#99095 all unsuccessful; their required checks failed and downstream jobs were skipped *because their gates failed*. After #99086's successful rerun, #99097 attempt 2 logged `#99096=unsuccessful, #99095=waiting, #99086=success`; its gate passed and [build-next actually ran and passed](https://github.com/vercel/next.js/actions/runs/35970109573/job/107609336752). #99098 could not use #99086 (outside its nearest-three window); it failed at 11:50 when #99097/#99096/#99095 had all finished without success. Completed failed gates do not restart automatically when a predecessor reruns.
Unrelated CI failures and recovered flaky tests - #99086's attempt 1 failed `lazy-dynamic-imports › does not parse a dynamic import target before activation`: redbox source was `null` after all in-job retries ([job](https://github.com/vercel/next.js/actions/runs/35967795126/job/107530613870)). **The same-head workflow passed on attempt 2.** - #99095, #99096, #99097 attempt 2 and #99099 failed `instant-insights-tab-overlay › should wrap the mobile overlay header only when it does not fit`: observed header top difference **24**, expected **< 4**, after in-job retries ([example job](https://github.com/vercel/next.js/actions/runs/35969902709/job/107537341995)). #99096 also failed `turbopack-loader-file-dependencies › should update when a build dependency changes` (`build-one` instead of `build-two`, [job](https://github.com/vercel/next.js/actions/runs/35970016367/job/107537932737)). #99097 attempt 2 additionally failed the `lazy-dynamic-imports` redbox assertion in an experimental cache-components shard ([job](https://github.com/vercel/next.js/actions/runs/35970109573/job/107609732882)). These failures are not gate-test failures. - **Recovered in-job flakes:** #99096 `instant-validation/head-and-reporting › invalid - runtime prefetch - dynamic viewport blocks navigation` (redbox did not open; passed retry 1/2); #99095 `use-cache-without-experimental-flag › should recover from the build error if useCache flag is set` (Playwright execution context destroyed on navigation; passed retry 1/2); #99097 attempt 2 `lazy-dynamic-imports` in a different Turbopack-dev shard (passed retry 2/2), `instant-validation/suspense-boundaries` (redbox did not open; passed retry 2/2), and `instant-validation/head-and-reporting.partial-prefetching` (redbox did not open; passed retry 1/2).
Earlier implementation smoke tests and CJS-gate stack runs - The [folded-code root head `89f0ca34` full run](https://github.com/vercel/next.js/actions/runs/35965443069) passed gate, build, lint (including **24/24 Jest**) and required `thank you, next`. The earlier [compiled-action child head `25647f00` run](https://github.com/vercel/next.js/actions/runs/35922700289) also passed full CI, proving same-SHA sparse checkout before the lint-order follow-up was folded into root. - **CJS-gate fail-closed run (base `ec0abf41`, 2026-09-23):** [#99086](https://github.com/vercel/next.js/actions/runs/35875541530), [#99095](https://github.com/vercel/next.js/actions/runs/35875598833), [#99096](https://github.com/vercel/next.js/actions/runs/35875651890) and top [#99099](https://github.com/vercel/next.js/actions/runs/35875952426) ran full CI and failed unrelated tests. Middle [#99097](https://github.com/vercel/next.js/actions/runs/35875701404/job/107231099534) failed its gate at 15:20 and [#99098](https://github.com/vercel/next.js/actions/runs/35875873698/job/107231827956) at 15:21; required checks failed and expensive jobs never started. A Turbopack source-map snapshot failure was subsequently fixed in [#99106](https://github.com/vercel/next.js/pull/99106). - **CJS-gate release run (base `8ff7f2bd`, 2026-09-23):** [#99086](https://github.com/vercel/next.js/actions/runs/35887913444) and [#99095](https://github.com/vercel/next.js/actions/runs/35887957948) passed full CI. [#99097](https://github.com/vercel/next.js/actions/runs/35888074168/job/107273254039) and [#99098](https://github.com/vercel/next.js/actions/runs/35888146874/job/107273514756) waited, then released at the next poll on #99095's green check while a nearer predecessor was unresolved. Their later full CI failed; top [#99099](https://github.com/vercel/next.js/actions/runs/35888202575) started immediately (later canceled). These precede the TypeScript action and are not claimed as evidence for its current head.
--------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> --- .github/actions/pr-stack-ci-gate/.gitignore | 1 + .github/actions/pr-stack-ci-gate/README.md | 52 ++ .github/actions/pr-stack-ci-gate/action.yml | 5 + .../actions/pr-stack-ci-gate/dist/index.js | 3 + .../pr-stack-ci-gate/dist/licenses.txt | 590 ++++++++++++++++++ .../fixtures/successful-check.json | 19 + .github/actions/pr-stack-ci-gate/gate.test.js | 579 +++++++++++++++++ .../jest-typescript-transform.cjs | 18 + .../actions/pr-stack-ci-gate/jest.config.cjs | 7 + .github/actions/pr-stack-ci-gate/package.json | 17 + .github/actions/pr-stack-ci-gate/src/gate.ts | 472 ++++++++++++++ .github/actions/pr-stack-ci-gate/src/index.ts | 20 + .../actions/pr-stack-ci-gate/tsconfig.json | 12 + .github/pnpm-lock.yaml | 16 + .github/workflows/build_and_test.yml | 12 +- .github/workflows/pr_stack_optimizer.yml | 120 ++-- 16 files changed, 1874 insertions(+), 69 deletions(-) create mode 100644 .github/actions/pr-stack-ci-gate/.gitignore create mode 100644 .github/actions/pr-stack-ci-gate/README.md create mode 100644 .github/actions/pr-stack-ci-gate/action.yml create mode 100644 .github/actions/pr-stack-ci-gate/dist/index.js create mode 100644 .github/actions/pr-stack-ci-gate/dist/licenses.txt create mode 100644 .github/actions/pr-stack-ci-gate/fixtures/successful-check.json create mode 100644 .github/actions/pr-stack-ci-gate/gate.test.js create mode 100644 .github/actions/pr-stack-ci-gate/jest-typescript-transform.cjs create mode 100644 .github/actions/pr-stack-ci-gate/jest.config.cjs create mode 100644 .github/actions/pr-stack-ci-gate/package.json create mode 100644 .github/actions/pr-stack-ci-gate/src/gate.ts create mode 100644 .github/actions/pr-stack-ci-gate/src/index.ts create mode 100644 .github/actions/pr-stack-ci-gate/tsconfig.json diff --git a/.github/actions/pr-stack-ci-gate/.gitignore b/.github/actions/pr-stack-ci-gate/.gitignore new file mode 100644 index 000000000000..c2658d7d1b31 --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/.github/actions/pr-stack-ci-gate/README.md b/.github/actions/pr-stack-ci-gate/README.md new file mode 100644 index 000000000000..63278025802b --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/README.md @@ -0,0 +1,52 @@ +# PR Stack CI Gate + +The `build_and_test` workflow calls this read-only action for same-repository PRs. +It infers branch-based stacks from open PR head/base branch names; no GitHub +stack API or Graphite integration is required. Fork PRs bypass the checkout and +this action entirely and run full CI immediately. + +The first three PRs and every leaf run immediately. An internal PR polls the +closest three predecessors every five minutes: **any** current-head/current-base +`thank you, next` success releases full CI, while three terminal failures fail +the required gate. Unresolved waits open after five hours (six-hour job timeout). +Transient GitHub 5xx/429 errors retry every five minutes; other errors start +full CI so they cannot accidentally mark the aggregate green. + +## Development + +```bash +pnpm --dir .github/actions/pr-stack-ci-gate install --frozen-lockfile --ignore-scripts +pnpm --dir .github/actions/pr-stack-ci-gate types +pnpm --dir .github/actions/pr-stack-ci-gate build +pnpm --dir .github/actions/pr-stack-ci-gate test +``` + +Commit changes to `src/` **and** the generated `dist/index.js` and +`dist/licenses.txt`. Tests use an isolated Jest config because the repository's +main Jest config searches only the `test/` and package trees. A captured shape +from #99095's actual required-check response lives in `fixtures/`. The workflow +sparse-checks out only `action.yml` and `dist/index.js` from `${{ github.sha }}`; +that is the test-merge commit on PR runs. The caller and action job grant only +`checks: read`, `contents: read` and `pull-requests: read`. No secrets are +inherited, and `persist-credentials` is false. + +## API budget and correctness + +A stable middle-PR poll rechecks its own PR, finds successors, walks three +predecessor links and checks all three required results: **8 REST calls** versus +11 before this action. The `pulls.list` response already contains head/base +SHAs, so the three extra predecessor `pulls.get` calls are unnecessary while +waiting. Before opening or failing, the action re-reads current PR metadata and +the decisive predecessor(s) and check(s). We cannot stop at the first pending +predecessor: an older PR can already have passed; nor can we permanently cache +a terminal failure because that PR can succeed on rerun. Rechecking live branch +topology on *every* five-minute poll catches retargeted, closed, or leaf PRs at +the original cadence. + +GitHub's `GITHUB_TOKEN` primary budget is typically **1,000/hour/repository** +for both REST requests and GraphQL points. Most REST GETs cost one point. +GraphQL may batch data into a one-point query, but the check-run-to-PR base-SHA +association and nested pagination would need independent correctness testing; +this implementation instead takes a measured REST reduction without weakening +the already verified current-base guard. An `ubuntu-slim` runner is unsuitable: +its 15-minute job limit is shorter than the gate's multi-hour wait. diff --git a/.github/actions/pr-stack-ci-gate/action.yml b/.github/actions/pr-stack-ci-gate/action.yml new file mode 100644 index 000000000000..e5ba1d6ebafd --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/action.yml @@ -0,0 +1,5 @@ +name: 'PR Stack CI Gate' +description: 'Delay expensive CI for branch-based stacked pull requests until prior CI succeeds.' +runs: + using: 'node24' + main: 'dist/index.js' diff --git a/.github/actions/pr-stack-ci-gate/dist/index.js b/.github/actions/pr-stack-ci-gate/dist/index.js new file mode 100644 index 000000000000..8fffb172ecdc --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/dist/index.js @@ -0,0 +1,3 @@ +(()=>{var e={4156:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.prototype.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};Object.defineProperty(A,"__esModule",{value:true});A.issue=A.issueCommand=void 0;const n=o(t(857));const i=t(6816);function issueCommand(e,A,t){const r=new Command(e,A,t);process.stdout.write(r.toString()+n.EOL)}A.issueCommand=issueCommand;function issue(e,A=""){issueCommand(e,{},A)}A.issue=issue;const a="::";class Command{constructor(e,A,t){if(!e){e="missing.command"}this.command=e;this.properties=A;this.message=t}toString(){let e=a+this.command;if(this.properties&&Object.keys(this.properties).length>0){e+=" ";let A=true;for(const t in this.properties){if(this.properties.hasOwnProperty(t)){const r=this.properties[t];if(r){if(A){A=false}else{e+=","}e+=`${t}=${escapeProperty(r)}`}}}}e+=`${a}${escapeData(this.message)}`;return e}}function escapeData(e){return(0,i.toCommandValue)(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A")}function escapeProperty(e){return(0,i.toCommandValue)(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/:/g,"%3A").replace(/,/g,"%2C")}},4442:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.prototype.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};var n=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};Object.defineProperty(A,"__esModule",{value:true});A.platform=A.toPlatformPath=A.toWin32Path=A.toPosixPath=A.markdownSummary=A.summary=A.getIDToken=A.getState=A.saveState=A.group=A.endGroup=A.startGroup=A.info=A.notice=A.warning=A.error=A.debug=A.isDebug=A.setFailed=A.setCommandEcho=A.setOutput=A.getBooleanInput=A.getMultilineInput=A.getInput=A.addPath=A.setSecret=A.exportVariable=A.ExitCode=void 0;const i=t(4156);const a=t(9883);const c=t(6816);const g=o(t(857));const E=o(t(6928));const l=t(8224);var u;(function(e){e[e["Success"]=0]="Success";e[e["Failure"]=1]="Failure"})(u||(A.ExitCode=u={}));function exportVariable(e,A){const t=(0,c.toCommandValue)(A);process.env[e]=t;const r=process.env["GITHUB_ENV"]||"";if(r){return(0,a.issueFileCommand)("ENV",(0,a.prepareKeyValueMessage)(e,A))}(0,i.issueCommand)("set-env",{name:e},t)}A.exportVariable=exportVariable;function setSecret(e){(0,i.issueCommand)("add-mask",{},e)}A.setSecret=setSecret;function addPath(e){const A=process.env["GITHUB_PATH"]||"";if(A){(0,a.issueFileCommand)("PATH",e)}else{(0,i.issueCommand)("add-path",{},e)}process.env["PATH"]=`${e}${E.delimiter}${process.env["PATH"]}`}A.addPath=addPath;function getInput(e,A){const t=process.env[`INPUT_${e.replace(/ /g,"_").toUpperCase()}`]||"";if(A&&A.required&&!t){throw new Error(`Input required and not supplied: ${e}`)}if(A&&A.trimWhitespace===false){return t}return t.trim()}A.getInput=getInput;function getMultilineInput(e,A){const t=getInput(e,A).split("\n").filter((e=>e!==""));if(A&&A.trimWhitespace===false){return t}return t.map((e=>e.trim()))}A.getMultilineInput=getMultilineInput;function getBooleanInput(e,A){const t=["true","True","TRUE"];const r=["false","False","FALSE"];const s=getInput(e,A);if(t.includes(s))return true;if(r.includes(s))return false;throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${e}\n`+`Support boolean input list: \`true | True | TRUE | false | False | FALSE\``)}A.getBooleanInput=getBooleanInput;function setOutput(e,A){const t=process.env["GITHUB_OUTPUT"]||"";if(t){return(0,a.issueFileCommand)("OUTPUT",(0,a.prepareKeyValueMessage)(e,A))}process.stdout.write(g.EOL);(0,i.issueCommand)("set-output",{name:e},(0,c.toCommandValue)(A))}A.setOutput=setOutput;function setCommandEcho(e){(0,i.issue)("echo",e?"on":"off")}A.setCommandEcho=setCommandEcho;function setFailed(e){process.exitCode=u.Failure;error(e)}A.setFailed=setFailed;function isDebug(){return process.env["RUNNER_DEBUG"]==="1"}A.isDebug=isDebug;function debug(e){(0,i.issueCommand)("debug",{},e)}A.debug=debug;function error(e,A={}){(0,i.issueCommand)("error",(0,c.toCommandProperties)(A),e instanceof Error?e.toString():e)}A.error=error;function warning(e,A={}){(0,i.issueCommand)("warning",(0,c.toCommandProperties)(A),e instanceof Error?e.toString():e)}A.warning=warning;function notice(e,A={}){(0,i.issueCommand)("notice",(0,c.toCommandProperties)(A),e instanceof Error?e.toString():e)}A.notice=notice;function info(e){process.stdout.write(e+g.EOL)}A.info=info;function startGroup(e){(0,i.issue)("group",e)}A.startGroup=startGroup;function endGroup(){(0,i.issue)("endgroup")}A.endGroup=endGroup;function group(e,A){return n(this,void 0,void 0,(function*(){startGroup(e);let t;try{t=yield A()}finally{endGroup()}return t}))}A.group=group;function saveState(e,A){const t=process.env["GITHUB_STATE"]||"";if(t){return(0,a.issueFileCommand)("STATE",(0,a.prepareKeyValueMessage)(e,A))}(0,i.issueCommand)("save-state",{name:e},(0,c.toCommandValue)(A))}A.saveState=saveState;function getState(e){return process.env[`STATE_${e}`]||""}A.getState=getState;function getIDToken(e){return n(this,void 0,void 0,(function*(){return yield l.OidcClient.getIDToken(e)}))}A.getIDToken=getIDToken;var Q=t(669);Object.defineProperty(A,"summary",{enumerable:true,get:function(){return Q.summary}});var C=t(669);Object.defineProperty(A,"markdownSummary",{enumerable:true,get:function(){return C.markdownSummary}});var h=t(2078);Object.defineProperty(A,"toPosixPath",{enumerable:true,get:function(){return h.toPosixPath}});Object.defineProperty(A,"toWin32Path",{enumerable:true,get:function(){return h.toWin32Path}});Object.defineProperty(A,"toPlatformPath",{enumerable:true,get:function(){return h.toPlatformPath}});A.platform=o(t(4182))},9883:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.prototype.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};Object.defineProperty(A,"__esModule",{value:true});A.prepareKeyValueMessage=A.issueFileCommand=void 0;const n=o(t(6982));const i=o(t(9896));const a=o(t(857));const c=t(6816);function issueFileCommand(e,A){const t=process.env[`GITHUB_${e}`];if(!t){throw new Error(`Unable to find environment variable for file command ${e}`)}if(!i.existsSync(t)){throw new Error(`Missing file at path: ${t}`)}i.appendFileSync(t,`${(0,c.toCommandValue)(A)}${a.EOL}`,{encoding:"utf8"})}A.issueFileCommand=issueFileCommand;function prepareKeyValueMessage(e,A){const t=`ghadelimiter_${n.randomUUID()}`;const r=(0,c.toCommandValue)(A);if(e.includes(t)){throw new Error(`Unexpected input: name should not contain the delimiter "${t}"`)}if(r.includes(t)){throw new Error(`Unexpected input: value should not contain the delimiter "${t}"`)}return`${e}<<${t}${a.EOL}${r}${a.EOL}${t}`}A.prepareKeyValueMessage=prepareKeyValueMessage},8224:function(e,A,t){"use strict";var r=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};Object.defineProperty(A,"__esModule",{value:true});A.OidcClient=void 0;const s=t(5290);const o=t(8358);const n=t(4442);class OidcClient{static createHttpClient(e=true,A=10){const t={allowRetries:e,maxRetries:A};return new s.HttpClient("actions/oidc-client",[new o.BearerCredentialHandler(OidcClient.getRequestToken())],t)}static getRequestToken(){const e=process.env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"];if(!e){throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable")}return e}static getIDTokenUrl(){const e=process.env["ACTIONS_ID_TOKEN_REQUEST_URL"];if(!e){throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable")}return e}static getCall(e){var A;return r(this,void 0,void 0,(function*(){const t=OidcClient.createHttpClient();const r=yield t.getJson(e).catch((e=>{throw new Error(`Failed to get ID Token. \n \n Error Code : ${e.statusCode}\n \n Error Message: ${e.message}`)}));const s=(A=r.result)===null||A===void 0?void 0:A.value;if(!s){throw new Error("Response json body do not have ID Token field")}return s}))}static getIDToken(e){return r(this,void 0,void 0,(function*(){try{let A=OidcClient.getIDTokenUrl();if(e){const t=encodeURIComponent(e);A=`${A}&audience=${t}`}(0,n.debug)(`ID token url is ${A}`);const t=yield OidcClient.getCall(A);(0,n.setSecret)(t);return t}catch(e){throw new Error(`Error message: ${e.message}`)}}))}}A.OidcClient=OidcClient},2078:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.prototype.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};Object.defineProperty(A,"__esModule",{value:true});A.toPlatformPath=A.toWin32Path=A.toPosixPath=void 0;const n=o(t(6928));function toPosixPath(e){return e.replace(/[\\]/g,"/")}A.toPosixPath=toPosixPath;function toWin32Path(e){return e.replace(/[/]/g,"\\")}A.toWin32Path=toWin32Path;function toPlatformPath(e){return e.replace(/[/\\]/g,n.sep)}A.toPlatformPath=toPlatformPath},4182:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.prototype.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};var n=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};var i=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(A,"__esModule",{value:true});A.getDetails=A.isLinux=A.isMacOS=A.isWindows=A.arch=A.platform=void 0;const a=i(t(857));const c=o(t(7167));const getWindowsInfo=()=>n(void 0,void 0,void 0,(function*(){const{stdout:e}=yield c.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"',undefined,{silent:true});const{stdout:A}=yield c.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"',undefined,{silent:true});return{name:A.trim(),version:e.trim()}}));const getMacOsInfo=()=>n(void 0,void 0,void 0,(function*(){var e,A,t,r;const{stdout:s}=yield c.getExecOutput("sw_vers",undefined,{silent:true});const o=(A=(e=s.match(/ProductVersion:\s*(.+)/))===null||e===void 0?void 0:e[1])!==null&&A!==void 0?A:"";const n=(r=(t=s.match(/ProductName:\s*(.+)/))===null||t===void 0?void 0:t[1])!==null&&r!==void 0?r:"";return{name:n,version:o}}));const getLinuxInfo=()=>n(void 0,void 0,void 0,(function*(){const{stdout:e}=yield c.getExecOutput("lsb_release",["-i","-r","-s"],{silent:true});const[A,t]=e.trim().split("\n");return{name:A,version:t}}));A.platform=a.default.platform();A.arch=a.default.arch();A.isWindows=A.platform==="win32";A.isMacOS=A.platform==="darwin";A.isLinux=A.platform==="linux";function getDetails(){return n(this,void 0,void 0,(function*(){return Object.assign(Object.assign({},yield A.isWindows?getWindowsInfo():A.isMacOS?getMacOsInfo():getLinuxInfo()),{platform:A.platform,arch:A.arch,isWindows:A.isWindows,isMacOS:A.isMacOS,isLinux:A.isLinux})}))}A.getDetails=getDetails},669:function(e,A,t){"use strict";var r=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};Object.defineProperty(A,"__esModule",{value:true});A.summary=A.markdownSummary=A.SUMMARY_DOCS_URL=A.SUMMARY_ENV_VAR=void 0;const s=t(857);const o=t(9896);const{access:n,appendFile:i,writeFile:a}=o.promises;A.SUMMARY_ENV_VAR="GITHUB_STEP_SUMMARY";A.SUMMARY_DOCS_URL="https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary";class Summary{constructor(){this._buffer=""}filePath(){return r(this,void 0,void 0,(function*(){if(this._filePath){return this._filePath}const e=process.env[A.SUMMARY_ENV_VAR];if(!e){throw new Error(`Unable to find environment variable for $${A.SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`)}try{yield n(e,o.constants.R_OK|o.constants.W_OK)}catch(A){throw new Error(`Unable to access summary file: '${e}'. Check if the file has correct read/write permissions.`)}this._filePath=e;return this._filePath}))}wrap(e,A,t={}){const r=Object.entries(t).map((([e,A])=>` ${e}="${A}"`)).join("");if(!A){return`<${e}${r}>`}return`<${e}${r}>${A}`}write(e){return r(this,void 0,void 0,(function*(){const A=!!(e===null||e===void 0?void 0:e.overwrite);const t=yield this.filePath();const r=A?a:i;yield r(t,this._buffer,{encoding:"utf8"});return this.emptyBuffer()}))}clear(){return r(this,void 0,void 0,(function*(){return this.emptyBuffer().write({overwrite:true})}))}stringify(){return this._buffer}isEmptyBuffer(){return this._buffer.length===0}emptyBuffer(){this._buffer="";return this}addRaw(e,A=false){this._buffer+=e;return A?this.addEOL():this}addEOL(){return this.addRaw(s.EOL)}addCodeBlock(e,A){const t=Object.assign({},A&&{lang:A});const r=this.wrap("pre",this.wrap("code",e),t);return this.addRaw(r).addEOL()}addList(e,A=false){const t=A?"ol":"ul";const r=e.map((e=>this.wrap("li",e))).join("");const s=this.wrap(t,r);return this.addRaw(s).addEOL()}addTable(e){const A=e.map((e=>{const A=e.map((e=>{if(typeof e==="string"){return this.wrap("td",e)}const{header:A,data:t,colspan:r,rowspan:s}=e;const o=A?"th":"td";const n=Object.assign(Object.assign({},r&&{colspan:r}),s&&{rowspan:s});return this.wrap(o,t,n)})).join("");return this.wrap("tr",A)})).join("");const t=this.wrap("table",A);return this.addRaw(t).addEOL()}addDetails(e,A){const t=this.wrap("details",this.wrap("summary",e)+A);return this.addRaw(t).addEOL()}addImage(e,A,t){const{width:r,height:s}=t||{};const o=Object.assign(Object.assign({},r&&{width:r}),s&&{height:s});const n=this.wrap("img",null,Object.assign({src:e,alt:A},o));return this.addRaw(n).addEOL()}addHeading(e,A){const t=`h${A}`;const r=["h1","h2","h3","h4","h5","h6"].includes(t)?t:"h1";const s=this.wrap(r,e);return this.addRaw(s).addEOL()}addSeparator(){const e=this.wrap("hr",null);return this.addRaw(e).addEOL()}addBreak(){const e=this.wrap("br",null);return this.addRaw(e).addEOL()}addQuote(e,A){const t=Object.assign({},A&&{cite:A});const r=this.wrap("blockquote",e,t);return this.addRaw(r).addEOL()}addLink(e,A){const t=this.wrap("a",e,{href:A});return this.addRaw(t).addEOL()}}const c=new Summary;A.markdownSummary=c;A.summary=c},6816:(e,A)=>{"use strict";Object.defineProperty(A,"__esModule",{value:true});A.toCommandProperties=A.toCommandValue=void 0;function toCommandValue(e){if(e===null||e===undefined){return""}else if(typeof e==="string"||e instanceof String){return e}return JSON.stringify(e)}A.toCommandValue=toCommandValue;function toCommandProperties(e){if(!Object.keys(e).length){return{}}return{title:e.title,file:e.file,line:e.startLine,endLine:e.endLine,col:e.startColumn,endColumn:e.endColumn}}A.toCommandProperties=toCommandProperties},7167:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;Object.defineProperty(e,r,{enumerable:true,get:function(){return A[t]}})}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};var n=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};Object.defineProperty(A,"__esModule",{value:true});A.getExecOutput=A.exec=void 0;const i=t(3193);const a=o(t(7594));function exec(e,A,t){return n(this,void 0,void 0,(function*(){const r=a.argStringToArray(e);if(r.length===0){throw new Error(`Parameter 'commandLine' cannot be null or empty.`)}const s=r[0];A=r.slice(1).concat(A||[]);const o=new a.ToolRunner(s,A,t);return o.exec()}))}A.exec=exec;function getExecOutput(e,A,t){var r,s;return n(this,void 0,void 0,(function*(){let o="";let n="";const a=new i.StringDecoder("utf8");const c=new i.StringDecoder("utf8");const g=(r=t===null||t===void 0?void 0:t.listeners)===null||r===void 0?void 0:r.stdout;const E=(s=t===null||t===void 0?void 0:t.listeners)===null||s===void 0?void 0:s.stderr;const stdErrListener=e=>{n+=c.write(e);if(E){E(e)}};const stdOutListener=e=>{o+=a.write(e);if(g){g(e)}};const l=Object.assign(Object.assign({},t===null||t===void 0?void 0:t.listeners),{stdout:stdOutListener,stderr:stdErrListener});const u=yield exec(e,A,Object.assign(Object.assign({},t),{listeners:l}));o+=a.end();n+=c.end();return{exitCode:u,stdout:o,stderr:n}}))}A.getExecOutput=getExecOutput},7594:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;Object.defineProperty(e,r,{enumerable:true,get:function(){return A[t]}})}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};var n=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};Object.defineProperty(A,"__esModule",{value:true});A.argStringToArray=A.ToolRunner=void 0;const i=o(t(857));const a=o(t(4434));const c=o(t(5317));const g=o(t(6928));const E=o(t(4398));const l=o(t(2667));const u=t(3557);const Q=process.platform==="win32";class ToolRunner extends a.EventEmitter{constructor(e,A,t){super();if(!e){throw new Error("Parameter 'toolPath' cannot be null or empty.")}this.toolPath=e;this.args=A||[];this.options=t||{}}_debug(e){if(this.options.listeners&&this.options.listeners.debug){this.options.listeners.debug(e)}}_getCommandString(e,A){const t=this._getSpawnFileName();const r=this._getSpawnArgs(e);let s=A?"":"[command]";if(Q){if(this._isCmdFile()){s+=t;for(const e of r){s+=` ${e}`}}else if(e.windowsVerbatimArguments){s+=`"${t}"`;for(const e of r){s+=` ${e}`}}else{s+=this._windowsQuoteCmdArg(t);for(const e of r){s+=` ${this._windowsQuoteCmdArg(e)}`}}}else{s+=t;for(const e of r){s+=` ${e}`}}return s}_processLineBuffer(e,A,t){try{let r=A+e.toString();let s=r.indexOf(i.EOL);while(s>-1){const e=r.substring(0,s);t(e);r=r.substring(s+i.EOL.length);s=r.indexOf(i.EOL)}return r}catch(e){this._debug(`error processing line. Failed with error ${e}`);return""}}_getSpawnFileName(){if(Q){if(this._isCmdFile()){return process.env["COMSPEC"]||"cmd.exe"}}return this.toolPath}_getSpawnArgs(e){if(Q){if(this._isCmdFile()){let A=`/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`;for(const t of this.args){A+=" ";A+=e.windowsVerbatimArguments?t:this._windowsQuoteCmdArg(t)}A+='"';return[A]}}return this.args}_endsWith(e,A){return e.endsWith(A)}_isCmdFile(){const e=this.toolPath.toUpperCase();return this._endsWith(e,".CMD")||this._endsWith(e,".BAT")}_windowsQuoteCmdArg(e){if(!this._isCmdFile()){return this._uvQuoteCmdArg(e)}if(!e){return'""'}const A=[" ","\t","&","(",")","[","]","{","}","^","=",";","!","'","+",",","`","~","|","<",">",'"'];let t=false;for(const r of e){if(A.some((e=>e===r))){t=true;break}}if(!t){return e}let r='"';let s=true;for(let A=e.length;A>0;A--){r+=e[A-1];if(s&&e[A-1]==="\\"){r+="\\"}else if(e[A-1]==='"'){s=true;r+='"'}else{s=false}}r+='"';return r.split("").reverse().join("")}_uvQuoteCmdArg(e){if(!e){return'""'}if(!e.includes(" ")&&!e.includes("\t")&&!e.includes('"')){return e}if(!e.includes('"')&&!e.includes("\\")){return`"${e}"`}let A='"';let t=true;for(let r=e.length;r>0;r--){A+=e[r-1];if(t&&e[r-1]==="\\"){A+="\\"}else if(e[r-1]==='"'){t=true;A+="\\"}else{t=false}}A+='"';return A.split("").reverse().join("")}_cloneExecOptions(e){e=e||{};const A={cwd:e.cwd||process.cwd(),env:e.env||process.env,silent:e.silent||false,windowsVerbatimArguments:e.windowsVerbatimArguments||false,failOnStdErr:e.failOnStdErr||false,ignoreReturnCode:e.ignoreReturnCode||false,delay:e.delay||1e4};A.outStream=e.outStream||process.stdout;A.errStream=e.errStream||process.stderr;return A}_getSpawnOptions(e,A){e=e||{};const t={};t.cwd=e.cwd;t.env=e.env;t["windowsVerbatimArguments"]=e.windowsVerbatimArguments||this._isCmdFile();if(e.windowsVerbatimArguments){t.argv0=`"${A}"`}return t}exec(){return n(this,void 0,void 0,(function*(){if(!l.isRooted(this.toolPath)&&(this.toolPath.includes("/")||Q&&this.toolPath.includes("\\"))){this.toolPath=g.resolve(process.cwd(),this.options.cwd||process.cwd(),this.toolPath)}this.toolPath=yield E.which(this.toolPath,true);return new Promise(((e,A)=>n(this,void 0,void 0,(function*(){this._debug(`exec tool: ${this.toolPath}`);this._debug("arguments:");for(const e of this.args){this._debug(` ${e}`)}const t=this._cloneExecOptions(this.options);if(!t.silent&&t.outStream){t.outStream.write(this._getCommandString(t)+i.EOL)}const r=new ExecState(t,this.toolPath);r.on("debug",(e=>{this._debug(e)}));if(this.options.cwd&&!(yield l.exists(this.options.cwd))){return A(new Error(`The cwd: ${this.options.cwd} does not exist!`))}const s=this._getSpawnFileName();const o=c.spawn(s,this._getSpawnArgs(t),this._getSpawnOptions(this.options,s));let n="";if(o.stdout){o.stdout.on("data",(e=>{if(this.options.listeners&&this.options.listeners.stdout){this.options.listeners.stdout(e)}if(!t.silent&&t.outStream){t.outStream.write(e)}n=this._processLineBuffer(e,n,(e=>{if(this.options.listeners&&this.options.listeners.stdline){this.options.listeners.stdline(e)}}))}))}let a="";if(o.stderr){o.stderr.on("data",(e=>{r.processStderr=true;if(this.options.listeners&&this.options.listeners.stderr){this.options.listeners.stderr(e)}if(!t.silent&&t.errStream&&t.outStream){const A=t.failOnStdErr?t.errStream:t.outStream;A.write(e)}a=this._processLineBuffer(e,a,(e=>{if(this.options.listeners&&this.options.listeners.errline){this.options.listeners.errline(e)}}))}))}o.on("error",(e=>{r.processError=e.message;r.processExited=true;r.processClosed=true;r.CheckComplete()}));o.on("exit",(e=>{r.processExitCode=e;r.processExited=true;this._debug(`Exit code ${e} received from tool '${this.toolPath}'`);r.CheckComplete()}));o.on("close",(e=>{r.processExitCode=e;r.processExited=true;r.processClosed=true;this._debug(`STDIO streams have closed for tool '${this.toolPath}'`);r.CheckComplete()}));r.on("done",((t,r)=>{if(n.length>0){this.emit("stdline",n)}if(a.length>0){this.emit("errline",a)}o.removeAllListeners();if(t){A(t)}else{e(r)}}));if(this.options.input){if(!o.stdin){throw new Error("child process missing stdin")}o.stdin.end(this.options.input)}}))))}))}}A.ToolRunner=ToolRunner;function argStringToArray(e){const A=[];let t=false;let r=false;let s="";function append(e){if(r&&e!=='"'){s+="\\"}s+=e;r=false}for(let o=0;o0){A.push(s);s=""}continue}append(n)}if(s.length>0){A.push(s.trim())}return A}A.argStringToArray=argStringToArray;class ExecState extends a.EventEmitter{constructor(e,A){super();this.processClosed=false;this.processError="";this.processExitCode=0;this.processExited=false;this.processStderr=false;this.delay=1e4;this.done=false;this.timeout=null;if(!A){throw new Error("toolPath must not be empty")}this.options=e;this.toolPath=A;if(e.delay){this.delay=e.delay}}CheckComplete(){if(this.done){return}if(this.processClosed){this._setResult()}else if(this.processExited){this.timeout=u.setTimeout(ExecState.HandleTimeout,this.delay,this)}}_debug(e){this.emit("debug",e)}_setResult(){let e;if(this.processExited){if(this.processError){e=new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`)}else if(this.processExitCode!==0&&!this.options.ignoreReturnCode){e=new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`)}else if(this.processStderr&&this.options.failOnStdErr){e=new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`)}}if(this.timeout){clearTimeout(this.timeout);this.timeout=null}this.done=true;this.emit("done",e,this.processExitCode)}static HandleTimeout(e){if(e.done){return}if(!e.processClosed&&e.processExited){const A=`The STDIO streams did not close within ${e.delay/1e3} seconds of the exit event from process '${e.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`;e._debug(A)}e._setResult()}}},88:(e,A,t)=>{"use strict";Object.defineProperty(A,"__esModule",{value:true});A.Context=void 0;const r=t(9896);const s=t(857);class Context{constructor(){var e,A,t;this.payload={};if(process.env.GITHUB_EVENT_PATH){if((0,r.existsSync)(process.env.GITHUB_EVENT_PATH)){this.payload=JSON.parse((0,r.readFileSync)(process.env.GITHUB_EVENT_PATH,{encoding:"utf8"}))}else{const e=process.env.GITHUB_EVENT_PATH;process.stdout.write(`GITHUB_EVENT_PATH ${e} does not exist${s.EOL}`)}}this.eventName=process.env.GITHUB_EVENT_NAME;this.sha=process.env.GITHUB_SHA;this.ref=process.env.GITHUB_REF;this.workflow=process.env.GITHUB_WORKFLOW;this.action=process.env.GITHUB_ACTION;this.actor=process.env.GITHUB_ACTOR;this.job=process.env.GITHUB_JOB;this.runNumber=parseInt(process.env.GITHUB_RUN_NUMBER,10);this.runId=parseInt(process.env.GITHUB_RUN_ID,10);this.apiUrl=(e=process.env.GITHUB_API_URL)!==null&&e!==void 0?e:`https://api.github.com`;this.serverUrl=(A=process.env.GITHUB_SERVER_URL)!==null&&A!==void 0?A:`https://github.com`;this.graphqlUrl=(t=process.env.GITHUB_GRAPHQL_URL)!==null&&t!==void 0?t:`https://api.github.com/graphql`}get issue(){const e=this.payload;return Object.assign(Object.assign({},this.repo),{number:(e.issue||e.pull_request||e).number})}get repo(){if(process.env.GITHUB_REPOSITORY){const[e,A]=process.env.GITHUB_REPOSITORY.split("/");return{owner:e,repo:A}}if(this.payload.repository){return{owner:this.payload.repository.owner.login,repo:this.payload.repository.name}}throw new Error("context.repo requires a GITHUB_REPOSITORY environment variable like 'owner/repo'")}}A.Context=Context},9156:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.prototype.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};Object.defineProperty(A,"__esModule",{value:true});A.getOctokit=A.context=void 0;const n=o(t(88));const i=t(2942);A.context=new n.Context;function getOctokit(e,A,...t){const r=i.GitHub.plugin(...t);return new r((0,i.getOctokitOptions)(e,A))}A.getOctokit=getOctokit},9644:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.prototype.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};var n=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};Object.defineProperty(A,"__esModule",{value:true});A.getApiBaseUrl=A.getProxyFetch=A.getProxyAgentDispatcher=A.getProxyAgent=A.getAuthString=void 0;const i=o(t(5290));const a=t(4770);function getAuthString(e,A){if(!e&&!A.auth){throw new Error("Parameter token or opts.auth is required")}else if(e&&A.auth){throw new Error("Parameters token and opts.auth may not both be specified")}return typeof A.auth==="string"?A.auth:`token ${e}`}A.getAuthString=getAuthString;function getProxyAgent(e){const A=new i.HttpClient;return A.getAgent(e)}A.getProxyAgent=getProxyAgent;function getProxyAgentDispatcher(e){const A=new i.HttpClient;return A.getAgentDispatcher(e)}A.getProxyAgentDispatcher=getProxyAgentDispatcher;function getProxyFetch(e){const A=getProxyAgentDispatcher(e);const proxyFetch=(e,t)=>n(this,void 0,void 0,(function*(){return(0,a.fetch)(e,Object.assign(Object.assign({},t),{dispatcher:A}))}));return proxyFetch}A.getProxyFetch=getProxyFetch;function getApiBaseUrl(){return process.env["GITHUB_API_URL"]||"https://api.github.com"}A.getApiBaseUrl=getApiBaseUrl},2942:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.prototype.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};Object.defineProperty(A,"__esModule",{value:true});A.getOctokitOptions=A.GitHub=A.defaults=A.context=void 0;const n=o(t(88));const i=o(t(9644));const a=t(8850);const c=t(9389);const g=t(3895);A.context=new n.Context;const E=i.getApiBaseUrl();A.defaults={baseUrl:E,request:{agent:i.getProxyAgent(E),fetch:i.getProxyFetch(E)}};A.GitHub=a.Octokit.plugin(c.restEndpointMethods,g.paginateRest).defaults(A.defaults);function getOctokitOptions(e,A){const t=Object.assign({},A||{});const r=i.getAuthString(e,t);if(r){t.auth=r}return t}A.getOctokitOptions=getOctokitOptions},8358:function(e,A){"use strict";var t=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};Object.defineProperty(A,"__esModule",{value:true});A.PersonalAccessTokenCredentialHandler=A.BearerCredentialHandler=A.BasicCredentialHandler=void 0;class BasicCredentialHandler{constructor(e,A){this.username=e;this.password=A}prepareRequest(e){if(!e.headers){throw Error("The request has no headers")}e.headers["Authorization"]=`Basic ${Buffer.from(`${this.username}:${this.password}`).toString("base64")}`}canHandleAuthentication(){return false}handleAuthentication(){return t(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}A.BasicCredentialHandler=BasicCredentialHandler;class BearerCredentialHandler{constructor(e){this.token=e}prepareRequest(e){if(!e.headers){throw Error("The request has no headers")}e.headers["Authorization"]=`Bearer ${this.token}`}canHandleAuthentication(){return false}handleAuthentication(){return t(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}A.BearerCredentialHandler=BearerCredentialHandler;class PersonalAccessTokenCredentialHandler{constructor(e){this.token=e}prepareRequest(e){if(!e.headers){throw Error("The request has no headers")}e.headers["Authorization"]=`Basic ${Buffer.from(`PAT:${this.token}`).toString("base64")}`}canHandleAuthentication(){return false}handleAuthentication(){return t(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}A.PersonalAccessTokenCredentialHandler=PersonalAccessTokenCredentialHandler},5290:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.prototype.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};var n=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};Object.defineProperty(A,"__esModule",{value:true});A.HttpClient=A.isHttps=A.HttpClientResponse=A.HttpClientError=A.getProxyUrl=A.MediaTypes=A.Headers=A.HttpCodes=void 0;const i=o(t(8611));const a=o(t(5692));const c=o(t(8758));const g=o(t(7285));const E=t(4770);var l;(function(e){e[e["OK"]=200]="OK";e[e["MultipleChoices"]=300]="MultipleChoices";e[e["MovedPermanently"]=301]="MovedPermanently";e[e["ResourceMoved"]=302]="ResourceMoved";e[e["SeeOther"]=303]="SeeOther";e[e["NotModified"]=304]="NotModified";e[e["UseProxy"]=305]="UseProxy";e[e["SwitchProxy"]=306]="SwitchProxy";e[e["TemporaryRedirect"]=307]="TemporaryRedirect";e[e["PermanentRedirect"]=308]="PermanentRedirect";e[e["BadRequest"]=400]="BadRequest";e[e["Unauthorized"]=401]="Unauthorized";e[e["PaymentRequired"]=402]="PaymentRequired";e[e["Forbidden"]=403]="Forbidden";e[e["NotFound"]=404]="NotFound";e[e["MethodNotAllowed"]=405]="MethodNotAllowed";e[e["NotAcceptable"]=406]="NotAcceptable";e[e["ProxyAuthenticationRequired"]=407]="ProxyAuthenticationRequired";e[e["RequestTimeout"]=408]="RequestTimeout";e[e["Conflict"]=409]="Conflict";e[e["Gone"]=410]="Gone";e[e["TooManyRequests"]=429]="TooManyRequests";e[e["InternalServerError"]=500]="InternalServerError";e[e["NotImplemented"]=501]="NotImplemented";e[e["BadGateway"]=502]="BadGateway";e[e["ServiceUnavailable"]=503]="ServiceUnavailable";e[e["GatewayTimeout"]=504]="GatewayTimeout"})(l||(A.HttpCodes=l={}));var u;(function(e){e["Accept"]="accept";e["ContentType"]="content-type"})(u||(A.Headers=u={}));var Q;(function(e){e["ApplicationJson"]="application/json"})(Q||(A.MediaTypes=Q={}));function getProxyUrl(e){const A=c.getProxyUrl(new URL(e));return A?A.href:""}A.getProxyUrl=getProxyUrl;const C=[l.MovedPermanently,l.ResourceMoved,l.SeeOther,l.TemporaryRedirect,l.PermanentRedirect];const h=[l.BadGateway,l.ServiceUnavailable,l.GatewayTimeout];const B=["OPTIONS","GET","DELETE","HEAD"];const I=10;const d=5;class HttpClientError extends Error{constructor(e,A){super(e);this.name="HttpClientError";this.statusCode=A;Object.setPrototypeOf(this,HttpClientError.prototype)}}A.HttpClientError=HttpClientError;class HttpClientResponse{constructor(e){this.message=e}readBody(){return n(this,void 0,void 0,(function*(){return new Promise((e=>n(this,void 0,void 0,(function*(){let A=Buffer.alloc(0);this.message.on("data",(e=>{A=Buffer.concat([A,e])}));this.message.on("end",(()=>{e(A.toString())}))}))))}))}readBodyBuffer(){return n(this,void 0,void 0,(function*(){return new Promise((e=>n(this,void 0,void 0,(function*(){const A=[];this.message.on("data",(e=>{A.push(e)}));this.message.on("end",(()=>{e(Buffer.concat(A))}))}))))}))}}A.HttpClientResponse=HttpClientResponse;function isHttps(e){const A=new URL(e);return A.protocol==="https:"}A.isHttps=isHttps;class HttpClient{constructor(e,A,t){this._ignoreSslError=false;this._allowRedirects=true;this._allowRedirectDowngrade=false;this._maxRedirects=50;this._allowRetries=false;this._maxRetries=1;this._keepAlive=false;this._disposed=false;this.userAgent=e;this.handlers=A||[];this.requestOptions=t;if(t){if(t.ignoreSslError!=null){this._ignoreSslError=t.ignoreSslError}this._socketTimeout=t.socketTimeout;if(t.allowRedirects!=null){this._allowRedirects=t.allowRedirects}if(t.allowRedirectDowngrade!=null){this._allowRedirectDowngrade=t.allowRedirectDowngrade}if(t.maxRedirects!=null){this._maxRedirects=Math.max(t.maxRedirects,0)}if(t.keepAlive!=null){this._keepAlive=t.keepAlive}if(t.allowRetries!=null){this._allowRetries=t.allowRetries}if(t.maxRetries!=null){this._maxRetries=t.maxRetries}}}options(e,A){return n(this,void 0,void 0,(function*(){return this.request("OPTIONS",e,null,A||{})}))}get(e,A){return n(this,void 0,void 0,(function*(){return this.request("GET",e,null,A||{})}))}del(e,A){return n(this,void 0,void 0,(function*(){return this.request("DELETE",e,null,A||{})}))}post(e,A,t){return n(this,void 0,void 0,(function*(){return this.request("POST",e,A,t||{})}))}patch(e,A,t){return n(this,void 0,void 0,(function*(){return this.request("PATCH",e,A,t||{})}))}put(e,A,t){return n(this,void 0,void 0,(function*(){return this.request("PUT",e,A,t||{})}))}head(e,A){return n(this,void 0,void 0,(function*(){return this.request("HEAD",e,null,A||{})}))}sendStream(e,A,t,r){return n(this,void 0,void 0,(function*(){return this.request(e,A,t,r)}))}getJson(e,A={}){return n(this,void 0,void 0,(function*(){A[u.Accept]=this._getExistingOrDefaultHeader(A,u.Accept,Q.ApplicationJson);const t=yield this.get(e,A);return this._processResponse(t,this.requestOptions)}))}postJson(e,A,t={}){return n(this,void 0,void 0,(function*(){const r=JSON.stringify(A,null,2);t[u.Accept]=this._getExistingOrDefaultHeader(t,u.Accept,Q.ApplicationJson);t[u.ContentType]=this._getExistingOrDefaultHeader(t,u.ContentType,Q.ApplicationJson);const s=yield this.post(e,r,t);return this._processResponse(s,this.requestOptions)}))}putJson(e,A,t={}){return n(this,void 0,void 0,(function*(){const r=JSON.stringify(A,null,2);t[u.Accept]=this._getExistingOrDefaultHeader(t,u.Accept,Q.ApplicationJson);t[u.ContentType]=this._getExistingOrDefaultHeader(t,u.ContentType,Q.ApplicationJson);const s=yield this.put(e,r,t);return this._processResponse(s,this.requestOptions)}))}patchJson(e,A,t={}){return n(this,void 0,void 0,(function*(){const r=JSON.stringify(A,null,2);t[u.Accept]=this._getExistingOrDefaultHeader(t,u.Accept,Q.ApplicationJson);t[u.ContentType]=this._getExistingOrDefaultHeader(t,u.ContentType,Q.ApplicationJson);const s=yield this.patch(e,r,t);return this._processResponse(s,this.requestOptions)}))}request(e,A,t,r){return n(this,void 0,void 0,(function*(){if(this._disposed){throw new Error("Client has already been disposed.")}const s=new URL(A);let o=this._prepareRequest(e,s,r);const n=this._allowRetries&&B.includes(e)?this._maxRetries+1:1;let i=0;let a;do{a=yield this.requestRaw(o,t);if(a&&a.message&&a.message.statusCode===l.Unauthorized){let e;for(const A of this.handlers){if(A.canHandleAuthentication(a)){e=A;break}}if(e){return e.handleAuthentication(this,o,t)}else{return a}}let A=this._maxRedirects;while(a.message.statusCode&&C.includes(a.message.statusCode)&&this._allowRedirects&&A>0){const n=a.message.headers["location"];if(!n){break}const i=new URL(n);if(s.protocol==="https:"&&s.protocol!==i.protocol&&!this._allowRedirectDowngrade){throw new Error("Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.")}yield a.readBody();if(i.hostname!==s.hostname){for(const e in r){if(e.toLowerCase()==="authorization"){delete r[e]}}}o=this._prepareRequest(e,i,r);a=yield this.requestRaw(o,t);A--}if(!a.message.statusCode||!h.includes(a.message.statusCode)){return a}i+=1;if(i{function callbackForResult(e,A){if(e){r(e)}else if(!A){r(new Error("Unknown error"))}else{t(A)}}this.requestRawWithCallback(e,A,callbackForResult)}))}))}requestRawWithCallback(e,A,t){if(typeof A==="string"){if(!e.options.headers){e.options.headers={}}e.options.headers["Content-Length"]=Buffer.byteLength(A,"utf8")}let r=false;function handleResult(e,A){if(!r){r=true;t(e,A)}}const s=e.httpModule.request(e.options,(e=>{const A=new HttpClientResponse(e);handleResult(undefined,A)}));let o;s.on("socket",(e=>{o=e}));s.setTimeout(this._socketTimeout||3*6e4,(()=>{if(o){o.end()}handleResult(new Error(`Request timeout: ${e.options.path}`))}));s.on("error",(function(e){handleResult(e)}));if(A&&typeof A==="string"){s.write(A,"utf8")}if(A&&typeof A!=="string"){A.on("close",(function(){s.end()}));A.pipe(s)}else{s.end()}}getAgent(e){const A=new URL(e);return this._getAgent(A)}getAgentDispatcher(e){const A=new URL(e);const t=c.getProxyUrl(A);const r=t&&t.hostname;if(!r){return}return this._getProxyAgentDispatcher(A,t)}_prepareRequest(e,A,t){const r={};r.parsedUrl=A;const s=r.parsedUrl.protocol==="https:";r.httpModule=s?a:i;const o=s?443:80;r.options={};r.options.host=r.parsedUrl.hostname;r.options.port=r.parsedUrl.port?parseInt(r.parsedUrl.port):o;r.options.path=(r.parsedUrl.pathname||"")+(r.parsedUrl.search||"");r.options.method=e;r.options.headers=this._mergeHeaders(t);if(this.userAgent!=null){r.options.headers["user-agent"]=this.userAgent}r.options.agent=this._getAgent(r.parsedUrl);if(this.handlers){for(const e of this.handlers){e.prepareRequest(r.options)}}return r}_mergeHeaders(e){if(this.requestOptions&&this.requestOptions.headers){return Object.assign({},lowercaseKeys(this.requestOptions.headers),lowercaseKeys(e||{}))}return lowercaseKeys(e||{})}_getExistingOrDefaultHeader(e,A,t){let r;if(this.requestOptions&&this.requestOptions.headers){r=lowercaseKeys(this.requestOptions.headers)[A]}return e[A]||r||t}_getAgent(e){let A;const t=c.getProxyUrl(e);const r=t&&t.hostname;if(this._keepAlive&&r){A=this._proxyAgent}if(!r){A=this._agent}if(A){return A}const s=e.protocol==="https:";let o=100;if(this.requestOptions){o=this.requestOptions.maxSockets||i.globalAgent.maxSockets}if(t&&t.hostname){const e={maxSockets:o,keepAlive:this._keepAlive,proxy:Object.assign(Object.assign({},(t.username||t.password)&&{proxyAuth:`${t.username}:${t.password}`}),{host:t.hostname,port:t.port})};let r;const n=t.protocol==="https:";if(s){r=n?g.httpsOverHttps:g.httpsOverHttp}else{r=n?g.httpOverHttps:g.httpOverHttp}A=r(e);this._proxyAgent=A}if(!A){const e={keepAlive:this._keepAlive,maxSockets:o};A=s?new a.Agent(e):new i.Agent(e);this._agent=A}if(s&&this._ignoreSslError){A.options=Object.assign(A.options||{},{rejectUnauthorized:false})}return A}_getProxyAgentDispatcher(e,A){let t;if(this._keepAlive){t=this._proxyAgentDispatcher}if(t){return t}const r=e.protocol==="https:";t=new E.ProxyAgent(Object.assign({uri:A.href,pipelining:!this._keepAlive?0:1},(A.username||A.password)&&{token:`Basic ${Buffer.from(`${A.username}:${A.password}`).toString("base64")}`}));this._proxyAgentDispatcher=t;if(r&&this._ignoreSslError){t.options=Object.assign(t.options.requestTls||{},{rejectUnauthorized:false})}return t}_performExponentialBackoff(e){return n(this,void 0,void 0,(function*(){e=Math.min(I,e);const A=d*Math.pow(2,e);return new Promise((e=>setTimeout((()=>e()),A)))}))}_processResponse(e,A){return n(this,void 0,void 0,(function*(){return new Promise(((t,r)=>n(this,void 0,void 0,(function*(){const s=e.message.statusCode||0;const o={statusCode:s,result:null,headers:{}};if(s===l.NotFound){t(o)}function dateTimeDeserializer(e,A){if(typeof A==="string"){const e=new Date(A);if(!isNaN(e.valueOf())){return e}}return A}let n;let i;try{i=yield e.readBody();if(i&&i.length>0){if(A&&A.deserializeDates){n=JSON.parse(i,dateTimeDeserializer)}else{n=JSON.parse(i)}o.result=n}o.headers=e.message.headers}catch(e){}if(s>299){let e;if(n&&n.message){e=n.message}else if(i&&i.length>0){e=i}else{e=`Failed request: (${s})`}const A=new HttpClientError(e,s);A.result=o.result;r(A)}else{t(o)}}))))}))}}A.HttpClient=HttpClient;const lowercaseKeys=e=>Object.keys(e).reduce(((A,t)=>(A[t.toLowerCase()]=e[t],A)),{})},8758:(e,A)=>{"use strict";Object.defineProperty(A,"__esModule",{value:true});A.checkBypass=A.getProxyUrl=void 0;function getProxyUrl(e){const A=e.protocol==="https:";if(checkBypass(e)){return undefined}const t=(()=>{if(A){return process.env["https_proxy"]||process.env["HTTPS_PROXY"]}else{return process.env["http_proxy"]||process.env["HTTP_PROXY"]}})();if(t){try{return new DecodedURL(t)}catch(e){if(!t.startsWith("http://")&&!t.startsWith("https://"))return new DecodedURL(`http://${t}`)}}else{return undefined}}A.getProxyUrl=getProxyUrl;function checkBypass(e){if(!e.hostname){return false}const A=e.hostname;if(isLoopbackAddress(A)){return true}const t=process.env["no_proxy"]||process.env["NO_PROXY"]||"";if(!t){return false}let r;if(e.port){r=Number(e.port)}else if(e.protocol==="http:"){r=80}else if(e.protocol==="https:"){r=443}const s=[e.hostname.toUpperCase()];if(typeof r==="number"){s.push(`${s[0]}:${r}`)}for(const e of t.split(",").map((e=>e.trim().toUpperCase())).filter((e=>e))){if(e==="*"||s.some((A=>A===e||A.endsWith(`.${e}`)||e.startsWith(".")&&A.endsWith(`${e}`)))){return true}}return false}A.checkBypass=checkBypass;function isLoopbackAddress(e){const A=e.toLowerCase();return A==="localhost"||A.startsWith("127.")||A.startsWith("[::1]")||A.startsWith("[0:0:0:0:0:0:0:1]")}class DecodedURL extends URL{constructor(e,A){super(e,A);this._decodedUsername=decodeURIComponent(super.username);this._decodedPassword=decodeURIComponent(super.password)}get username(){return this._decodedUsername}get password(){return this._decodedPassword}}},2667:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;Object.defineProperty(e,r,{enumerable:true,get:function(){return A[t]}})}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};var n=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};var i;Object.defineProperty(A,"__esModule",{value:true});A.getCmdPath=A.tryGetExecutablePath=A.isRooted=A.isDirectory=A.exists=A.READONLY=A.UV_FS_O_EXLOCK=A.IS_WINDOWS=A.unlink=A.symlink=A.stat=A.rmdir=A.rm=A.rename=A.readlink=A.readdir=A.open=A.mkdir=A.lstat=A.copyFile=A.chmod=void 0;const a=o(t(9896));const c=o(t(6928));i=a.promises,A.chmod=i.chmod,A.copyFile=i.copyFile,A.lstat=i.lstat,A.mkdir=i.mkdir,A.open=i.open,A.readdir=i.readdir,A.readlink=i.readlink,A.rename=i.rename,A.rm=i.rm,A.rmdir=i.rmdir,A.stat=i.stat,A.symlink=i.symlink,A.unlink=i.unlink;A.IS_WINDOWS=process.platform==="win32";A.UV_FS_O_EXLOCK=268435456;A.READONLY=a.constants.O_RDONLY;function exists(e){return n(this,void 0,void 0,(function*(){try{yield A.stat(e)}catch(e){if(e.code==="ENOENT"){return false}throw e}return true}))}A.exists=exists;function isDirectory(e,t=false){return n(this,void 0,void 0,(function*(){const r=t?yield A.stat(e):yield A.lstat(e);return r.isDirectory()}))}A.isDirectory=isDirectory;function isRooted(e){e=normalizeSeparators(e);if(!e){throw new Error('isRooted() parameter "p" cannot be empty')}if(A.IS_WINDOWS){return e.startsWith("\\")||/^[A-Z]:/i.test(e)}return e.startsWith("/")}A.isRooted=isRooted;function tryGetExecutablePath(e,t){return n(this,void 0,void 0,(function*(){let r=undefined;try{r=yield A.stat(e)}catch(A){if(A.code!=="ENOENT"){console.log(`Unexpected error attempting to determine if executable file exists '${e}': ${A}`)}}if(r&&r.isFile()){if(A.IS_WINDOWS){const A=c.extname(e).toUpperCase();if(t.some((e=>e.toUpperCase()===A))){return e}}else{if(isUnixExecutable(r)){return e}}}const s=e;for(const o of t){e=s+o;r=undefined;try{r=yield A.stat(e)}catch(A){if(A.code!=="ENOENT"){console.log(`Unexpected error attempting to determine if executable file exists '${e}': ${A}`)}}if(r&&r.isFile()){if(A.IS_WINDOWS){try{const t=c.dirname(e);const r=c.basename(e).toUpperCase();for(const s of yield A.readdir(t)){if(r===s.toUpperCase()){e=c.join(t,s);break}}}catch(A){console.log(`Unexpected error attempting to determine the actual case of the file '${e}': ${A}`)}return e}else{if(isUnixExecutable(r)){return e}}}}return""}))}A.tryGetExecutablePath=tryGetExecutablePath;function normalizeSeparators(e){e=e||"";if(A.IS_WINDOWS){e=e.replace(/\//g,"\\");return e.replace(/\\\\+/g,"\\")}return e.replace(/\/\/+/g,"/")}function isUnixExecutable(e){return(e.mode&1)>0||(e.mode&8)>0&&e.gid===process.getgid()||(e.mode&64)>0&&e.uid===process.getuid()}function getCmdPath(){var e;return(e=process.env["COMSPEC"])!==null&&e!==void 0?e:`cmd.exe`}A.getCmdPath=getCmdPath},4398:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;Object.defineProperty(e,r,{enumerable:true,get:function(){return A[t]}})}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t in e)if(t!=="default"&&Object.hasOwnProperty.call(e,t))r(A,e,t);s(A,e);return A};var n=this&&this.__awaiter||function(e,A,t,r){function adopt(e){return e instanceof t?e:new t((function(A){A(e)}))}return new(t||(t=Promise))((function(t,s){function fulfilled(e){try{step(r.next(e))}catch(e){s(e)}}function rejected(e){try{step(r["throw"](e))}catch(e){s(e)}}function step(e){e.done?t(e.value):adopt(e.value).then(fulfilled,rejected)}step((r=r.apply(e,A||[])).next())}))};Object.defineProperty(A,"__esModule",{value:true});A.findInPath=A.which=A.mkdirP=A.rmRF=A.mv=A.cp=void 0;const i=t(2613);const a=o(t(6928));const c=o(t(2667));function cp(e,A,t={}){return n(this,void 0,void 0,(function*(){const{force:r,recursive:s,copySourceDirectory:o}=readCopyOptions(t);const n=(yield c.exists(A))?yield c.stat(A):null;if(n&&n.isFile()&&!r){return}const i=n&&n.isDirectory()&&o?a.join(A,a.basename(e)):A;if(!(yield c.exists(e))){throw new Error(`no such file or directory: ${e}`)}const g=yield c.stat(e);if(g.isDirectory()){if(!s){throw new Error(`Failed to copy. ${e} is a directory, but tried to copy without recursive flag.`)}else{yield cpDirRecursive(e,i,0,r)}}else{if(a.relative(e,i)===""){throw new Error(`'${i}' and '${e}' are the same file`)}yield copyFile(e,i,r)}}))}A.cp=cp;function mv(e,A,t={}){return n(this,void 0,void 0,(function*(){if(yield c.exists(A)){let r=true;if(yield c.isDirectory(A)){A=a.join(A,a.basename(e));r=yield c.exists(A)}if(r){if(t.force==null||t.force){yield rmRF(A)}else{throw new Error("Destination already exists")}}}yield mkdirP(a.dirname(A));yield c.rename(e,A)}))}A.mv=mv;function rmRF(e){return n(this,void 0,void 0,(function*(){if(c.IS_WINDOWS){if(/[*"<>|]/.test(e)){throw new Error('File path must not contain `*`, `"`, `<`, `>` or `|` on Windows')}}try{yield c.rm(e,{force:true,maxRetries:3,recursive:true,retryDelay:300})}catch(e){throw new Error(`File was unable to be removed ${e}`)}}))}A.rmRF=rmRF;function mkdirP(e){return n(this,void 0,void 0,(function*(){i.ok(e,"a path argument must be provided");yield c.mkdir(e,{recursive:true})}))}A.mkdirP=mkdirP;function which(e,A){return n(this,void 0,void 0,(function*(){if(!e){throw new Error("parameter 'tool' is required")}if(A){const A=yield which(e,false);if(!A){if(c.IS_WINDOWS){throw new Error(`Unable to locate executable file: ${e}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`)}else{throw new Error(`Unable to locate executable file: ${e}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`)}}return A}const t=yield findInPath(e);if(t&&t.length>0){return t[0]}return""}))}A.which=which;function findInPath(e){return n(this,void 0,void 0,(function*(){if(!e){throw new Error("parameter 'tool' is required")}const A=[];if(c.IS_WINDOWS&&process.env["PATHEXT"]){for(const e of process.env["PATHEXT"].split(a.delimiter)){if(e){A.push(e)}}}if(c.isRooted(e)){const t=yield c.tryGetExecutablePath(e,A);if(t){return[t]}return[]}if(e.includes(a.sep)){return[]}const t=[];if(process.env.PATH){for(const e of process.env.PATH.split(a.delimiter)){if(e){t.push(e)}}}const r=[];for(const s of t){const t=yield c.tryGetExecutablePath(a.join(s,e),A);if(t){r.push(t)}}return r}))}A.findInPath=findInPath;function readCopyOptions(e){const A=e.force==null?true:e.force;const t=Boolean(e.recursive);const r=e.copySourceDirectory==null?true:Boolean(e.copySourceDirectory);return{force:A,recursive:t,copySourceDirectory:r}}function cpDirRecursive(e,A,t,r){return n(this,void 0,void 0,(function*(){if(t>=255)return;t++;yield mkdirP(A);const s=yield c.readdir(e);for(const o of s){const s=`${e}/${o}`;const n=`${A}/${o}`;const i=yield c.lstat(s);if(i.isDirectory()){yield cpDirRecursive(s,n,t,r)}else{yield copyFile(s,n,r)}}yield c.chmod(A,(yield c.stat(e)).mode)}))}function copyFile(e,A,t){return n(this,void 0,void 0,(function*(){if((yield c.lstat(e)).isSymbolicLink()){try{yield c.lstat(A);yield c.unlink(A)}catch(e){if(e.code==="EPERM"){yield c.chmod(A,"0666");yield c.unlink(A)}}const t=yield c.readlink(e);yield c.symlink(t,A,c.IS_WINDOWS?"junction":null)}else if(!(yield c.exists(A))||t){yield c.copyFile(e,A)}}))}},3907:e=>{"use strict";var A=Object.defineProperty;var t=Object.getOwnPropertyDescriptor;var r=Object.getOwnPropertyNames;var s=Object.prototype.hasOwnProperty;var __export=(e,t)=>{for(var r in t)A(e,r,{get:t[r],enumerable:true})};var __copyProps=(e,o,n,i)=>{if(o&&typeof o==="object"||typeof o==="function"){for(let a of r(o))if(!s.call(e,a)&&a!==n)A(e,a,{get:()=>o[a],enumerable:!(i=t(o,a))||i.enumerable})}return e};var __toCommonJS=e=>__copyProps(A({},"__esModule",{value:true}),e);var o={};__export(o,{createTokenAuth:()=>c});e.exports=__toCommonJS(o);var n=/^v1\./;var i=/^ghs_/;var a=/^ghu_/;async function auth(e){const A=e.split(/\./).length===3;const t=n.test(e)||i.test(e);const r=a.test(e);const s=A?"app":t?"installation":r?"user-to-server":"oauth";return{type:"token",token:e,tokenType:s}}function withAuthorizationPrefix(e){if(e.split(/\./).length===3){return`bearer ${e}`}return`token ${e}`}async function hook(e,A,t,r){const s=A.endpoint.merge(t,r);s.headers.authorization=withAuthorizationPrefix(e);return A(s)}var c=function createTokenAuth2(e){if(!e){throw new Error("[@octokit/auth-token] No token passed to createTokenAuth")}if(typeof e!=="string"){throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string")}e=e.replace(/^(token|bearer) +/i,"");return Object.assign(auth.bind(null,e),{hook:hook.bind(null,e)})};0&&0},8850:(e,A,t)=>{"use strict";var r=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var o=Object.getOwnPropertyNames;var n=Object.prototype.hasOwnProperty;var __export=(e,A)=>{for(var t in A)r(e,t,{get:A[t],enumerable:true})};var __copyProps=(e,A,t,i)=>{if(A&&typeof A==="object"||typeof A==="function"){for(let a of o(A))if(!n.call(e,a)&&a!==t)r(e,a,{get:()=>A[a],enumerable:!(i=s(A,a))||i.enumerable})}return e};var __toCommonJS=e=>__copyProps(r({},"__esModule",{value:true}),e);var i={};__export(i,{Octokit:()=>B});e.exports=__toCommonJS(i);var a=t(9653);var c=t(7932);var g=t(8662);var E=t(6536);var l=t(3907);var u="5.0.2";var noop=()=>{};var Q=console.warn.bind(console);var C=console.error.bind(console);var h=`octokit-core.js/${u} ${(0,a.getUserAgent)()}`;var B=class{static{this.VERSION=u}static defaults(e){const A=class extends(this){constructor(...A){const t=A[0]||{};if(typeof e==="function"){super(e(t));return}super(Object.assign({},e,t,t.userAgent&&e.userAgent?{userAgent:`${t.userAgent} ${e.userAgent}`}:null))}};return A}static{this.plugins=[]}static plugin(...e){const A=this.plugins;const t=class extends(this){static{this.plugins=A.concat(e.filter((e=>!A.includes(e))))}};return t}constructor(e={}){const A=new c.Collection;const t={baseUrl:g.request.endpoint.DEFAULTS.baseUrl,headers:{},request:Object.assign({},e.request,{hook:A.bind(null,"request")}),mediaType:{previews:[],format:""}};t.headers["user-agent"]=e.userAgent?`${e.userAgent} ${h}`:h;if(e.baseUrl){t.baseUrl=e.baseUrl}if(e.previews){t.mediaType.previews=e.previews}if(e.timeZone){t.headers["time-zone"]=e.timeZone}this.request=g.request.defaults(t);this.graphql=(0,E.withCustomRequest)(this.request).defaults(t);this.log=Object.assign({debug:noop,info:noop,warn:Q,error:C},e.log);this.hook=A;if(!e.authStrategy){if(!e.auth){this.auth=async()=>({type:"unauthenticated"})}else{const t=(0,l.createTokenAuth)(e.auth);A.wrap("request",t.hook);this.auth=t}}else{const{authStrategy:t,...r}=e;const s=t(Object.assign({request:this.request,log:this.log,octokit:this,octokitOptions:r},e.auth));A.wrap("request",s.hook);this.auth=s}const r=this.constructor;for(let A=0;A{"use strict";var r=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var o=Object.getOwnPropertyNames;var n=Object.prototype.hasOwnProperty;var __export=(e,A)=>{for(var t in A)r(e,t,{get:A[t],enumerable:true})};var __copyProps=(e,A,t,i)=>{if(A&&typeof A==="object"||typeof A==="function"){for(let a of o(A))if(!n.call(e,a)&&a!==t)r(e,a,{get:()=>A[a],enumerable:!(i=s(A,a))||i.enumerable})}return e};var __toCommonJS=e=>__copyProps(r({},"__esModule",{value:true}),e);var i={};__export(i,{endpoint:()=>u});e.exports=__toCommonJS(i);var a=t(9653);var c="9.0.4";var g=`octokit-endpoint.js/${c} ${(0,a.getUserAgent)()}`;var E={method:"GET",baseUrl:"https://api.github.com",headers:{accept:"application/vnd.github.v3+json","user-agent":g},mediaType:{format:""}};function lowercaseKeys(e){if(!e){return{}}return Object.keys(e).reduce(((A,t)=>{A[t.toLowerCase()]=e[t];return A}),{})}function isPlainObject(e){if(typeof e!=="object"||e===null)return false;if(Object.prototype.toString.call(e)!=="[object Object]")return false;const A=Object.getPrototypeOf(e);if(A===null)return true;const t=Object.prototype.hasOwnProperty.call(A,"constructor")&&A.constructor;return typeof t==="function"&&t instanceof t&&Function.prototype.call(t)===Function.prototype.call(e)}function mergeDeep(e,A){const t=Object.assign({},e);Object.keys(A).forEach((r=>{if(isPlainObject(A[r])){if(!(r in e))Object.assign(t,{[r]:A[r]});else t[r]=mergeDeep(e[r],A[r])}else{Object.assign(t,{[r]:A[r]})}}));return t}function removeUndefinedProperties(e){for(const A in e){if(e[A]===void 0){delete e[A]}}return e}function merge(e,A,t){if(typeof A==="string"){let[e,r]=A.split(" ");t=Object.assign(r?{method:e,url:r}:{url:e},t)}else{t=Object.assign({},A)}t.headers=lowercaseKeys(t.headers);removeUndefinedProperties(t);removeUndefinedProperties(t.headers);const r=mergeDeep(e||{},t);if(t.url==="/graphql"){if(e&&e.mediaType.previews?.length){r.mediaType.previews=e.mediaType.previews.filter((e=>!r.mediaType.previews.includes(e))).concat(r.mediaType.previews)}r.mediaType.previews=(r.mediaType.previews||[]).map((e=>e.replace(/-preview/,"")))}return r}function addQueryParameters(e,A){const t=/\?/.test(e)?"&":"?";const r=Object.keys(A);if(r.length===0){return e}return e+t+r.map((e=>{if(e==="q"){return"q="+A.q.split("+").map(encodeURIComponent).join("+")}return`${e}=${encodeURIComponent(A[e])}`})).join("&")}var l=/\{[^}]+\}/g;function removeNonChars(e){return e.replace(/^\W+|\W+$/g,"").split(/,/)}function extractUrlVariableNames(e){const A=e.match(l);if(!A){return[]}return A.map(removeNonChars).reduce(((e,A)=>e.concat(A)),[])}function omit(e,A){const t={__proto__:null};for(const r of Object.keys(e)){if(A.indexOf(r)===-1){t[r]=e[r]}}return t}function encodeReserved(e){return e.split(/(%[0-9A-Fa-f]{2})/g).map((function(e){if(!/%[0-9A-Fa-f]/.test(e)){e=encodeURI(e).replace(/%5B/g,"[").replace(/%5D/g,"]")}return e})).join("")}function encodeUnreserved(e){return encodeURIComponent(e).replace(/[!'()*]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}))}function encodeValue(e,A,t){A=e==="+"||e==="#"?encodeReserved(A):encodeUnreserved(A);if(t){return encodeUnreserved(t)+"="+A}else{return A}}function isDefined(e){return e!==void 0&&e!==null}function isKeyOperator(e){return e===";"||e==="&"||e==="?"}function getValues(e,A,t,r){var s=e[t],o=[];if(isDefined(s)&&s!==""){if(typeof s==="string"||typeof s==="number"||typeof s==="boolean"){s=s.toString();if(r&&r!=="*"){s=s.substring(0,parseInt(r,10))}o.push(encodeValue(A,s,isKeyOperator(A)?t:""))}else{if(r==="*"){if(Array.isArray(s)){s.filter(isDefined).forEach((function(e){o.push(encodeValue(A,e,isKeyOperator(A)?t:""))}))}else{Object.keys(s).forEach((function(e){if(isDefined(s[e])){o.push(encodeValue(A,s[e],e))}}))}}else{const e=[];if(Array.isArray(s)){s.filter(isDefined).forEach((function(t){e.push(encodeValue(A,t))}))}else{Object.keys(s).forEach((function(t){if(isDefined(s[t])){e.push(encodeUnreserved(t));e.push(encodeValue(A,s[t].toString()))}}))}if(isKeyOperator(A)){o.push(encodeUnreserved(t)+"="+e.join(","))}else if(e.length!==0){o.push(e.join(","))}}}}else{if(A===";"){if(isDefined(s)){o.push(encodeUnreserved(t))}}else if(s===""&&(A==="&"||A==="?")){o.push(encodeUnreserved(t)+"=")}else if(s===""){o.push("")}}return o}function parseUrl(e){return{expand:expand.bind(null,e)}}function expand(e,A){var t=["+","#",".","/",";","?","&"];e=e.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g,(function(e,r,s){if(r){let e="";const s=[];if(t.indexOf(r.charAt(0))!==-1){e=r.charAt(0);r=r.substr(1)}r.split(/,/g).forEach((function(t){var r=/([^:\*]*)(?::(\d+)|(\*))?/.exec(t);s.push(getValues(A,e,r[1],r[2]||r[3]))}));if(e&&e!=="+"){var o=",";if(e==="?"){o="&"}else if(e!=="#"){o=e}return(s.length!==0?e:"")+s.join(o)}else{return s.join(",")}}else{return encodeReserved(s)}}));if(e==="/"){return e}else{return e.replace(/\/$/,"")}}function parse(e){let A=e.method.toUpperCase();let t=(e.url||"/").replace(/:([a-z]\w+)/g,"{$1}");let r=Object.assign({},e.headers);let s;let o=omit(e,["method","baseUrl","url","headers","request","mediaType"]);const n=extractUrlVariableNames(t);t=parseUrl(t).expand(o);if(!/^http/.test(t)){t=e.baseUrl+t}const i=Object.keys(e).filter((e=>n.includes(e))).concat("baseUrl");const a=omit(o,i);const c=/application\/octet-stream/i.test(r.accept);if(!c){if(e.mediaType.format){r.accept=r.accept.split(/,/).map((A=>A.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/,`application/vnd$1$2.${e.mediaType.format}`))).join(",")}if(t.endsWith("/graphql")){if(e.mediaType.previews?.length){const A=r.accept.match(/[\w-]+(?=-preview)/g)||[];r.accept=A.concat(e.mediaType.previews).map((A=>{const t=e.mediaType.format?`.${e.mediaType.format}`:"+json";return`application/vnd.github.${A}-preview${t}`})).join(",")}}}if(["GET","HEAD"].includes(A)){t=addQueryParameters(t,a)}else{if("data"in a){s=a.data}else{if(Object.keys(a).length){s=a}}}if(!r["content-type"]&&typeof s!=="undefined"){r["content-type"]="application/json; charset=utf-8"}if(["PATCH","PUT"].includes(A)&&typeof s==="undefined"){s=""}return Object.assign({method:A,url:t,headers:r},typeof s!=="undefined"?{body:s}:null,e.request?{request:e.request}:null)}function endpointWithDefaults(e,A,t){return parse(merge(e,A,t))}function withDefaults(e,A){const t=merge(e,A);const r=endpointWithDefaults.bind(null,t);return Object.assign(r,{DEFAULTS:t,defaults:withDefaults.bind(null,t),merge:merge.bind(null,t),parse:parse})}var u=withDefaults(null,E);0&&0},6536:(e,A,t)=>{"use strict";var r=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var o=Object.getOwnPropertyNames;var n=Object.prototype.hasOwnProperty;var __export=(e,A)=>{for(var t in A)r(e,t,{get:A[t],enumerable:true})};var __copyProps=(e,A,t,i)=>{if(A&&typeof A==="object"||typeof A==="function"){for(let a of o(A))if(!n.call(e,a)&&a!==t)r(e,a,{get:()=>A[a],enumerable:!(i=s(A,a))||i.enumerable})}return e};var __toCommonJS=e=>__copyProps(r({},"__esModule",{value:true}),e);var i={};__export(i,{GraphqlResponseError:()=>u,graphql:()=>B,withCustomRequest:()=>withCustomRequest});e.exports=__toCommonJS(i);var a=t(8662);var c=t(9653);var g="7.0.2";var E=t(8662);var l=t(8662);function _buildMessageForResponseErrors(e){return`Request failed due to following response errors:\n`+e.errors.map((e=>` - ${e.message}`)).join("\n")}var u=class extends Error{constructor(e,A,t){super(_buildMessageForResponseErrors(t));this.request=e;this.headers=A;this.response=t;this.name="GraphqlResponseError";this.errors=t.errors;this.data=t.data;if(Error.captureStackTrace){Error.captureStackTrace(this,this.constructor)}}};var Q=["method","baseUrl","url","headers","request","query","mediaType"];var C=["query","method","url"];var h=/\/api\/v3\/?$/;function graphql(e,A,t){if(t){if(typeof A==="string"&&"query"in t){return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`))}for(const e in t){if(!C.includes(e))continue;return Promise.reject(new Error(`[@octokit/graphql] "${e}" cannot be used as variable name`))}}const r=typeof A==="string"?Object.assign({query:A},t):A;const s=Object.keys(r).reduce(((e,A)=>{if(Q.includes(A)){e[A]=r[A];return e}if(!e.variables){e.variables={}}e.variables[A]=r[A];return e}),{});const o=r.baseUrl||e.endpoint.DEFAULTS.baseUrl;if(h.test(o)){s.url=o.replace(h,"/api/graphql")}return e(s).then((e=>{if(e.data.errors){const A={};for(const t of Object.keys(e.headers)){A[t]=e.headers[t]}throw new u(s,A,e.data)}return e.data.data}))}function withDefaults(e,A){const t=e.defaults(A);const newApi=(e,A)=>graphql(t,e,A);return Object.assign(newApi,{defaults:withDefaults.bind(null,t),endpoint:t.endpoint})}var B=withDefaults(a.request,{headers:{"user-agent":`octokit-graphql.js/${g} ${(0,c.getUserAgent)()}`},method:"POST",url:"/graphql"});function withCustomRequest(e){return withDefaults(e,{method:"POST",url:"/graphql"})}0&&0},3895:e=>{"use strict";var A=Object.defineProperty;var t=Object.getOwnPropertyDescriptor;var r=Object.getOwnPropertyNames;var s=Object.prototype.hasOwnProperty;var __export=(e,t)=>{for(var r in t)A(e,r,{get:t[r],enumerable:true})};var __copyProps=(e,o,n,i)=>{if(o&&typeof o==="object"||typeof o==="function"){for(let a of r(o))if(!s.call(e,a)&&a!==n)A(e,a,{get:()=>o[a],enumerable:!(i=t(o,a))||i.enumerable})}return e};var __toCommonJS=e=>__copyProps(A({},"__esModule",{value:true}),e);var o={};__export(o,{composePaginateRest:()=>i,isPaginatingEndpoint:()=>isPaginatingEndpoint,paginateRest:()=>paginateRest,paginatingEndpoints:()=>a});e.exports=__toCommonJS(o);var n="9.1.5";function normalizePaginatedListResponse(e){if(!e.data){return{...e,data:[]}}const A="total_count"in e.data&&!("url"in e.data);if(!A)return e;const t=e.data.incomplete_results;const r=e.data.repository_selection;const s=e.data.total_count;delete e.data.incomplete_results;delete e.data.repository_selection;delete e.data.total_count;const o=Object.keys(e.data)[0];const n=e.data[o];e.data=n;if(typeof t!=="undefined"){e.data.incomplete_results=t}if(typeof r!=="undefined"){e.data.repository_selection=r}e.data.total_count=s;return e}function iterator(e,A,t){const r=typeof A==="function"?A.endpoint(t):e.request.endpoint(A,t);const s=typeof A==="function"?A:e.request;const o=r.method;const n=r.headers;let i=r.url;return{[Symbol.asyncIterator]:()=>({async next(){if(!i)return{done:true};try{const e=await s({method:o,url:i,headers:n});const A=normalizePaginatedListResponse(e);i=((A.headers.link||"").match(/<([^>]+)>;\s*rel="next"/)||[])[1];return{value:A}}catch(e){if(e.status!==409)throw e;i="";return{value:{status:200,headers:{},data:[]}}}}})}}function paginate(e,A,t,r){if(typeof t==="function"){r=t;t=void 0}return gather(e,[],iterator(e,A,t)[Symbol.asyncIterator](),r)}function gather(e,A,t,r){return t.next().then((s=>{if(s.done){return A}let o=false;function done(){o=true}A=A.concat(r?r(s.value,done):s.value.data);if(o){return A}return gather(e,A,t,r)}))}var i=Object.assign(paginate,{iterator:iterator});var a=["GET /advisories","GET /app/hook/deliveries","GET /app/installation-requests","GET /app/installations","GET /assignments/{assignment_id}/accepted_assignments","GET /classrooms","GET /classrooms/{classroom_id}/assignments","GET /enterprises/{enterprise}/dependabot/alerts","GET /enterprises/{enterprise}/secret-scanning/alerts","GET /events","GET /gists","GET /gists/public","GET /gists/starred","GET /gists/{gist_id}/comments","GET /gists/{gist_id}/commits","GET /gists/{gist_id}/forks","GET /installation/repositories","GET /issues","GET /licenses","GET /marketplace_listing/plans","GET /marketplace_listing/plans/{plan_id}/accounts","GET /marketplace_listing/stubbed/plans","GET /marketplace_listing/stubbed/plans/{plan_id}/accounts","GET /networks/{owner}/{repo}/events","GET /notifications","GET /organizations","GET /orgs/{org}/actions/cache/usage-by-repository","GET /orgs/{org}/actions/permissions/repositories","GET /orgs/{org}/actions/runners","GET /orgs/{org}/actions/secrets","GET /orgs/{org}/actions/secrets/{secret_name}/repositories","GET /orgs/{org}/actions/variables","GET /orgs/{org}/actions/variables/{name}/repositories","GET /orgs/{org}/blocks","GET /orgs/{org}/code-scanning/alerts","GET /orgs/{org}/codespaces","GET /orgs/{org}/codespaces/secrets","GET /orgs/{org}/codespaces/secrets/{secret_name}/repositories","GET /orgs/{org}/copilot/billing/seats","GET /orgs/{org}/dependabot/alerts","GET /orgs/{org}/dependabot/secrets","GET /orgs/{org}/dependabot/secrets/{secret_name}/repositories","GET /orgs/{org}/events","GET /orgs/{org}/failed_invitations","GET /orgs/{org}/hooks","GET /orgs/{org}/hooks/{hook_id}/deliveries","GET /orgs/{org}/installations","GET /orgs/{org}/invitations","GET /orgs/{org}/invitations/{invitation_id}/teams","GET /orgs/{org}/issues","GET /orgs/{org}/members","GET /orgs/{org}/members/{username}/codespaces","GET /orgs/{org}/migrations","GET /orgs/{org}/migrations/{migration_id}/repositories","GET /orgs/{org}/outside_collaborators","GET /orgs/{org}/packages","GET /orgs/{org}/packages/{package_type}/{package_name}/versions","GET /orgs/{org}/personal-access-token-requests","GET /orgs/{org}/personal-access-token-requests/{pat_request_id}/repositories","GET /orgs/{org}/personal-access-tokens","GET /orgs/{org}/personal-access-tokens/{pat_id}/repositories","GET /orgs/{org}/projects","GET /orgs/{org}/properties/values","GET /orgs/{org}/public_members","GET /orgs/{org}/repos","GET /orgs/{org}/rulesets","GET /orgs/{org}/rulesets/rule-suites","GET /orgs/{org}/secret-scanning/alerts","GET /orgs/{org}/security-advisories","GET /orgs/{org}/teams","GET /orgs/{org}/teams/{team_slug}/discussions","GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments","GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions","GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions","GET /orgs/{org}/teams/{team_slug}/invitations","GET /orgs/{org}/teams/{team_slug}/members","GET /orgs/{org}/teams/{team_slug}/projects","GET /orgs/{org}/teams/{team_slug}/repos","GET /orgs/{org}/teams/{team_slug}/teams","GET /projects/columns/{column_id}/cards","GET /projects/{project_id}/collaborators","GET /projects/{project_id}/columns","GET /repos/{owner}/{repo}/actions/artifacts","GET /repos/{owner}/{repo}/actions/caches","GET /repos/{owner}/{repo}/actions/organization-secrets","GET /repos/{owner}/{repo}/actions/organization-variables","GET /repos/{owner}/{repo}/actions/runners","GET /repos/{owner}/{repo}/actions/runs","GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts","GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs","GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs","GET /repos/{owner}/{repo}/actions/secrets","GET /repos/{owner}/{repo}/actions/variables","GET /repos/{owner}/{repo}/actions/workflows","GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs","GET /repos/{owner}/{repo}/activity","GET /repos/{owner}/{repo}/assignees","GET /repos/{owner}/{repo}/branches","GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations","GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs","GET /repos/{owner}/{repo}/code-scanning/alerts","GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances","GET /repos/{owner}/{repo}/code-scanning/analyses","GET /repos/{owner}/{repo}/codespaces","GET /repos/{owner}/{repo}/codespaces/devcontainers","GET /repos/{owner}/{repo}/codespaces/secrets","GET /repos/{owner}/{repo}/collaborators","GET /repos/{owner}/{repo}/comments","GET /repos/{owner}/{repo}/comments/{comment_id}/reactions","GET /repos/{owner}/{repo}/commits","GET /repos/{owner}/{repo}/commits/{commit_sha}/comments","GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls","GET /repos/{owner}/{repo}/commits/{ref}/check-runs","GET /repos/{owner}/{repo}/commits/{ref}/check-suites","GET /repos/{owner}/{repo}/commits/{ref}/status","GET /repos/{owner}/{repo}/commits/{ref}/statuses","GET /repos/{owner}/{repo}/contributors","GET /repos/{owner}/{repo}/dependabot/alerts","GET /repos/{owner}/{repo}/dependabot/secrets","GET /repos/{owner}/{repo}/deployments","GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses","GET /repos/{owner}/{repo}/environments","GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies","GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps","GET /repos/{owner}/{repo}/events","GET /repos/{owner}/{repo}/forks","GET /repos/{owner}/{repo}/hooks","GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries","GET /repos/{owner}/{repo}/invitations","GET /repos/{owner}/{repo}/issues","GET /repos/{owner}/{repo}/issues/comments","GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions","GET /repos/{owner}/{repo}/issues/events","GET /repos/{owner}/{repo}/issues/{issue_number}/comments","GET /repos/{owner}/{repo}/issues/{issue_number}/events","GET /repos/{owner}/{repo}/issues/{issue_number}/labels","GET /repos/{owner}/{repo}/issues/{issue_number}/reactions","GET /repos/{owner}/{repo}/issues/{issue_number}/timeline","GET /repos/{owner}/{repo}/keys","GET /repos/{owner}/{repo}/labels","GET /repos/{owner}/{repo}/milestones","GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels","GET /repos/{owner}/{repo}/notifications","GET /repos/{owner}/{repo}/pages/builds","GET /repos/{owner}/{repo}/projects","GET /repos/{owner}/{repo}/pulls","GET /repos/{owner}/{repo}/pulls/comments","GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions","GET /repos/{owner}/{repo}/pulls/{pull_number}/comments","GET /repos/{owner}/{repo}/pulls/{pull_number}/commits","GET /repos/{owner}/{repo}/pulls/{pull_number}/files","GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews","GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments","GET /repos/{owner}/{repo}/releases","GET /repos/{owner}/{repo}/releases/{release_id}/assets","GET /repos/{owner}/{repo}/releases/{release_id}/reactions","GET /repos/{owner}/{repo}/rules/branches/{branch}","GET /repos/{owner}/{repo}/rulesets","GET /repos/{owner}/{repo}/rulesets/rule-suites","GET /repos/{owner}/{repo}/secret-scanning/alerts","GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations","GET /repos/{owner}/{repo}/security-advisories","GET /repos/{owner}/{repo}/stargazers","GET /repos/{owner}/{repo}/subscribers","GET /repos/{owner}/{repo}/tags","GET /repos/{owner}/{repo}/teams","GET /repos/{owner}/{repo}/topics","GET /repositories","GET /repositories/{repository_id}/environments/{environment_name}/secrets","GET /repositories/{repository_id}/environments/{environment_name}/variables","GET /search/code","GET /search/commits","GET /search/issues","GET /search/labels","GET /search/repositories","GET /search/topics","GET /search/users","GET /teams/{team_id}/discussions","GET /teams/{team_id}/discussions/{discussion_number}/comments","GET /teams/{team_id}/discussions/{discussion_number}/comments/{comment_number}/reactions","GET /teams/{team_id}/discussions/{discussion_number}/reactions","GET /teams/{team_id}/invitations","GET /teams/{team_id}/members","GET /teams/{team_id}/projects","GET /teams/{team_id}/repos","GET /teams/{team_id}/teams","GET /user/blocks","GET /user/codespaces","GET /user/codespaces/secrets","GET /user/emails","GET /user/followers","GET /user/following","GET /user/gpg_keys","GET /user/installations","GET /user/installations/{installation_id}/repositories","GET /user/issues","GET /user/keys","GET /user/marketplace_purchases","GET /user/marketplace_purchases/stubbed","GET /user/memberships/orgs","GET /user/migrations","GET /user/migrations/{migration_id}/repositories","GET /user/orgs","GET /user/packages","GET /user/packages/{package_type}/{package_name}/versions","GET /user/public_emails","GET /user/repos","GET /user/repository_invitations","GET /user/social_accounts","GET /user/ssh_signing_keys","GET /user/starred","GET /user/subscriptions","GET /user/teams","GET /users","GET /users/{username}/events","GET /users/{username}/events/orgs/{org}","GET /users/{username}/events/public","GET /users/{username}/followers","GET /users/{username}/following","GET /users/{username}/gists","GET /users/{username}/gpg_keys","GET /users/{username}/keys","GET /users/{username}/orgs","GET /users/{username}/packages","GET /users/{username}/projects","GET /users/{username}/received_events","GET /users/{username}/received_events/public","GET /users/{username}/repos","GET /users/{username}/social_accounts","GET /users/{username}/ssh_signing_keys","GET /users/{username}/starred","GET /users/{username}/subscriptions"];function isPaginatingEndpoint(e){if(typeof e==="string"){return a.includes(e)}else{return false}}function paginateRest(e){return{paginate:Object.assign(paginate.bind(null,e),{iterator:iterator.bind(null,e)})}}paginateRest.VERSION=n;0&&0},9389:e=>{"use strict";var A=Object.defineProperty;var t=Object.getOwnPropertyDescriptor;var r=Object.getOwnPropertyNames;var s=Object.prototype.hasOwnProperty;var __export=(e,t)=>{for(var r in t)A(e,r,{get:t[r],enumerable:true})};var __copyProps=(e,o,n,i)=>{if(o&&typeof o==="object"||typeof o==="function"){for(let a of r(o))if(!s.call(e,a)&&a!==n)A(e,a,{get:()=>o[a],enumerable:!(i=t(o,a))||i.enumerable})}return e};var __toCommonJS=e=>__copyProps(A({},"__esModule",{value:true}),e);var o={};__export(o,{legacyRestEndpointMethods:()=>legacyRestEndpointMethods,restEndpointMethods:()=>restEndpointMethods});e.exports=__toCommonJS(o);var n="10.2.0";var i={actions:{addCustomLabelsToSelfHostedRunnerForOrg:["POST /orgs/{org}/actions/runners/{runner_id}/labels"],addCustomLabelsToSelfHostedRunnerForRepo:["POST /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],approveWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/approve"],cancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel"],createEnvironmentVariable:["POST /repositories/{repository_id}/environments/{environment_name}/variables"],createOrUpdateEnvironmentSecret:["PUT /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}"],createOrgVariable:["POST /orgs/{org}/actions/variables"],createRegistrationTokenForOrg:["POST /orgs/{org}/actions/runners/registration-token"],createRegistrationTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/registration-token"],createRemoveTokenForOrg:["POST /orgs/{org}/actions/runners/remove-token"],createRemoveTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/remove-token"],createRepoVariable:["POST /repos/{owner}/{repo}/actions/variables"],createWorkflowDispatch:["POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches"],deleteActionsCacheById:["DELETE /repos/{owner}/{repo}/actions/caches/{cache_id}"],deleteActionsCacheByKey:["DELETE /repos/{owner}/{repo}/actions/caches{?key,ref}"],deleteArtifact:["DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],deleteEnvironmentSecret:["DELETE /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}"],deleteEnvironmentVariable:["DELETE /repositories/{repository_id}/environments/{environment_name}/variables/{name}"],deleteOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}"],deleteOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}"],deleteRepoVariable:["DELETE /repos/{owner}/{repo}/actions/variables/{name}"],deleteSelfHostedRunnerFromOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}"],deleteSelfHostedRunnerFromRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}"],deleteWorkflowRun:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}"],deleteWorkflowRunLogs:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],disableSelectedRepositoryGithubActionsOrganization:["DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}"],disableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable"],downloadArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}"],downloadJobLogsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs"],downloadWorkflowRunAttemptLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/logs"],downloadWorkflowRunLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],enableSelectedRepositoryGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories/{repository_id}"],enableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable"],forceCancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/force-cancel"],generateRunnerJitconfigForOrg:["POST /orgs/{org}/actions/runners/generate-jitconfig"],generateRunnerJitconfigForRepo:["POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig"],getActionsCacheList:["GET /repos/{owner}/{repo}/actions/caches"],getActionsCacheUsage:["GET /repos/{owner}/{repo}/actions/cache/usage"],getActionsCacheUsageByRepoForOrg:["GET /orgs/{org}/actions/cache/usage-by-repository"],getActionsCacheUsageForOrg:["GET /orgs/{org}/actions/cache/usage"],getAllowedActionsOrganization:["GET /orgs/{org}/actions/permissions/selected-actions"],getAllowedActionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/selected-actions"],getArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],getEnvironmentPublicKey:["GET /repositories/{repository_id}/environments/{environment_name}/secrets/public-key"],getEnvironmentSecret:["GET /repositories/{repository_id}/environments/{environment_name}/secrets/{secret_name}"],getEnvironmentVariable:["GET /repositories/{repository_id}/environments/{environment_name}/variables/{name}"],getGithubActionsDefaultWorkflowPermissionsOrganization:["GET /orgs/{org}/actions/permissions/workflow"],getGithubActionsDefaultWorkflowPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/workflow"],getGithubActionsPermissionsOrganization:["GET /orgs/{org}/actions/permissions"],getGithubActionsPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions"],getJobForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}"],getOrgPublicKey:["GET /orgs/{org}/actions/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}"],getOrgVariable:["GET /orgs/{org}/actions/variables/{name}"],getPendingDeploymentsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],getRepoPermissions:["GET /repos/{owner}/{repo}/actions/permissions",{},{renamed:["actions","getGithubActionsPermissionsRepository"]}],getRepoPublicKey:["GET /repos/{owner}/{repo}/actions/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/actions/secrets/{secret_name}"],getRepoVariable:["GET /repos/{owner}/{repo}/actions/variables/{name}"],getReviewsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/approvals"],getSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}"],getSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}"],getWorkflow:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}"],getWorkflowAccessToRepository:["GET /repos/{owner}/{repo}/actions/permissions/access"],getWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}"],getWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}"],getWorkflowRunUsage:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/timing"],getWorkflowUsage:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/timing"],listArtifactsForRepo:["GET /repos/{owner}/{repo}/actions/artifacts"],listEnvironmentSecrets:["GET /repositories/{repository_id}/environments/{environment_name}/secrets"],listEnvironmentVariables:["GET /repositories/{repository_id}/environments/{environment_name}/variables"],listJobsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs"],listJobsForWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs"],listLabelsForSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}/labels"],listLabelsForSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],listOrgSecrets:["GET /orgs/{org}/actions/secrets"],listOrgVariables:["GET /orgs/{org}/actions/variables"],listRepoOrganizationSecrets:["GET /repos/{owner}/{repo}/actions/organization-secrets"],listRepoOrganizationVariables:["GET /repos/{owner}/{repo}/actions/organization-variables"],listRepoSecrets:["GET /repos/{owner}/{repo}/actions/secrets"],listRepoVariables:["GET /repos/{owner}/{repo}/actions/variables"],listRepoWorkflows:["GET /repos/{owner}/{repo}/actions/workflows"],listRunnerApplicationsForOrg:["GET /orgs/{org}/actions/runners/downloads"],listRunnerApplicationsForRepo:["GET /repos/{owner}/{repo}/actions/runners/downloads"],listSelectedReposForOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}/repositories"],listSelectedReposForOrgVariable:["GET /orgs/{org}/actions/variables/{name}/repositories"],listSelectedRepositoriesEnabledGithubActionsOrganization:["GET /orgs/{org}/actions/permissions/repositories"],listSelfHostedRunnersForOrg:["GET /orgs/{org}/actions/runners"],listSelfHostedRunnersForRepo:["GET /repos/{owner}/{repo}/actions/runners"],listWorkflowRunArtifacts:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts"],listWorkflowRuns:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs"],listWorkflowRunsForRepo:["GET /repos/{owner}/{repo}/actions/runs"],reRunJobForWorkflowRun:["POST /repos/{owner}/{repo}/actions/jobs/{job_id}/rerun"],reRunWorkflow:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun"],reRunWorkflowFailedJobs:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs"],removeAllCustomLabelsFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels"],removeAllCustomLabelsFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],removeCustomLabelFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels/{name}"],removeCustomLabelFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels/{name}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],reviewCustomGatesForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule"],reviewPendingDeploymentsForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],setAllowedActionsOrganization:["PUT /orgs/{org}/actions/permissions/selected-actions"],setAllowedActionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/selected-actions"],setCustomLabelsForSelfHostedRunnerForOrg:["PUT /orgs/{org}/actions/runners/{runner_id}/labels"],setCustomLabelsForSelfHostedRunnerForRepo:["PUT /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],setGithubActionsDefaultWorkflowPermissionsOrganization:["PUT /orgs/{org}/actions/permissions/workflow"],setGithubActionsDefaultWorkflowPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/workflow"],setGithubActionsPermissionsOrganization:["PUT /orgs/{org}/actions/permissions"],setGithubActionsPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories"],setSelectedReposForOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories"],setSelectedRepositoriesEnabledGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories"],setWorkflowAccessToRepository:["PUT /repos/{owner}/{repo}/actions/permissions/access"],updateEnvironmentVariable:["PATCH /repositories/{repository_id}/environments/{environment_name}/variables/{name}"],updateOrgVariable:["PATCH /orgs/{org}/actions/variables/{name}"],updateRepoVariable:["PATCH /repos/{owner}/{repo}/actions/variables/{name}"]},activity:{checkRepoIsStarredByAuthenticatedUser:["GET /user/starred/{owner}/{repo}"],deleteRepoSubscription:["DELETE /repos/{owner}/{repo}/subscription"],deleteThreadSubscription:["DELETE /notifications/threads/{thread_id}/subscription"],getFeeds:["GET /feeds"],getRepoSubscription:["GET /repos/{owner}/{repo}/subscription"],getThread:["GET /notifications/threads/{thread_id}"],getThreadSubscriptionForAuthenticatedUser:["GET /notifications/threads/{thread_id}/subscription"],listEventsForAuthenticatedUser:["GET /users/{username}/events"],listNotificationsForAuthenticatedUser:["GET /notifications"],listOrgEventsForAuthenticatedUser:["GET /users/{username}/events/orgs/{org}"],listPublicEvents:["GET /events"],listPublicEventsForRepoNetwork:["GET /networks/{owner}/{repo}/events"],listPublicEventsForUser:["GET /users/{username}/events/public"],listPublicOrgEvents:["GET /orgs/{org}/events"],listReceivedEventsForUser:["GET /users/{username}/received_events"],listReceivedPublicEventsForUser:["GET /users/{username}/received_events/public"],listRepoEvents:["GET /repos/{owner}/{repo}/events"],listRepoNotificationsForAuthenticatedUser:["GET /repos/{owner}/{repo}/notifications"],listReposStarredByAuthenticatedUser:["GET /user/starred"],listReposStarredByUser:["GET /users/{username}/starred"],listReposWatchedByUser:["GET /users/{username}/subscriptions"],listStargazersForRepo:["GET /repos/{owner}/{repo}/stargazers"],listWatchedReposForAuthenticatedUser:["GET /user/subscriptions"],listWatchersForRepo:["GET /repos/{owner}/{repo}/subscribers"],markNotificationsAsRead:["PUT /notifications"],markRepoNotificationsAsRead:["PUT /repos/{owner}/{repo}/notifications"],markThreadAsRead:["PATCH /notifications/threads/{thread_id}"],setRepoSubscription:["PUT /repos/{owner}/{repo}/subscription"],setThreadSubscription:["PUT /notifications/threads/{thread_id}/subscription"],starRepoForAuthenticatedUser:["PUT /user/starred/{owner}/{repo}"],unstarRepoForAuthenticatedUser:["DELETE /user/starred/{owner}/{repo}"]},apps:{addRepoToInstallation:["PUT /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","addRepoToInstallationForAuthenticatedUser"]}],addRepoToInstallationForAuthenticatedUser:["PUT /user/installations/{installation_id}/repositories/{repository_id}"],checkToken:["POST /applications/{client_id}/token"],createFromManifest:["POST /app-manifests/{code}/conversions"],createInstallationAccessToken:["POST /app/installations/{installation_id}/access_tokens"],deleteAuthorization:["DELETE /applications/{client_id}/grant"],deleteInstallation:["DELETE /app/installations/{installation_id}"],deleteToken:["DELETE /applications/{client_id}/token"],getAuthenticated:["GET /app"],getBySlug:["GET /apps/{app_slug}"],getInstallation:["GET /app/installations/{installation_id}"],getOrgInstallation:["GET /orgs/{org}/installation"],getRepoInstallation:["GET /repos/{owner}/{repo}/installation"],getSubscriptionPlanForAccount:["GET /marketplace_listing/accounts/{account_id}"],getSubscriptionPlanForAccountStubbed:["GET /marketplace_listing/stubbed/accounts/{account_id}"],getUserInstallation:["GET /users/{username}/installation"],getWebhookConfigForApp:["GET /app/hook/config"],getWebhookDelivery:["GET /app/hook/deliveries/{delivery_id}"],listAccountsForPlan:["GET /marketplace_listing/plans/{plan_id}/accounts"],listAccountsForPlanStubbed:["GET /marketplace_listing/stubbed/plans/{plan_id}/accounts"],listInstallationReposForAuthenticatedUser:["GET /user/installations/{installation_id}/repositories"],listInstallationRequestsForAuthenticatedApp:["GET /app/installation-requests"],listInstallations:["GET /app/installations"],listInstallationsForAuthenticatedUser:["GET /user/installations"],listPlans:["GET /marketplace_listing/plans"],listPlansStubbed:["GET /marketplace_listing/stubbed/plans"],listReposAccessibleToInstallation:["GET /installation/repositories"],listSubscriptionsForAuthenticatedUser:["GET /user/marketplace_purchases"],listSubscriptionsForAuthenticatedUserStubbed:["GET /user/marketplace_purchases/stubbed"],listWebhookDeliveries:["GET /app/hook/deliveries"],redeliverWebhookDelivery:["POST /app/hook/deliveries/{delivery_id}/attempts"],removeRepoFromInstallation:["DELETE /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","removeRepoFromInstallationForAuthenticatedUser"]}],removeRepoFromInstallationForAuthenticatedUser:["DELETE /user/installations/{installation_id}/repositories/{repository_id}"],resetToken:["PATCH /applications/{client_id}/token"],revokeInstallationAccessToken:["DELETE /installation/token"],scopeToken:["POST /applications/{client_id}/token/scoped"],suspendInstallation:["PUT /app/installations/{installation_id}/suspended"],unsuspendInstallation:["DELETE /app/installations/{installation_id}/suspended"],updateWebhookConfigForApp:["PATCH /app/hook/config"]},billing:{getGithubActionsBillingOrg:["GET /orgs/{org}/settings/billing/actions"],getGithubActionsBillingUser:["GET /users/{username}/settings/billing/actions"],getGithubPackagesBillingOrg:["GET /orgs/{org}/settings/billing/packages"],getGithubPackagesBillingUser:["GET /users/{username}/settings/billing/packages"],getSharedStorageBillingOrg:["GET /orgs/{org}/settings/billing/shared-storage"],getSharedStorageBillingUser:["GET /users/{username}/settings/billing/shared-storage"]},checks:{create:["POST /repos/{owner}/{repo}/check-runs"],createSuite:["POST /repos/{owner}/{repo}/check-suites"],get:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}"],getSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}"],listAnnotations:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations"],listForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-runs"],listForSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs"],listSuitesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-suites"],rerequestRun:["POST /repos/{owner}/{repo}/check-runs/{check_run_id}/rerequest"],rerequestSuite:["POST /repos/{owner}/{repo}/check-suites/{check_suite_id}/rerequest"],setSuitesPreferences:["PATCH /repos/{owner}/{repo}/check-suites/preferences"],update:["PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}"]},codeScanning:{deleteAnalysis:["DELETE /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}{?confirm_delete}"],getAlert:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}",{},{renamedParameters:{alert_id:"alert_number"}}],getAnalysis:["GET /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}"],getCodeqlDatabase:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases/{language}"],getDefaultSetup:["GET /repos/{owner}/{repo}/code-scanning/default-setup"],getSarif:["GET /repos/{owner}/{repo}/code-scanning/sarifs/{sarif_id}"],listAlertInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances"],listAlertsForOrg:["GET /orgs/{org}/code-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/code-scanning/alerts"],listAlertsInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances",{},{renamed:["codeScanning","listAlertInstances"]}],listCodeqlDatabases:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases"],listRecentAnalyses:["GET /repos/{owner}/{repo}/code-scanning/analyses"],updateAlert:["PATCH /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}"],updateDefaultSetup:["PATCH /repos/{owner}/{repo}/code-scanning/default-setup"],uploadSarif:["POST /repos/{owner}/{repo}/code-scanning/sarifs"]},codesOfConduct:{getAllCodesOfConduct:["GET /codes_of_conduct"],getConductCode:["GET /codes_of_conduct/{key}"]},codespaces:{addRepositoryForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],checkPermissionsForDevcontainer:["GET /repos/{owner}/{repo}/codespaces/permissions_check"],codespaceMachinesForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/machines"],createForAuthenticatedUser:["POST /user/codespaces"],createOrUpdateOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],createOrUpdateSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}"],createWithPrForAuthenticatedUser:["POST /repos/{owner}/{repo}/pulls/{pull_number}/codespaces"],createWithRepoForAuthenticatedUser:["POST /repos/{owner}/{repo}/codespaces"],deleteForAuthenticatedUser:["DELETE /user/codespaces/{codespace_name}"],deleteFromOrganization:["DELETE /orgs/{org}/members/{username}/codespaces/{codespace_name}"],deleteOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],deleteSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}"],exportForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/exports"],getCodespacesForUserInOrg:["GET /orgs/{org}/members/{username}/codespaces"],getExportDetailsForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/exports/{export_id}"],getForAuthenticatedUser:["GET /user/codespaces/{codespace_name}"],getOrgPublicKey:["GET /orgs/{org}/codespaces/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}"],getPublicKeyForAuthenticatedUser:["GET /user/codespaces/secrets/public-key"],getRepoPublicKey:["GET /repos/{owner}/{repo}/codespaces/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],getSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}"],listDevcontainersInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/devcontainers"],listForAuthenticatedUser:["GET /user/codespaces"],listInOrganization:["GET /orgs/{org}/codespaces",{},{renamedParameters:{org_id:"org"}}],listInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces"],listOrgSecrets:["GET /orgs/{org}/codespaces/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/codespaces/secrets"],listRepositoriesForSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}/repositories"],listSecretsForAuthenticatedUser:["GET /user/codespaces/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],preFlightWithRepoForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/new"],publishForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/publish"],removeRepositoryForSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],repoMachinesForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/machines"],setRepositoriesForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],startForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/start"],stopForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/stop"],stopInOrganization:["POST /orgs/{org}/members/{username}/codespaces/{codespace_name}/stop"],updateForAuthenticatedUser:["PATCH /user/codespaces/{codespace_name}"]},copilot:{addCopilotForBusinessSeatsForTeams:["POST /orgs/{org}/copilot/billing/selected_teams"],addCopilotForBusinessSeatsForUsers:["POST /orgs/{org}/copilot/billing/selected_users"],cancelCopilotSeatAssignmentForTeams:["DELETE /orgs/{org}/copilot/billing/selected_teams"],cancelCopilotSeatAssignmentForUsers:["DELETE /orgs/{org}/copilot/billing/selected_users"],getCopilotOrganizationDetails:["GET /orgs/{org}/copilot/billing"],getCopilotSeatDetailsForUser:["GET /orgs/{org}/members/{username}/copilot"],listCopilotSeats:["GET /orgs/{org}/copilot/billing/seats"]},dependabot:{addSelectedRepoToOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],deleteOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],getAlert:["GET /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"],getOrgPublicKey:["GET /orgs/{org}/dependabot/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}"],getRepoPublicKey:["GET /repos/{owner}/{repo}/dependabot/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],listAlertsForEnterprise:["GET /enterprises/{enterprise}/dependabot/alerts"],listAlertsForOrg:["GET /orgs/{org}/dependabot/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/dependabot/alerts"],listOrgSecrets:["GET /orgs/{org}/dependabot/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/dependabot/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],updateAlert:["PATCH /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"]},dependencyGraph:{createRepositorySnapshot:["POST /repos/{owner}/{repo}/dependency-graph/snapshots"],diffRange:["GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}"],exportSbom:["GET /repos/{owner}/{repo}/dependency-graph/sbom"]},emojis:{get:["GET /emojis"]},gists:{checkIsStarred:["GET /gists/{gist_id}/star"],create:["POST /gists"],createComment:["POST /gists/{gist_id}/comments"],delete:["DELETE /gists/{gist_id}"],deleteComment:["DELETE /gists/{gist_id}/comments/{comment_id}"],fork:["POST /gists/{gist_id}/forks"],get:["GET /gists/{gist_id}"],getComment:["GET /gists/{gist_id}/comments/{comment_id}"],getRevision:["GET /gists/{gist_id}/{sha}"],list:["GET /gists"],listComments:["GET /gists/{gist_id}/comments"],listCommits:["GET /gists/{gist_id}/commits"],listForUser:["GET /users/{username}/gists"],listForks:["GET /gists/{gist_id}/forks"],listPublic:["GET /gists/public"],listStarred:["GET /gists/starred"],star:["PUT /gists/{gist_id}/star"],unstar:["DELETE /gists/{gist_id}/star"],update:["PATCH /gists/{gist_id}"],updateComment:["PATCH /gists/{gist_id}/comments/{comment_id}"]},git:{createBlob:["POST /repos/{owner}/{repo}/git/blobs"],createCommit:["POST /repos/{owner}/{repo}/git/commits"],createRef:["POST /repos/{owner}/{repo}/git/refs"],createTag:["POST /repos/{owner}/{repo}/git/tags"],createTree:["POST /repos/{owner}/{repo}/git/trees"],deleteRef:["DELETE /repos/{owner}/{repo}/git/refs/{ref}"],getBlob:["GET /repos/{owner}/{repo}/git/blobs/{file_sha}"],getCommit:["GET /repos/{owner}/{repo}/git/commits/{commit_sha}"],getRef:["GET /repos/{owner}/{repo}/git/ref/{ref}"],getTag:["GET /repos/{owner}/{repo}/git/tags/{tag_sha}"],getTree:["GET /repos/{owner}/{repo}/git/trees/{tree_sha}"],listMatchingRefs:["GET /repos/{owner}/{repo}/git/matching-refs/{ref}"],updateRef:["PATCH /repos/{owner}/{repo}/git/refs/{ref}"]},gitignore:{getAllTemplates:["GET /gitignore/templates"],getTemplate:["GET /gitignore/templates/{name}"]},interactions:{getRestrictionsForAuthenticatedUser:["GET /user/interaction-limits"],getRestrictionsForOrg:["GET /orgs/{org}/interaction-limits"],getRestrictionsForRepo:["GET /repos/{owner}/{repo}/interaction-limits"],getRestrictionsForYourPublicRepos:["GET /user/interaction-limits",{},{renamed:["interactions","getRestrictionsForAuthenticatedUser"]}],removeRestrictionsForAuthenticatedUser:["DELETE /user/interaction-limits"],removeRestrictionsForOrg:["DELETE /orgs/{org}/interaction-limits"],removeRestrictionsForRepo:["DELETE /repos/{owner}/{repo}/interaction-limits"],removeRestrictionsForYourPublicRepos:["DELETE /user/interaction-limits",{},{renamed:["interactions","removeRestrictionsForAuthenticatedUser"]}],setRestrictionsForAuthenticatedUser:["PUT /user/interaction-limits"],setRestrictionsForOrg:["PUT /orgs/{org}/interaction-limits"],setRestrictionsForRepo:["PUT /repos/{owner}/{repo}/interaction-limits"],setRestrictionsForYourPublicRepos:["PUT /user/interaction-limits",{},{renamed:["interactions","setRestrictionsForAuthenticatedUser"]}]},issues:{addAssignees:["POST /repos/{owner}/{repo}/issues/{issue_number}/assignees"],addLabels:["POST /repos/{owner}/{repo}/issues/{issue_number}/labels"],checkUserCanBeAssigned:["GET /repos/{owner}/{repo}/assignees/{assignee}"],checkUserCanBeAssignedToIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/assignees/{assignee}"],create:["POST /repos/{owner}/{repo}/issues"],createComment:["POST /repos/{owner}/{repo}/issues/{issue_number}/comments"],createLabel:["POST /repos/{owner}/{repo}/labels"],createMilestone:["POST /repos/{owner}/{repo}/milestones"],deleteComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}"],deleteLabel:["DELETE /repos/{owner}/{repo}/labels/{name}"],deleteMilestone:["DELETE /repos/{owner}/{repo}/milestones/{milestone_number}"],get:["GET /repos/{owner}/{repo}/issues/{issue_number}"],getComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}"],getEvent:["GET /repos/{owner}/{repo}/issues/events/{event_id}"],getLabel:["GET /repos/{owner}/{repo}/labels/{name}"],getMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}"],list:["GET /issues"],listAssignees:["GET /repos/{owner}/{repo}/assignees"],listComments:["GET /repos/{owner}/{repo}/issues/{issue_number}/comments"],listCommentsForRepo:["GET /repos/{owner}/{repo}/issues/comments"],listEvents:["GET /repos/{owner}/{repo}/issues/{issue_number}/events"],listEventsForRepo:["GET /repos/{owner}/{repo}/issues/events"],listEventsForTimeline:["GET /repos/{owner}/{repo}/issues/{issue_number}/timeline"],listForAuthenticatedUser:["GET /user/issues"],listForOrg:["GET /orgs/{org}/issues"],listForRepo:["GET /repos/{owner}/{repo}/issues"],listLabelsForMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels"],listLabelsForRepo:["GET /repos/{owner}/{repo}/labels"],listLabelsOnIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/labels"],listMilestones:["GET /repos/{owner}/{repo}/milestones"],lock:["PUT /repos/{owner}/{repo}/issues/{issue_number}/lock"],removeAllLabels:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels"],removeAssignees:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/assignees"],removeLabel:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}"],setLabels:["PUT /repos/{owner}/{repo}/issues/{issue_number}/labels"],unlock:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/lock"],update:["PATCH /repos/{owner}/{repo}/issues/{issue_number}"],updateComment:["PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}"],updateLabel:["PATCH /repos/{owner}/{repo}/labels/{name}"],updateMilestone:["PATCH /repos/{owner}/{repo}/milestones/{milestone_number}"]},licenses:{get:["GET /licenses/{license}"],getAllCommonlyUsed:["GET /licenses"],getForRepo:["GET /repos/{owner}/{repo}/license"]},markdown:{render:["POST /markdown"],renderRaw:["POST /markdown/raw",{headers:{"content-type":"text/plain; charset=utf-8"}}]},meta:{get:["GET /meta"],getAllVersions:["GET /versions"],getOctocat:["GET /octocat"],getZen:["GET /zen"],root:["GET /"]},migrations:{cancelImport:["DELETE /repos/{owner}/{repo}/import",{},{deprecated:"octokit.rest.migrations.cancelImport() is deprecated, see https://docs.github.com/rest/migrations/source-imports#cancel-an-import"}],deleteArchiveForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/archive"],deleteArchiveForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/archive"],downloadArchiveForOrg:["GET /orgs/{org}/migrations/{migration_id}/archive"],getArchiveForAuthenticatedUser:["GET /user/migrations/{migration_id}/archive"],getCommitAuthors:["GET /repos/{owner}/{repo}/import/authors",{},{deprecated:"octokit.rest.migrations.getCommitAuthors() is deprecated, see https://docs.github.com/rest/migrations/source-imports#get-commit-authors"}],getImportStatus:["GET /repos/{owner}/{repo}/import",{},{deprecated:"octokit.rest.migrations.getImportStatus() is deprecated, see https://docs.github.com/rest/migrations/source-imports#get-an-import-status"}],getLargeFiles:["GET /repos/{owner}/{repo}/import/large_files",{},{deprecated:"octokit.rest.migrations.getLargeFiles() is deprecated, see https://docs.github.com/rest/migrations/source-imports#get-large-files"}],getStatusForAuthenticatedUser:["GET /user/migrations/{migration_id}"],getStatusForOrg:["GET /orgs/{org}/migrations/{migration_id}"],listForAuthenticatedUser:["GET /user/migrations"],listForOrg:["GET /orgs/{org}/migrations"],listReposForAuthenticatedUser:["GET /user/migrations/{migration_id}/repositories"],listReposForOrg:["GET /orgs/{org}/migrations/{migration_id}/repositories"],listReposForUser:["GET /user/migrations/{migration_id}/repositories",{},{renamed:["migrations","listReposForAuthenticatedUser"]}],mapCommitAuthor:["PATCH /repos/{owner}/{repo}/import/authors/{author_id}",{},{deprecated:"octokit.rest.migrations.mapCommitAuthor() is deprecated, see https://docs.github.com/rest/migrations/source-imports#map-a-commit-author"}],setLfsPreference:["PATCH /repos/{owner}/{repo}/import/lfs",{},{deprecated:"octokit.rest.migrations.setLfsPreference() is deprecated, see https://docs.github.com/rest/migrations/source-imports#update-git-lfs-preference"}],startForAuthenticatedUser:["POST /user/migrations"],startForOrg:["POST /orgs/{org}/migrations"],startImport:["PUT /repos/{owner}/{repo}/import",{},{deprecated:"octokit.rest.migrations.startImport() is deprecated, see https://docs.github.com/rest/migrations/source-imports#start-an-import"}],unlockRepoForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/repos/{repo_name}/lock"],unlockRepoForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/repos/{repo_name}/lock"],updateImport:["PATCH /repos/{owner}/{repo}/import",{},{deprecated:"octokit.rest.migrations.updateImport() is deprecated, see https://docs.github.com/rest/migrations/source-imports#update-an-import"}]},orgs:{addSecurityManagerTeam:["PUT /orgs/{org}/security-managers/teams/{team_slug}"],blockUser:["PUT /orgs/{org}/blocks/{username}"],cancelInvitation:["DELETE /orgs/{org}/invitations/{invitation_id}"],checkBlockedUser:["GET /orgs/{org}/blocks/{username}"],checkMembershipForUser:["GET /orgs/{org}/members/{username}"],checkPublicMembershipForUser:["GET /orgs/{org}/public_members/{username}"],convertMemberToOutsideCollaborator:["PUT /orgs/{org}/outside_collaborators/{username}"],createInvitation:["POST /orgs/{org}/invitations"],createOrUpdateCustomProperties:["PATCH /orgs/{org}/properties/schema"],createOrUpdateCustomPropertiesValuesForRepos:["PATCH /orgs/{org}/properties/values"],createOrUpdateCustomProperty:["PUT /orgs/{org}/properties/schema/{custom_property_name}"],createWebhook:["POST /orgs/{org}/hooks"],delete:["DELETE /orgs/{org}"],deleteWebhook:["DELETE /orgs/{org}/hooks/{hook_id}"],enableOrDisableSecurityProductOnAllOrgRepos:["POST /orgs/{org}/{security_product}/{enablement}"],get:["GET /orgs/{org}"],getAllCustomProperties:["GET /orgs/{org}/properties/schema"],getCustomProperty:["GET /orgs/{org}/properties/schema/{custom_property_name}"],getMembershipForAuthenticatedUser:["GET /user/memberships/orgs/{org}"],getMembershipForUser:["GET /orgs/{org}/memberships/{username}"],getWebhook:["GET /orgs/{org}/hooks/{hook_id}"],getWebhookConfigForOrg:["GET /orgs/{org}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}"],list:["GET /organizations"],listAppInstallations:["GET /orgs/{org}/installations"],listBlockedUsers:["GET /orgs/{org}/blocks"],listCustomPropertiesValuesForRepos:["GET /orgs/{org}/properties/values"],listFailedInvitations:["GET /orgs/{org}/failed_invitations"],listForAuthenticatedUser:["GET /user/orgs"],listForUser:["GET /users/{username}/orgs"],listInvitationTeams:["GET /orgs/{org}/invitations/{invitation_id}/teams"],listMembers:["GET /orgs/{org}/members"],listMembershipsForAuthenticatedUser:["GET /user/memberships/orgs"],listOutsideCollaborators:["GET /orgs/{org}/outside_collaborators"],listPatGrantRepositories:["GET /orgs/{org}/personal-access-tokens/{pat_id}/repositories"],listPatGrantRequestRepositories:["GET /orgs/{org}/personal-access-token-requests/{pat_request_id}/repositories"],listPatGrantRequests:["GET /orgs/{org}/personal-access-token-requests"],listPatGrants:["GET /orgs/{org}/personal-access-tokens"],listPendingInvitations:["GET /orgs/{org}/invitations"],listPublicMembers:["GET /orgs/{org}/public_members"],listSecurityManagerTeams:["GET /orgs/{org}/security-managers"],listWebhookDeliveries:["GET /orgs/{org}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /orgs/{org}/hooks"],pingWebhook:["POST /orgs/{org}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeCustomProperty:["DELETE /orgs/{org}/properties/schema/{custom_property_name}"],removeMember:["DELETE /orgs/{org}/members/{username}"],removeMembershipForUser:["DELETE /orgs/{org}/memberships/{username}"],removeOutsideCollaborator:["DELETE /orgs/{org}/outside_collaborators/{username}"],removePublicMembershipForAuthenticatedUser:["DELETE /orgs/{org}/public_members/{username}"],removeSecurityManagerTeam:["DELETE /orgs/{org}/security-managers/teams/{team_slug}"],reviewPatGrantRequest:["POST /orgs/{org}/personal-access-token-requests/{pat_request_id}"],reviewPatGrantRequestsInBulk:["POST /orgs/{org}/personal-access-token-requests"],setMembershipForUser:["PUT /orgs/{org}/memberships/{username}"],setPublicMembershipForAuthenticatedUser:["PUT /orgs/{org}/public_members/{username}"],unblockUser:["DELETE /orgs/{org}/blocks/{username}"],update:["PATCH /orgs/{org}"],updateMembershipForAuthenticatedUser:["PATCH /user/memberships/orgs/{org}"],updatePatAccess:["POST /orgs/{org}/personal-access-tokens/{pat_id}"],updatePatAccesses:["POST /orgs/{org}/personal-access-tokens"],updateWebhook:["PATCH /orgs/{org}/hooks/{hook_id}"],updateWebhookConfigForOrg:["PATCH /orgs/{org}/hooks/{hook_id}/config"]},packages:{deletePackageForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}"],deletePackageForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}"],deletePackageForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}"],deletePackageVersionForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getAllPackageVersionsForAPackageOwnedByAnOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByOrg"]}],getAllPackageVersionsForAPackageOwnedByTheAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByAuthenticatedUser"]}],getAllPackageVersionsForPackageOwnedByAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions"],getPackageForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}"],getPackageForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}"],getPackageForUser:["GET /users/{username}/packages/{package_type}/{package_name}"],getPackageVersionForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],listDockerMigrationConflictingPackagesForAuthenticatedUser:["GET /user/docker/conflicts"],listDockerMigrationConflictingPackagesForOrganization:["GET /orgs/{org}/docker/conflicts"],listDockerMigrationConflictingPackagesForUser:["GET /users/{username}/docker/conflicts"],listPackagesForAuthenticatedUser:["GET /user/packages"],listPackagesForOrganization:["GET /orgs/{org}/packages"],listPackagesForUser:["GET /users/{username}/packages"],restorePackageForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForUser:["POST /users/{username}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageVersionForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForUser:["POST /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"]},projects:{addCollaborator:["PUT /projects/{project_id}/collaborators/{username}"],createCard:["POST /projects/columns/{column_id}/cards"],createColumn:["POST /projects/{project_id}/columns"],createForAuthenticatedUser:["POST /user/projects"],createForOrg:["POST /orgs/{org}/projects"],createForRepo:["POST /repos/{owner}/{repo}/projects"],delete:["DELETE /projects/{project_id}"],deleteCard:["DELETE /projects/columns/cards/{card_id}"],deleteColumn:["DELETE /projects/columns/{column_id}"],get:["GET /projects/{project_id}"],getCard:["GET /projects/columns/cards/{card_id}"],getColumn:["GET /projects/columns/{column_id}"],getPermissionForUser:["GET /projects/{project_id}/collaborators/{username}/permission"],listCards:["GET /projects/columns/{column_id}/cards"],listCollaborators:["GET /projects/{project_id}/collaborators"],listColumns:["GET /projects/{project_id}/columns"],listForOrg:["GET /orgs/{org}/projects"],listForRepo:["GET /repos/{owner}/{repo}/projects"],listForUser:["GET /users/{username}/projects"],moveCard:["POST /projects/columns/cards/{card_id}/moves"],moveColumn:["POST /projects/columns/{column_id}/moves"],removeCollaborator:["DELETE /projects/{project_id}/collaborators/{username}"],update:["PATCH /projects/{project_id}"],updateCard:["PATCH /projects/columns/cards/{card_id}"],updateColumn:["PATCH /projects/columns/{column_id}"]},pulls:{checkIfMerged:["GET /repos/{owner}/{repo}/pulls/{pull_number}/merge"],create:["POST /repos/{owner}/{repo}/pulls"],createReplyForReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies"],createReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],createReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments"],deletePendingReview:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],deleteReviewComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}"],dismissReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/dismissals"],get:["GET /repos/{owner}/{repo}/pulls/{pull_number}"],getReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],getReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}"],list:["GET /repos/{owner}/{repo}/pulls"],listCommentsForReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments"],listCommits:["GET /repos/{owner}/{repo}/pulls/{pull_number}/commits"],listFiles:["GET /repos/{owner}/{repo}/pulls/{pull_number}/files"],listRequestedReviewers:["GET /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],listReviewComments:["GET /repos/{owner}/{repo}/pulls/{pull_number}/comments"],listReviewCommentsForRepo:["GET /repos/{owner}/{repo}/pulls/comments"],listReviews:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],merge:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge"],removeRequestedReviewers:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],requestReviewers:["POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],submitReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events"],update:["PATCH /repos/{owner}/{repo}/pulls/{pull_number}"],updateBranch:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch"],updateReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],updateReviewComment:["PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}"]},rateLimit:{get:["GET /rate_limit"]},reactions:{createForCommitComment:["POST /repos/{owner}/{repo}/comments/{comment_id}/reactions"],createForIssue:["POST /repos/{owner}/{repo}/issues/{issue_number}/reactions"],createForIssueComment:["POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],createForPullRequestReviewComment:["POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],createForRelease:["POST /repos/{owner}/{repo}/releases/{release_id}/reactions"],createForTeamDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],createForTeamDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"],deleteForCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}/reactions/{reaction_id}"],deleteForIssue:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/reactions/{reaction_id}"],deleteForIssueComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions/{reaction_id}"],deleteForPullRequestComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions/{reaction_id}"],deleteForRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}/reactions/{reaction_id}"],deleteForTeamDiscussion:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions/{reaction_id}"],deleteForTeamDiscussionComment:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions/{reaction_id}"],listForCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}/reactions"],listForIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/reactions"],listForIssueComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],listForPullRequestReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],listForRelease:["GET /repos/{owner}/{repo}/releases/{release_id}/reactions"],listForTeamDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],listForTeamDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"]},repos:{acceptInvitation:["PATCH /user/repository_invitations/{invitation_id}",{},{renamed:["repos","acceptInvitationForAuthenticatedUser"]}],acceptInvitationForAuthenticatedUser:["PATCH /user/repository_invitations/{invitation_id}"],addAppAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],addCollaborator:["PUT /repos/{owner}/{repo}/collaborators/{username}"],addStatusCheckContexts:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],addTeamAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],addUserAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],checkAutomatedSecurityFixes:["GET /repos/{owner}/{repo}/automated-security-fixes"],checkCollaborator:["GET /repos/{owner}/{repo}/collaborators/{username}"],checkVulnerabilityAlerts:["GET /repos/{owner}/{repo}/vulnerability-alerts"],codeownersErrors:["GET /repos/{owner}/{repo}/codeowners/errors"],compareCommits:["GET /repos/{owner}/{repo}/compare/{base}...{head}"],compareCommitsWithBasehead:["GET /repos/{owner}/{repo}/compare/{basehead}"],createAutolink:["POST /repos/{owner}/{repo}/autolinks"],createCommitComment:["POST /repos/{owner}/{repo}/commits/{commit_sha}/comments"],createCommitSignatureProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],createCommitStatus:["POST /repos/{owner}/{repo}/statuses/{sha}"],createDeployKey:["POST /repos/{owner}/{repo}/keys"],createDeployment:["POST /repos/{owner}/{repo}/deployments"],createDeploymentBranchPolicy:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],createDeploymentProtectionRule:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],createDeploymentStatus:["POST /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],createDispatchEvent:["POST /repos/{owner}/{repo}/dispatches"],createForAuthenticatedUser:["POST /user/repos"],createFork:["POST /repos/{owner}/{repo}/forks"],createInOrg:["POST /orgs/{org}/repos"],createOrUpdateEnvironment:["PUT /repos/{owner}/{repo}/environments/{environment_name}"],createOrUpdateFileContents:["PUT /repos/{owner}/{repo}/contents/{path}"],createOrgRuleset:["POST /orgs/{org}/rulesets"],createPagesDeployment:["POST /repos/{owner}/{repo}/pages/deployment"],createPagesSite:["POST /repos/{owner}/{repo}/pages"],createRelease:["POST /repos/{owner}/{repo}/releases"],createRepoRuleset:["POST /repos/{owner}/{repo}/rulesets"],createTagProtection:["POST /repos/{owner}/{repo}/tags/protection"],createUsingTemplate:["POST /repos/{template_owner}/{template_repo}/generate"],createWebhook:["POST /repos/{owner}/{repo}/hooks"],declineInvitation:["DELETE /user/repository_invitations/{invitation_id}",{},{renamed:["repos","declineInvitationForAuthenticatedUser"]}],declineInvitationForAuthenticatedUser:["DELETE /user/repository_invitations/{invitation_id}"],delete:["DELETE /repos/{owner}/{repo}"],deleteAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],deleteAdminBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],deleteAnEnvironment:["DELETE /repos/{owner}/{repo}/environments/{environment_name}"],deleteAutolink:["DELETE /repos/{owner}/{repo}/autolinks/{autolink_id}"],deleteBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection"],deleteCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}"],deleteCommitSignatureProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],deleteDeployKey:["DELETE /repos/{owner}/{repo}/keys/{key_id}"],deleteDeployment:["DELETE /repos/{owner}/{repo}/deployments/{deployment_id}"],deleteDeploymentBranchPolicy:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],deleteFile:["DELETE /repos/{owner}/{repo}/contents/{path}"],deleteInvitation:["DELETE /repos/{owner}/{repo}/invitations/{invitation_id}"],deleteOrgRuleset:["DELETE /orgs/{org}/rulesets/{ruleset_id}"],deletePagesSite:["DELETE /repos/{owner}/{repo}/pages"],deletePullRequestReviewProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],deleteRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}"],deleteReleaseAsset:["DELETE /repos/{owner}/{repo}/releases/assets/{asset_id}"],deleteRepoRuleset:["DELETE /repos/{owner}/{repo}/rulesets/{ruleset_id}"],deleteTagProtection:["DELETE /repos/{owner}/{repo}/tags/protection/{tag_protection_id}"],deleteWebhook:["DELETE /repos/{owner}/{repo}/hooks/{hook_id}"],disableAutomatedSecurityFixes:["DELETE /repos/{owner}/{repo}/automated-security-fixes"],disableDeploymentProtectionRule:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],disablePrivateVulnerabilityReporting:["DELETE /repos/{owner}/{repo}/private-vulnerability-reporting"],disableVulnerabilityAlerts:["DELETE /repos/{owner}/{repo}/vulnerability-alerts"],downloadArchive:["GET /repos/{owner}/{repo}/zipball/{ref}",{},{renamed:["repos","downloadZipballArchive"]}],downloadTarballArchive:["GET /repos/{owner}/{repo}/tarball/{ref}"],downloadZipballArchive:["GET /repos/{owner}/{repo}/zipball/{ref}"],enableAutomatedSecurityFixes:["PUT /repos/{owner}/{repo}/automated-security-fixes"],enablePrivateVulnerabilityReporting:["PUT /repos/{owner}/{repo}/private-vulnerability-reporting"],enableVulnerabilityAlerts:["PUT /repos/{owner}/{repo}/vulnerability-alerts"],generateReleaseNotes:["POST /repos/{owner}/{repo}/releases/generate-notes"],get:["GET /repos/{owner}/{repo}"],getAccessRestrictions:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],getAdminBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],getAllDeploymentProtectionRules:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],getAllEnvironments:["GET /repos/{owner}/{repo}/environments"],getAllStatusCheckContexts:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts"],getAllTopics:["GET /repos/{owner}/{repo}/topics"],getAppsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps"],getAutolink:["GET /repos/{owner}/{repo}/autolinks/{autolink_id}"],getBranch:["GET /repos/{owner}/{repo}/branches/{branch}"],getBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection"],getBranchRules:["GET /repos/{owner}/{repo}/rules/branches/{branch}"],getClones:["GET /repos/{owner}/{repo}/traffic/clones"],getCodeFrequencyStats:["GET /repos/{owner}/{repo}/stats/code_frequency"],getCollaboratorPermissionLevel:["GET /repos/{owner}/{repo}/collaborators/{username}/permission"],getCombinedStatusForRef:["GET /repos/{owner}/{repo}/commits/{ref}/status"],getCommit:["GET /repos/{owner}/{repo}/commits/{ref}"],getCommitActivityStats:["GET /repos/{owner}/{repo}/stats/commit_activity"],getCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}"],getCommitSignatureProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],getCommunityProfileMetrics:["GET /repos/{owner}/{repo}/community/profile"],getContent:["GET /repos/{owner}/{repo}/contents/{path}"],getContributorsStats:["GET /repos/{owner}/{repo}/stats/contributors"],getCustomDeploymentProtectionRule:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],getCustomPropertiesValues:["GET /repos/{owner}/{repo}/properties/values"],getDeployKey:["GET /repos/{owner}/{repo}/keys/{key_id}"],getDeployment:["GET /repos/{owner}/{repo}/deployments/{deployment_id}"],getDeploymentBranchPolicy:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],getDeploymentStatus:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses/{status_id}"],getEnvironment:["GET /repos/{owner}/{repo}/environments/{environment_name}"],getLatestPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/latest"],getLatestRelease:["GET /repos/{owner}/{repo}/releases/latest"],getOrgRuleSuite:["GET /orgs/{org}/rulesets/rule-suites/{rule_suite_id}"],getOrgRuleSuites:["GET /orgs/{org}/rulesets/rule-suites"],getOrgRuleset:["GET /orgs/{org}/rulesets/{ruleset_id}"],getOrgRulesets:["GET /orgs/{org}/rulesets"],getPages:["GET /repos/{owner}/{repo}/pages"],getPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/{build_id}"],getPagesHealthCheck:["GET /repos/{owner}/{repo}/pages/health"],getParticipationStats:["GET /repos/{owner}/{repo}/stats/participation"],getPullRequestReviewProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],getPunchCardStats:["GET /repos/{owner}/{repo}/stats/punch_card"],getReadme:["GET /repos/{owner}/{repo}/readme"],getReadmeInDirectory:["GET /repos/{owner}/{repo}/readme/{dir}"],getRelease:["GET /repos/{owner}/{repo}/releases/{release_id}"],getReleaseAsset:["GET /repos/{owner}/{repo}/releases/assets/{asset_id}"],getReleaseByTag:["GET /repos/{owner}/{repo}/releases/tags/{tag}"],getRepoRuleSuite:["GET /repos/{owner}/{repo}/rulesets/rule-suites/{rule_suite_id}"],getRepoRuleSuites:["GET /repos/{owner}/{repo}/rulesets/rule-suites"],getRepoRuleset:["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}"],getRepoRulesets:["GET /repos/{owner}/{repo}/rulesets"],getStatusChecksProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],getTeamsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams"],getTopPaths:["GET /repos/{owner}/{repo}/traffic/popular/paths"],getTopReferrers:["GET /repos/{owner}/{repo}/traffic/popular/referrers"],getUsersWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users"],getViews:["GET /repos/{owner}/{repo}/traffic/views"],getWebhook:["GET /repos/{owner}/{repo}/hooks/{hook_id}"],getWebhookConfigForRepo:["GET /repos/{owner}/{repo}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}"],listActivities:["GET /repos/{owner}/{repo}/activity"],listAutolinks:["GET /repos/{owner}/{repo}/autolinks"],listBranches:["GET /repos/{owner}/{repo}/branches"],listBranchesForHeadCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/branches-where-head"],listCollaborators:["GET /repos/{owner}/{repo}/collaborators"],listCommentsForCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/comments"],listCommitCommentsForRepo:["GET /repos/{owner}/{repo}/comments"],listCommitStatusesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/statuses"],listCommits:["GET /repos/{owner}/{repo}/commits"],listContributors:["GET /repos/{owner}/{repo}/contributors"],listCustomDeploymentRuleIntegrations:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps"],listDeployKeys:["GET /repos/{owner}/{repo}/keys"],listDeploymentBranchPolicies:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],listDeploymentStatuses:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],listDeployments:["GET /repos/{owner}/{repo}/deployments"],listForAuthenticatedUser:["GET /user/repos"],listForOrg:["GET /orgs/{org}/repos"],listForUser:["GET /users/{username}/repos"],listForks:["GET /repos/{owner}/{repo}/forks"],listInvitations:["GET /repos/{owner}/{repo}/invitations"],listInvitationsForAuthenticatedUser:["GET /user/repository_invitations"],listLanguages:["GET /repos/{owner}/{repo}/languages"],listPagesBuilds:["GET /repos/{owner}/{repo}/pages/builds"],listPublic:["GET /repositories"],listPullRequestsAssociatedWithCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls"],listReleaseAssets:["GET /repos/{owner}/{repo}/releases/{release_id}/assets"],listReleases:["GET /repos/{owner}/{repo}/releases"],listTagProtection:["GET /repos/{owner}/{repo}/tags/protection"],listTags:["GET /repos/{owner}/{repo}/tags"],listTeams:["GET /repos/{owner}/{repo}/teams"],listWebhookDeliveries:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /repos/{owner}/{repo}/hooks"],merge:["POST /repos/{owner}/{repo}/merges"],mergeUpstream:["POST /repos/{owner}/{repo}/merge-upstream"],pingWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeAppAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],removeCollaborator:["DELETE /repos/{owner}/{repo}/collaborators/{username}"],removeStatusCheckContexts:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],removeStatusCheckProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],removeTeamAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],removeUserAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],renameBranch:["POST /repos/{owner}/{repo}/branches/{branch}/rename"],replaceAllTopics:["PUT /repos/{owner}/{repo}/topics"],requestPagesBuild:["POST /repos/{owner}/{repo}/pages/builds"],setAdminBranchProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],setAppAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],setStatusCheckContexts:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],setTeamAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],setUserAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],testPushWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/tests"],transfer:["POST /repos/{owner}/{repo}/transfer"],update:["PATCH /repos/{owner}/{repo}"],updateBranchProtection:["PUT /repos/{owner}/{repo}/branches/{branch}/protection"],updateCommitComment:["PATCH /repos/{owner}/{repo}/comments/{comment_id}"],updateDeploymentBranchPolicy:["PUT /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],updateInformationAboutPagesSite:["PUT /repos/{owner}/{repo}/pages"],updateInvitation:["PATCH /repos/{owner}/{repo}/invitations/{invitation_id}"],updateOrgRuleset:["PUT /orgs/{org}/rulesets/{ruleset_id}"],updatePullRequestReviewProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],updateRelease:["PATCH /repos/{owner}/{repo}/releases/{release_id}"],updateReleaseAsset:["PATCH /repos/{owner}/{repo}/releases/assets/{asset_id}"],updateRepoRuleset:["PUT /repos/{owner}/{repo}/rulesets/{ruleset_id}"],updateStatusCheckPotection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks",{},{renamed:["repos","updateStatusCheckProtection"]}],updateStatusCheckProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],updateWebhook:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}"],updateWebhookConfigForRepo:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}/config"],uploadReleaseAsset:["POST /repos/{owner}/{repo}/releases/{release_id}/assets{?name,label}",{baseUrl:"https://uploads.github.com"}]},search:{code:["GET /search/code"],commits:["GET /search/commits"],issuesAndPullRequests:["GET /search/issues"],labels:["GET /search/labels"],repos:["GET /search/repositories"],topics:["GET /search/topics"],users:["GET /search/users"]},secretScanning:{getAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"],listAlertsForEnterprise:["GET /enterprises/{enterprise}/secret-scanning/alerts"],listAlertsForOrg:["GET /orgs/{org}/secret-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/secret-scanning/alerts"],listLocationsForAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations"],updateAlert:["PATCH /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"]},securityAdvisories:{createPrivateVulnerabilityReport:["POST /repos/{owner}/{repo}/security-advisories/reports"],createRepositoryAdvisory:["POST /repos/{owner}/{repo}/security-advisories"],createRepositoryAdvisoryCveRequest:["POST /repos/{owner}/{repo}/security-advisories/{ghsa_id}/cve"],getGlobalAdvisory:["GET /advisories/{ghsa_id}"],getRepositoryAdvisory:["GET /repos/{owner}/{repo}/security-advisories/{ghsa_id}"],listGlobalAdvisories:["GET /advisories"],listOrgRepositoryAdvisories:["GET /orgs/{org}/security-advisories"],listRepositoryAdvisories:["GET /repos/{owner}/{repo}/security-advisories"],updateRepositoryAdvisory:["PATCH /repos/{owner}/{repo}/security-advisories/{ghsa_id}"]},teams:{addOrUpdateMembershipForUserInOrg:["PUT /orgs/{org}/teams/{team_slug}/memberships/{username}"],addOrUpdateProjectPermissionsInOrg:["PUT /orgs/{org}/teams/{team_slug}/projects/{project_id}"],addOrUpdateRepoPermissionsInOrg:["PUT /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],checkPermissionsForProjectInOrg:["GET /orgs/{org}/teams/{team_slug}/projects/{project_id}"],checkPermissionsForRepoInOrg:["GET /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],create:["POST /orgs/{org}/teams"],createDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],createDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions"],deleteDiscussionCommentInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],deleteDiscussionInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],deleteInOrg:["DELETE /orgs/{org}/teams/{team_slug}"],getByName:["GET /orgs/{org}/teams/{team_slug}"],getDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],getDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],getMembershipForUserInOrg:["GET /orgs/{org}/teams/{team_slug}/memberships/{username}"],list:["GET /orgs/{org}/teams"],listChildInOrg:["GET /orgs/{org}/teams/{team_slug}/teams"],listDiscussionCommentsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],listDiscussionsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions"],listForAuthenticatedUser:["GET /user/teams"],listMembersInOrg:["GET /orgs/{org}/teams/{team_slug}/members"],listPendingInvitationsInOrg:["GET /orgs/{org}/teams/{team_slug}/invitations"],listProjectsInOrg:["GET /orgs/{org}/teams/{team_slug}/projects"],listReposInOrg:["GET /orgs/{org}/teams/{team_slug}/repos"],removeMembershipForUserInOrg:["DELETE /orgs/{org}/teams/{team_slug}/memberships/{username}"],removeProjectInOrg:["DELETE /orgs/{org}/teams/{team_slug}/projects/{project_id}"],removeRepoInOrg:["DELETE /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],updateDiscussionCommentInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],updateDiscussionInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],updateInOrg:["PATCH /orgs/{org}/teams/{team_slug}"]},users:{addEmailForAuthenticated:["POST /user/emails",{},{renamed:["users","addEmailForAuthenticatedUser"]}],addEmailForAuthenticatedUser:["POST /user/emails"],addSocialAccountForAuthenticatedUser:["POST /user/social_accounts"],block:["PUT /user/blocks/{username}"],checkBlocked:["GET /user/blocks/{username}"],checkFollowingForUser:["GET /users/{username}/following/{target_user}"],checkPersonIsFollowedByAuthenticated:["GET /user/following/{username}"],createGpgKeyForAuthenticated:["POST /user/gpg_keys",{},{renamed:["users","createGpgKeyForAuthenticatedUser"]}],createGpgKeyForAuthenticatedUser:["POST /user/gpg_keys"],createPublicSshKeyForAuthenticated:["POST /user/keys",{},{renamed:["users","createPublicSshKeyForAuthenticatedUser"]}],createPublicSshKeyForAuthenticatedUser:["POST /user/keys"],createSshSigningKeyForAuthenticatedUser:["POST /user/ssh_signing_keys"],deleteEmailForAuthenticated:["DELETE /user/emails",{},{renamed:["users","deleteEmailForAuthenticatedUser"]}],deleteEmailForAuthenticatedUser:["DELETE /user/emails"],deleteGpgKeyForAuthenticated:["DELETE /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","deleteGpgKeyForAuthenticatedUser"]}],deleteGpgKeyForAuthenticatedUser:["DELETE /user/gpg_keys/{gpg_key_id}"],deletePublicSshKeyForAuthenticated:["DELETE /user/keys/{key_id}",{},{renamed:["users","deletePublicSshKeyForAuthenticatedUser"]}],deletePublicSshKeyForAuthenticatedUser:["DELETE /user/keys/{key_id}"],deleteSocialAccountForAuthenticatedUser:["DELETE /user/social_accounts"],deleteSshSigningKeyForAuthenticatedUser:["DELETE /user/ssh_signing_keys/{ssh_signing_key_id}"],follow:["PUT /user/following/{username}"],getAuthenticated:["GET /user"],getByUsername:["GET /users/{username}"],getContextForUser:["GET /users/{username}/hovercard"],getGpgKeyForAuthenticated:["GET /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","getGpgKeyForAuthenticatedUser"]}],getGpgKeyForAuthenticatedUser:["GET /user/gpg_keys/{gpg_key_id}"],getPublicSshKeyForAuthenticated:["GET /user/keys/{key_id}",{},{renamed:["users","getPublicSshKeyForAuthenticatedUser"]}],getPublicSshKeyForAuthenticatedUser:["GET /user/keys/{key_id}"],getSshSigningKeyForAuthenticatedUser:["GET /user/ssh_signing_keys/{ssh_signing_key_id}"],list:["GET /users"],listBlockedByAuthenticated:["GET /user/blocks",{},{renamed:["users","listBlockedByAuthenticatedUser"]}],listBlockedByAuthenticatedUser:["GET /user/blocks"],listEmailsForAuthenticated:["GET /user/emails",{},{renamed:["users","listEmailsForAuthenticatedUser"]}],listEmailsForAuthenticatedUser:["GET /user/emails"],listFollowedByAuthenticated:["GET /user/following",{},{renamed:["users","listFollowedByAuthenticatedUser"]}],listFollowedByAuthenticatedUser:["GET /user/following"],listFollowersForAuthenticatedUser:["GET /user/followers"],listFollowersForUser:["GET /users/{username}/followers"],listFollowingForUser:["GET /users/{username}/following"],listGpgKeysForAuthenticated:["GET /user/gpg_keys",{},{renamed:["users","listGpgKeysForAuthenticatedUser"]}],listGpgKeysForAuthenticatedUser:["GET /user/gpg_keys"],listGpgKeysForUser:["GET /users/{username}/gpg_keys"],listPublicEmailsForAuthenticated:["GET /user/public_emails",{},{renamed:["users","listPublicEmailsForAuthenticatedUser"]}],listPublicEmailsForAuthenticatedUser:["GET /user/public_emails"],listPublicKeysForUser:["GET /users/{username}/keys"],listPublicSshKeysForAuthenticated:["GET /user/keys",{},{renamed:["users","listPublicSshKeysForAuthenticatedUser"]}],listPublicSshKeysForAuthenticatedUser:["GET /user/keys"],listSocialAccountsForAuthenticatedUser:["GET /user/social_accounts"],listSocialAccountsForUser:["GET /users/{username}/social_accounts"],listSshSigningKeysForAuthenticatedUser:["GET /user/ssh_signing_keys"],listSshSigningKeysForUser:["GET /users/{username}/ssh_signing_keys"],setPrimaryEmailVisibilityForAuthenticated:["PATCH /user/email/visibility",{},{renamed:["users","setPrimaryEmailVisibilityForAuthenticatedUser"]}],setPrimaryEmailVisibilityForAuthenticatedUser:["PATCH /user/email/visibility"],unblock:["DELETE /user/blocks/{username}"],unfollow:["DELETE /user/following/{username}"],updateAuthenticated:["PATCH /user"]}};var a=i;var c=new Map;for(const[e,A]of Object.entries(a)){for(const[t,r]of Object.entries(A)){const[A,s,o]=r;const[n,i]=A.split(/ /);const a=Object.assign({method:n,url:i},s);if(!c.has(e)){c.set(e,new Map)}c.get(e).set(t,{scope:e,methodName:t,endpointDefaults:a,decorations:o})}}var g={has({scope:e},A){return c.get(e).has(A)},getOwnPropertyDescriptor(e,A){return{value:this.get(e,A),configurable:true,writable:true,enumerable:true}},defineProperty(e,A,t){Object.defineProperty(e.cache,A,t);return true},deleteProperty(e,A){delete e.cache[A];return true},ownKeys({scope:e}){return[...c.get(e).keys()]},set(e,A,t){return e.cache[A]=t},get({octokit:e,scope:A,cache:t},r){if(t[r]){return t[r]}const s=c.get(A).get(r);if(!s){return void 0}const{endpointDefaults:o,decorations:n}=s;if(n){t[r]=decorate(e,A,r,o,n)}else{t[r]=e.request.defaults(o)}return t[r]}};function endpointsToMethods(e){const A={};for(const t of c.keys()){A[t]=new Proxy({octokit:e,scope:t,cache:{}},g)}return A}function decorate(e,A,t,r,s){const o=e.request.defaults(r);function withDecorations(...r){let n=o.endpoint.merge(...r);if(s.mapToData){n=Object.assign({},n,{data:n[s.mapToData],[s.mapToData]:void 0});return o(n)}if(s.renamed){const[r,o]=s.renamed;e.log.warn(`octokit.${A}.${t}() has been renamed to octokit.${r}.${o}()`)}if(s.deprecated){e.log.warn(s.deprecated)}if(s.renamedParameters){const n=o.endpoint.merge(...r);for(const[r,o]of Object.entries(s.renamedParameters)){if(r in n){e.log.warn(`"${r}" parameter is deprecated for "octokit.${A}.${t}()". Use "${o}" instead`);if(!(o in n)){n[o]=n[r]}delete n[r]}}return o(n)}return o(...r)}return Object.assign(withDecorations,o)}function restEndpointMethods(e){const A=endpointsToMethods(e);return{rest:A}}restEndpointMethods.VERSION=n;function legacyRestEndpointMethods(e){const A=endpointsToMethods(e);return{...A,rest:A}}legacyRestEndpointMethods.VERSION=n;0&&0},7031:(e,A,t)=>{"use strict";var r=Object.create;var s=Object.defineProperty;var o=Object.getOwnPropertyDescriptor;var n=Object.getOwnPropertyNames;var i=Object.getPrototypeOf;var a=Object.prototype.hasOwnProperty;var __export=(e,A)=>{for(var t in A)s(e,t,{get:A[t],enumerable:true})};var __copyProps=(e,A,t,r)=>{if(A&&typeof A==="object"||typeof A==="function"){for(let i of n(A))if(!a.call(e,i)&&i!==t)s(e,i,{get:()=>A[i],enumerable:!(r=o(A,i))||r.enumerable})}return e};var __toESM=(e,A,t)=>(t=e!=null?r(i(e)):{},__copyProps(A||!e||!e.__esModule?s(t,"default",{value:e,enumerable:true}):t,e));var __toCommonJS=e=>__copyProps(s({},"__esModule",{value:true}),e);var c={};__export(c,{RequestError:()=>Q});e.exports=__toCommonJS(c);var g=t(3147);var E=__toESM(t(4947));var l=(0,E.default)((e=>console.warn(e)));var u=(0,E.default)((e=>console.warn(e)));var Q=class extends Error{constructor(e,A,t){super(e);if(Error.captureStackTrace){Error.captureStackTrace(this,this.constructor)}this.name="HttpError";this.status=A;let r;if("headers"in t&&typeof t.headers!=="undefined"){r=t.headers}if("response"in t){this.response=t.response;r=t.response.headers}const s=Object.assign({},t.request);if(t.request.headers.authorization){s.headers=Object.assign({},t.request.headers,{authorization:t.request.headers.authorization.replace(/ .*$/," [REDACTED]")})}s.url=s.url.replace(/\bclient_secret=\w+/g,"client_secret=[REDACTED]").replace(/\baccess_token=\w+/g,"access_token=[REDACTED]");this.request=s;Object.defineProperty(this,"code",{get(){l(new g.Deprecation("[@octokit/request-error] `error.code` is deprecated, use `error.status`."));return A}});Object.defineProperty(this,"headers",{get(){u(new g.Deprecation("[@octokit/request-error] `error.headers` is deprecated, use `error.response.headers`."));return r||{}}})}};0&&0},8662:(e,A,t)=>{"use strict";var r=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var o=Object.getOwnPropertyNames;var n=Object.prototype.hasOwnProperty;var __export=(e,A)=>{for(var t in A)r(e,t,{get:A[t],enumerable:true})};var __copyProps=(e,A,t,i)=>{if(A&&typeof A==="object"||typeof A==="function"){for(let a of o(A))if(!n.call(e,a)&&a!==t)r(e,a,{get:()=>A[a],enumerable:!(i=s(A,a))||i.enumerable})}return e};var __toCommonJS=e=>__copyProps(r({},"__esModule",{value:true}),e);var i={};__export(i,{request:()=>l});e.exports=__toCommonJS(i);var a=t(292);var c=t(9653);var g="8.1.6";function isPlainObject(e){if(typeof e!=="object"||e===null)return false;if(Object.prototype.toString.call(e)!=="[object Object]")return false;const A=Object.getPrototypeOf(e);if(A===null)return true;const t=Object.prototype.hasOwnProperty.call(A,"constructor")&&A.constructor;return typeof t==="function"&&t instanceof t&&Function.prototype.call(t)===Function.prototype.call(e)}var E=t(7031);function getBufferResponse(e){return e.arrayBuffer()}function fetchWrapper(e){var A,t,r;const s=e.request&&e.request.log?e.request.log:console;const o=((A=e.request)==null?void 0:A.parseSuccessResponseBody)!==false;if(isPlainObject(e.body)||Array.isArray(e.body)){e.body=JSON.stringify(e.body)}let n={};let i;let a;let{fetch:c}=globalThis;if((t=e.request)==null?void 0:t.fetch){c=e.request.fetch}if(!c){throw new Error("fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing")}return c(e.url,{method:e.method,body:e.body,headers:e.headers,signal:(r=e.request)==null?void 0:r.signal,...e.body&&{duplex:"half"}}).then((async A=>{a=A.url;i=A.status;for(const e of A.headers){n[e[0]]=e[1]}if("deprecation"in n){const A=n.link&&n.link.match(/<([^>]+)>; rel="deprecation"/);const t=A&&A.pop();s.warn(`[@octokit/request] "${e.method} ${e.url}" is deprecated. It is scheduled to be removed on ${n.sunset}${t?`. See ${t}`:""}`)}if(i===204||i===205){return}if(e.method==="HEAD"){if(i<400){return}throw new E.RequestError(A.statusText,i,{response:{url:a,status:i,headers:n,data:void 0},request:e})}if(i===304){throw new E.RequestError("Not modified",i,{response:{url:a,status:i,headers:n,data:await getResponseData(A)},request:e})}if(i>=400){const t=await getResponseData(A);const r=new E.RequestError(toErrorMessage(t),i,{response:{url:a,status:i,headers:n,data:t},request:e});throw r}return o?await getResponseData(A):A.body})).then((e=>({status:i,url:a,headers:n,data:e}))).catch((A=>{if(A instanceof E.RequestError)throw A;else if(A.name==="AbortError")throw A;let t=A.message;if(A.name==="TypeError"&&"cause"in A){if(A.cause instanceof Error){t=A.cause.message}else if(typeof A.cause==="string"){t=A.cause}}throw new E.RequestError(t,500,{request:e})}))}async function getResponseData(e){const A=e.headers.get("content-type");if(/application\/json/.test(A)){return e.json().catch((()=>e.text())).catch((()=>""))}if(!A||/^text\/|charset=utf-8$/.test(A)){return e.text()}return getBufferResponse(e)}function toErrorMessage(e){if(typeof e==="string")return e;if("message"in e){if(Array.isArray(e.errors)){return`${e.message}: ${e.errors.map(JSON.stringify).join(", ")}`}return e.message}return`Unknown error: ${JSON.stringify(e)}`}function withDefaults(e,A){const t=e.defaults(A);const newApi=function(e,A){const r=t.merge(e,A);if(!r.request||!r.request.hook){return fetchWrapper(t.parse(r))}const request2=(e,A)=>fetchWrapper(t.parse(t.merge(e,A)));Object.assign(request2,{endpoint:t,defaults:withDefaults.bind(null,t)});return r.request.hook(request2,r)};return Object.assign(newApi,{endpoint:t,defaults:withDefaults.bind(null,t)})}var l=withDefaults(a.endpoint,{headers:{"user-agent":`octokit-request.js/${g} ${(0,c.getUserAgent)()}`}});0&&0},7932:(e,A,t)=>{var r=t(2951);var s=t(2027);var o=t(4110);var n=Function.bind;var i=n.bind(n);function bindApi(e,A,t){var r=i(o,null).apply(null,t?[A,t]:[A]);e.api={remove:r};e.remove=r;["before","error","after","wrap"].forEach((function(r){var o=t?[A,r,t]:[A,r];e[r]=e.api[r]=i(s,null).apply(null,o)}))}function HookSingular(){var e="h";var A={registry:{}};var t=r.bind(null,A,e);bindApi(t,A,e);return t}function HookCollection(){var e={registry:{}};var A=r.bind(null,e);bindApi(A,e);return A}var a=false;function Hook(){if(!a){console.warn('[before-after-hook]: "Hook()" repurposing warning, use "Hook.Collection()". Read more: https://git.io/upgrade-before-after-hook-to-1.4');a=true}return HookCollection()}Hook.Singular=HookSingular.bind();Hook.Collection=HookCollection.bind();e.exports=Hook;e.exports.Hook=Hook;e.exports.Singular=Hook.Singular;e.exports.Collection=Hook.Collection},2027:e=>{e.exports=addHook;function addHook(e,A,t,r){var s=r;if(!e.registry[t]){e.registry[t]=[]}if(A==="before"){r=function(e,A){return Promise.resolve().then(s.bind(null,A)).then(e.bind(null,A))}}if(A==="after"){r=function(e,A){var t;return Promise.resolve().then(e.bind(null,A)).then((function(e){t=e;return s(t,A)})).then((function(){return t}))}}if(A==="error"){r=function(e,A){return Promise.resolve().then(e.bind(null,A)).catch((function(e){return s(e,A)}))}}e.registry[t].push({hook:r,orig:s})}},2951:e=>{e.exports=register;function register(e,A,t,r){if(typeof t!=="function"){throw new Error("method for before hook must be a function")}if(!r){r={}}if(Array.isArray(A)){return A.reverse().reduce((function(A,t){return register.bind(null,e,t,A,r)}),t)()}return Promise.resolve().then((function(){if(!e.registry[A]){return t(r)}return e.registry[A].reduce((function(e,A){return A.hook.bind(null,e,r)}),t)()}))}},4110:e=>{e.exports=removeHook;function removeHook(e,A,t){if(!e.registry[A]){return}var r=e.registry[A].map((function(e){return e.orig})).indexOf(t);if(r===-1){return}e.registry[A].splice(r,1)}},3147:(e,A)=>{"use strict";Object.defineProperty(A,"__esModule",{value:true});class Deprecation extends Error{constructor(e){super(e);if(Error.captureStackTrace){Error.captureStackTrace(this,this.constructor)}this.name="Deprecation"}}A.Deprecation=Deprecation},4947:(e,A,t)=>{var r=t(1459);e.exports=r(once);e.exports.strict=r(onceStrict);once.proto=once((function(){Object.defineProperty(Function.prototype,"once",{value:function(){return once(this)},configurable:true});Object.defineProperty(Function.prototype,"onceStrict",{value:function(){return onceStrict(this)},configurable:true})}));function once(e){var f=function(){if(f.called)return f.value;f.called=true;return f.value=e.apply(this,arguments)};f.called=false;return f}function onceStrict(e){var f=function(){if(f.called)throw new Error(f.onceError);f.called=true;return f.value=e.apply(this,arguments)};var A=e.name||"Function wrapped with `once`";f.onceError=A+" shouldn't be called more than once";f.called=false;return f}},7285:(e,A,t)=>{e.exports=t(8703)},8703:(e,A,t)=>{"use strict";var r=t(9278);var s=t(4756);var o=t(8611);var n=t(5692);var i=t(4434);var a=t(2613);var c=t(9023);A.httpOverHttp=httpOverHttp;A.httpsOverHttp=httpsOverHttp;A.httpOverHttps=httpOverHttps;A.httpsOverHttps=httpsOverHttps;function httpOverHttp(e){var A=new TunnelingAgent(e);A.request=o.request;return A}function httpsOverHttp(e){var A=new TunnelingAgent(e);A.request=o.request;A.createSocket=createSecureSocket;A.defaultPort=443;return A}function httpOverHttps(e){var A=new TunnelingAgent(e);A.request=n.request;return A}function httpsOverHttps(e){var A=new TunnelingAgent(e);A.request=n.request;A.createSocket=createSecureSocket;A.defaultPort=443;return A}function TunnelingAgent(e){var A=this;A.options=e||{};A.proxyOptions=A.options.proxy||{};A.maxSockets=A.options.maxSockets||o.Agent.defaultMaxSockets;A.requests=[];A.sockets=[];A.on("free",(function onFree(e,t,r,s){var o=toOptions(t,r,s);for(var n=0,i=A.requests.length;n=this.maxSockets){s.requests.push(o);return}s.createSocket(o,(function(A){A.on("free",onFree);A.on("close",onCloseOrRemove);A.on("agentRemove",onCloseOrRemove);e.onSocket(A);function onFree(){s.emit("free",A,o)}function onCloseOrRemove(e){s.removeSocket(A);A.removeListener("free",onFree);A.removeListener("close",onCloseOrRemove);A.removeListener("agentRemove",onCloseOrRemove)}}))};TunnelingAgent.prototype.createSocket=function createSocket(e,A){var t=this;var r={};t.sockets.push(r);var s=mergeOptions({},t.proxyOptions,{method:"CONNECT",path:e.host+":"+e.port,agent:false,headers:{host:e.host+":"+e.port}});if(e.localAddress){s.localAddress=e.localAddress}if(s.proxyAuth){s.headers=s.headers||{};s.headers["Proxy-Authorization"]="Basic "+new Buffer(s.proxyAuth).toString("base64")}g("making CONNECT request");var o=t.request(s);o.useChunkedEncodingByDefault=false;o.once("response",onResponse);o.once("upgrade",onUpgrade);o.once("connect",onConnect);o.once("error",onError);o.end();function onResponse(e){e.upgrade=true}function onUpgrade(e,A,t){process.nextTick((function(){onConnect(e,A,t)}))}function onConnect(s,n,i){o.removeAllListeners();n.removeAllListeners();if(s.statusCode!==200){g("tunneling socket could not be established, statusCode=%d",s.statusCode);n.destroy();var a=new Error("tunneling socket could not be established, "+"statusCode="+s.statusCode);a.code="ECONNRESET";e.request.emit("error",a);t.removeSocket(r);return}if(i.length>0){g("got illegal response body from proxy");n.destroy();var a=new Error("got illegal response body from proxy");a.code="ECONNRESET";e.request.emit("error",a);t.removeSocket(r);return}g("tunneling connection has established");t.sockets[t.sockets.indexOf(r)]=n;return A(n)}function onError(A){o.removeAllListeners();g("tunneling socket could not be established, cause=%s\n",A.message,A.stack);var s=new Error("tunneling socket could not be established, "+"cause="+A.message);s.code="ECONNRESET";e.request.emit("error",s);t.removeSocket(r)}};TunnelingAgent.prototype.removeSocket=function removeSocket(e){var A=this.sockets.indexOf(e);if(A===-1){return}this.sockets.splice(A,1);var t=this.requests.shift();if(t){this.createSocket(t,(function(e){t.request.onSocket(e)}))}};function createSecureSocket(e,A){var t=this;TunnelingAgent.prototype.createSocket.call(t,e,(function(r){var o=e.request.getHeader("host");var n=mergeOptions({},t.options,{socket:r,servername:o?o.replace(/:.*$/,""):e.host});var i=s.connect(0,n);t.sockets[t.sockets.indexOf(r)]=i;A(i)}))}function toOptions(e,A,t){if(typeof e==="string"){return{host:e,port:A,localAddress:t}}return e}function mergeOptions(e){for(var A=1,t=arguments.length;A{"use strict";const r=t(1247);const s=t(8841);const o=t(7221);const n=t(4094);const i=t(2263);const a=t(8787);const c=t(2806);const{InvalidArgumentError:g}=o;const E=t(2349);const l=t(4470);const u=t(7739);const Q=t(3703);const C=t(1986);const h=t(2567);const B=t(6014);const I=t(5723);const{getGlobalDispatcher:d,setGlobalDispatcher:p}=t(6875);const m=t(5658);const y=t(8977);const w=t(9421);let R;try{t(6982);R=true}catch{R=false}Object.assign(s.prototype,E);e.exports.Dispatcher=s;e.exports.Client=r;e.exports.Pool=n;e.exports.BalancedPool=i;e.exports.Agent=a;e.exports.ProxyAgent=B;e.exports.RetryHandler=I;e.exports.DecoratorHandler=m;e.exports.RedirectHandler=y;e.exports.createRedirectInterceptor=w;e.exports.buildConnector=l;e.exports.errors=o;function makeDispatcher(e){return(A,t,r)=>{if(typeof t==="function"){r=t;t=null}if(!A||typeof A!=="string"&&typeof A!=="object"&&!(A instanceof URL)){throw new g("invalid url")}if(t!=null&&typeof t!=="object"){throw new g("invalid opts")}if(t&&t.path!=null){if(typeof t.path!=="string"){throw new g("invalid opts.path")}let e=t.path;if(!t.path.startsWith("/")){e=`/${e}`}A=new URL(c.parseOrigin(A).origin+e)}else{if(!t){t=typeof A==="object"?A:{}}A=c.parseURL(A)}const{agent:s,dispatcher:o=d()}=t;if(s){throw new g("unsupported opts.agent. Did you mean opts.client?")}return e.call(o,{...t,origin:A.origin,path:A.search?`${A.pathname}${A.search}`:A.pathname,method:t.method||(t.body?"PUT":"GET")},r)}}e.exports.setGlobalDispatcher=p;e.exports.getGlobalDispatcher=d;if(c.nodeMajor>16||c.nodeMajor===16&&c.nodeMinor>=8){let A=null;e.exports.fetch=async function fetch(e){if(!A){A=t(5697).fetch}try{return await A(...arguments)}catch(e){if(typeof e==="object"){Error.captureStackTrace(e,this)}throw e}};e.exports.Headers=t(1815).Headers;e.exports.Response=t(7862).Response;e.exports.Request=t(1940).Request;e.exports.FormData=t(1499).FormData;e.exports.File=t(4019).File;e.exports.FileReader=t(2150).FileReader;const{setGlobalOrigin:r,getGlobalOrigin:s}=t(574);e.exports.setGlobalOrigin=r;e.exports.getGlobalOrigin=s;const{CacheStorage:o}=t(2472);const{kConstruct:n}=t(7202);e.exports.caches=new o(n)}if(c.nodeMajor>=16){const{deleteCookie:A,getCookies:r,getSetCookies:s,setCookie:o}=t(4346);e.exports.deleteCookie=A;e.exports.getCookies=r;e.exports.getSetCookies=s;e.exports.setCookie=o;const{parseMIMEType:n,serializeAMimeType:i}=t(7160);e.exports.parseMIMEType=n;e.exports.serializeAMimeType=i}if(c.nodeMajor>=18&&R){const{WebSocket:A}=t(7045);e.exports.WebSocket=A}e.exports.request=makeDispatcher(E.request);e.exports.stream=makeDispatcher(E.stream);e.exports.pipeline=makeDispatcher(E.pipeline);e.exports.connect=makeDispatcher(E.connect);e.exports.upgrade=makeDispatcher(E.upgrade);e.exports.MockClient=u;e.exports.MockPool=C;e.exports.MockAgent=Q;e.exports.mockErrors=h},8787:(e,A,t)=>{"use strict";const{InvalidArgumentError:r}=t(7221);const{kClients:s,kRunning:o,kClose:n,kDestroy:i,kDispatch:a,kInterceptors:c}=t(7781);const g=t(6915);const E=t(4094);const l=t(1247);const u=t(2806);const Q=t(9421);const{WeakRef:C,FinalizationRegistry:h}=t(4904)();const B=Symbol("onConnect");const I=Symbol("onDisconnect");const d=Symbol("onConnectionError");const p=Symbol("maxRedirections");const m=Symbol("onDrain");const y=Symbol("factory");const w=Symbol("finalizer");const R=Symbol("options");function defaultFactory(e,A){return A&&A.connections===1?new l(e,A):new E(e,A)}class Agent extends g{constructor({factory:e=defaultFactory,maxRedirections:A=0,connect:t,...o}={}){super();if(typeof e!=="function"){throw new r("factory must be a function.")}if(t!=null&&typeof t!=="function"&&typeof t!=="object"){throw new r("connect must be a function or an object")}if(!Number.isInteger(A)||A<0){throw new r("maxRedirections must be a positive number")}if(t&&typeof t!=="function"){t={...t}}this[c]=o.interceptors&&o.interceptors.Agent&&Array.isArray(o.interceptors.Agent)?o.interceptors.Agent:[Q({maxRedirections:A})];this[R]={...u.deepClone(o),connect:t};this[R].interceptors=o.interceptors?{...o.interceptors}:undefined;this[p]=A;this[y]=e;this[s]=new Map;this[w]=new h((e=>{const A=this[s].get(e);if(A!==undefined&&A.deref()===undefined){this[s].delete(e)}}));const n=this;this[m]=(e,A)=>{n.emit("drain",e,[n,...A])};this[B]=(e,A)=>{n.emit("connect",e,[n,...A])};this[I]=(e,A,t)=>{n.emit("disconnect",e,[n,...A],t)};this[d]=(e,A,t)=>{n.emit("connectionError",e,[n,...A],t)}}get[o](){let e=0;for(const A of this[s].values()){const t=A.deref();if(t){e+=t[o]}}return e}[a](e,A){let t;if(e.origin&&(typeof e.origin==="string"||e.origin instanceof URL)){t=String(e.origin)}else{throw new r("opts.origin must be a non-empty string or URL.")}const o=this[s].get(t);let n=o?o.deref():null;if(!n){n=this[y](e.origin,this[R]).on("drain",this[m]).on("connect",this[B]).on("disconnect",this[I]).on("connectionError",this[d]);this[s].set(t,new C(n));this[w].register(n,t)}return n.dispatch(e,A)}async[n](){const e=[];for(const A of this[s].values()){const t=A.deref();if(t){e.push(t.close())}}await Promise.all(e)}async[i](e){const A=[];for(const t of this[s].values()){const r=t.deref();if(r){A.push(r.destroy(e))}}await Promise.all(A)}}e.exports=Agent},4844:(e,A,t)=>{const{addAbortListener:r}=t(2806);const{RequestAbortedError:s}=t(7221);const o=Symbol("kListener");const n=Symbol("kSignal");function abort(e){if(e.abort){e.abort()}else{e.onError(new s)}}function addSignal(e,A){e[n]=null;e[o]=null;if(!A){return}if(A.aborted){abort(e);return}e[n]=A;e[o]=()=>{abort(e)};r(e[n],e[o])}function removeSignal(e){if(!e[n]){return}if("removeEventListener"in e[n]){e[n].removeEventListener("abort",e[o])}else{e[n].removeListener("abort",e[o])}e[n]=null;e[o]=null}e.exports={addSignal:addSignal,removeSignal:removeSignal}},1186:(e,A,t)=>{"use strict";const{AsyncResource:r}=t(290);const{InvalidArgumentError:s,RequestAbortedError:o,SocketError:n}=t(7221);const i=t(2806);const{addSignal:a,removeSignal:c}=t(4844);class ConnectHandler extends r{constructor(e,A){if(!e||typeof e!=="object"){throw new s("invalid opts")}if(typeof A!=="function"){throw new s("invalid callback")}const{signal:t,opaque:r,responseHeaders:o}=e;if(t&&typeof t.on!=="function"&&typeof t.addEventListener!=="function"){throw new s("signal must be an EventEmitter or EventTarget")}super("UNDICI_CONNECT");this.opaque=r||null;this.responseHeaders=o||null;this.callback=A;this.abort=null;a(this,t)}onConnect(e,A){if(!this.callback){throw new o}this.abort=e;this.context=A}onHeaders(){throw new n("bad connect",null)}onUpgrade(e,A,t){const{callback:r,opaque:s,context:o}=this;c(this);this.callback=null;let n=A;if(n!=null){n=this.responseHeaders==="raw"?i.parseRawHeaders(A):i.parseHeaders(A)}this.runInAsyncScope(r,null,null,{statusCode:e,headers:n,socket:t,opaque:s,context:o})}onError(e){const{callback:A,opaque:t}=this;c(this);if(A){this.callback=null;queueMicrotask((()=>{this.runInAsyncScope(A,null,e,{opaque:t})}))}}}function connect(e,A){if(A===undefined){return new Promise(((A,t)=>{connect.call(this,e,((e,r)=>e?t(e):A(r)))}))}try{const t=new ConnectHandler(e,A);this.dispatch({...e,method:"CONNECT"},t)}catch(t){if(typeof A!=="function"){throw t}const r=e&&e.opaque;queueMicrotask((()=>A(t,{opaque:r})))}}e.exports=connect},5144:(e,A,t)=>{"use strict";const{Readable:r,Duplex:s,PassThrough:o}=t(2203);const{InvalidArgumentError:n,InvalidReturnValueError:i,RequestAbortedError:a}=t(7221);const c=t(2806);const{AsyncResource:g}=t(290);const{addSignal:E,removeSignal:l}=t(4844);const u=t(2613);const Q=Symbol("resume");class PipelineRequest extends r{constructor(){super({autoDestroy:true});this[Q]=null}_read(){const{[Q]:e}=this;if(e){this[Q]=null;e()}}_destroy(e,A){this._read();A(e)}}class PipelineResponse extends r{constructor(e){super({autoDestroy:true});this[Q]=e}_read(){this[Q]()}_destroy(e,A){if(!e&&!this._readableState.endEmitted){e=new a}A(e)}}class PipelineHandler extends g{constructor(e,A){if(!e||typeof e!=="object"){throw new n("invalid opts")}if(typeof A!=="function"){throw new n("invalid handler")}const{signal:t,method:r,opaque:o,onInfo:i,responseHeaders:g}=e;if(t&&typeof t.on!=="function"&&typeof t.addEventListener!=="function"){throw new n("signal must be an EventEmitter or EventTarget")}if(r==="CONNECT"){throw new n("invalid method")}if(i&&typeof i!=="function"){throw new n("invalid onInfo callback")}super("UNDICI_PIPELINE");this.opaque=o||null;this.responseHeaders=g||null;this.handler=A;this.abort=null;this.context=null;this.onInfo=i||null;this.req=(new PipelineRequest).on("error",c.nop);this.ret=new s({readableObjectMode:e.objectMode,autoDestroy:true,read:()=>{const{body:e}=this;if(e&&e.resume){e.resume()}},write:(e,A,t)=>{const{req:r}=this;if(r.push(e,A)||r._readableState.destroyed){t()}else{r[Q]=t}},destroy:(e,A)=>{const{body:t,req:r,res:s,ret:o,abort:n}=this;if(!e&&!o._readableState.endEmitted){e=new a}if(n&&e){n()}c.destroy(t,e);c.destroy(r,e);c.destroy(s,e);l(this);A(e)}}).on("prefinish",(()=>{const{req:e}=this;e.push(null)}));this.res=null;E(this,t)}onConnect(e,A){const{ret:t,res:r}=this;u(!r,"pipeline cannot be retried");if(t.destroyed){throw new a}this.abort=e;this.context=A}onHeaders(e,A,t){const{opaque:r,handler:s,context:o}=this;if(e<200){if(this.onInfo){const t=this.responseHeaders==="raw"?c.parseRawHeaders(A):c.parseHeaders(A);this.onInfo({statusCode:e,headers:t})}return}this.res=new PipelineResponse(t);let n;try{this.handler=null;const t=this.responseHeaders==="raw"?c.parseRawHeaders(A):c.parseHeaders(A);n=this.runInAsyncScope(s,null,{statusCode:e,headers:t,opaque:r,body:this.res,context:o})}catch(e){this.res.on("error",c.nop);throw e}if(!n||typeof n.on!=="function"){throw new i("expected Readable")}n.on("data",(e=>{const{ret:A,body:t}=this;if(!A.push(e)&&t.pause){t.pause()}})).on("error",(e=>{const{ret:A}=this;c.destroy(A,e)})).on("end",(()=>{const{ret:e}=this;e.push(null)})).on("close",(()=>{const{ret:e}=this;if(!e._readableState.ended){c.destroy(e,new a)}}));this.body=n}onData(e){const{res:A}=this;return A.push(e)}onComplete(e){const{res:A}=this;A.push(null)}onError(e){const{ret:A}=this;this.handler=null;c.destroy(A,e)}}function pipeline(e,A){try{const t=new PipelineHandler(e,A);this.dispatch({...e,body:t.req},t);return t.ret}catch(e){return(new o).destroy(e)}}e.exports=pipeline},3933:(e,A,t)=>{"use strict";const r=t(5509);const{InvalidArgumentError:s,RequestAbortedError:o}=t(7221);const n=t(2806);const{getResolveErrorBodyCallback:i}=t(4061);const{AsyncResource:a}=t(290);const{addSignal:c,removeSignal:g}=t(4844);class RequestHandler extends a{constructor(e,A){if(!e||typeof e!=="object"){throw new s("invalid opts")}const{signal:t,method:r,opaque:o,body:i,onInfo:a,responseHeaders:g,throwOnError:E,highWaterMark:l}=e;try{if(typeof A!=="function"){throw new s("invalid callback")}if(l&&(typeof l!=="number"||l<0)){throw new s("invalid highWaterMark")}if(t&&typeof t.on!=="function"&&typeof t.addEventListener!=="function"){throw new s("signal must be an EventEmitter or EventTarget")}if(r==="CONNECT"){throw new s("invalid method")}if(a&&typeof a!=="function"){throw new s("invalid onInfo callback")}super("UNDICI_REQUEST")}catch(e){if(n.isStream(i)){n.destroy(i.on("error",n.nop),e)}throw e}this.responseHeaders=g||null;this.opaque=o||null;this.callback=A;this.res=null;this.abort=null;this.body=i;this.trailers={};this.context=null;this.onInfo=a||null;this.throwOnError=E;this.highWaterMark=l;if(n.isStream(i)){i.on("error",(e=>{this.onError(e)}))}c(this,t)}onConnect(e,A){if(!this.callback){throw new o}this.abort=e;this.context=A}onHeaders(e,A,t,s){const{callback:o,opaque:a,abort:c,context:g,responseHeaders:E,highWaterMark:l}=this;const u=E==="raw"?n.parseRawHeaders(A):n.parseHeaders(A);if(e<200){if(this.onInfo){this.onInfo({statusCode:e,headers:u})}return}const Q=E==="raw"?n.parseHeaders(A):u;const C=Q["content-type"];const h=new r({resume:t,abort:c,contentType:C,highWaterMark:l});this.callback=null;this.res=h;if(o!==null){if(this.throwOnError&&e>=400){this.runInAsyncScope(i,null,{callback:o,body:h,contentType:C,statusCode:e,statusMessage:s,headers:u})}else{this.runInAsyncScope(o,null,null,{statusCode:e,headers:u,trailers:this.trailers,opaque:a,body:h,context:g})}}}onData(e){const{res:A}=this;return A.push(e)}onComplete(e){const{res:A}=this;g(this);n.parseHeaders(e,this.trailers);A.push(null)}onError(e){const{res:A,callback:t,body:r,opaque:s}=this;g(this);if(t){this.callback=null;queueMicrotask((()=>{this.runInAsyncScope(t,null,e,{opaque:s})}))}if(A){this.res=null;queueMicrotask((()=>{n.destroy(A,e)}))}if(r){this.body=null;n.destroy(r,e)}}}function request(e,A){if(A===undefined){return new Promise(((A,t)=>{request.call(this,e,((e,r)=>e?t(e):A(r)))}))}try{this.dispatch(e,new RequestHandler(e,A))}catch(t){if(typeof A!=="function"){throw t}const r=e&&e.opaque;queueMicrotask((()=>A(t,{opaque:r})))}}e.exports=request;e.exports.RequestHandler=RequestHandler},9774:(e,A,t)=>{"use strict";const{finished:r,PassThrough:s}=t(2203);const{InvalidArgumentError:o,InvalidReturnValueError:n,RequestAbortedError:i}=t(7221);const a=t(2806);const{getResolveErrorBodyCallback:c}=t(4061);const{AsyncResource:g}=t(290);const{addSignal:E,removeSignal:l}=t(4844);class StreamHandler extends g{constructor(e,A,t){if(!e||typeof e!=="object"){throw new o("invalid opts")}const{signal:r,method:s,opaque:n,body:i,onInfo:c,responseHeaders:g,throwOnError:l}=e;try{if(typeof t!=="function"){throw new o("invalid callback")}if(typeof A!=="function"){throw new o("invalid factory")}if(r&&typeof r.on!=="function"&&typeof r.addEventListener!=="function"){throw new o("signal must be an EventEmitter or EventTarget")}if(s==="CONNECT"){throw new o("invalid method")}if(c&&typeof c!=="function"){throw new o("invalid onInfo callback")}super("UNDICI_STREAM")}catch(e){if(a.isStream(i)){a.destroy(i.on("error",a.nop),e)}throw e}this.responseHeaders=g||null;this.opaque=n||null;this.factory=A;this.callback=t;this.res=null;this.abort=null;this.context=null;this.trailers=null;this.body=i;this.onInfo=c||null;this.throwOnError=l||false;if(a.isStream(i)){i.on("error",(e=>{this.onError(e)}))}E(this,r)}onConnect(e,A){if(!this.callback){throw new i}this.abort=e;this.context=A}onHeaders(e,A,t,o){const{factory:i,opaque:g,context:E,callback:l,responseHeaders:u}=this;const Q=u==="raw"?a.parseRawHeaders(A):a.parseHeaders(A);if(e<200){if(this.onInfo){this.onInfo({statusCode:e,headers:Q})}return}this.factory=null;let C;if(this.throwOnError&&e>=400){const t=u==="raw"?a.parseHeaders(A):Q;const r=t["content-type"];C=new s;this.callback=null;this.runInAsyncScope(c,null,{callback:l,body:C,contentType:r,statusCode:e,statusMessage:o,headers:Q})}else{if(i===null){return}C=this.runInAsyncScope(i,null,{statusCode:e,headers:Q,opaque:g,context:E});if(!C||typeof C.write!=="function"||typeof C.end!=="function"||typeof C.on!=="function"){throw new n("expected Writable")}r(C,{readable:false},(e=>{const{callback:A,res:t,opaque:r,trailers:s,abort:o}=this;this.res=null;if(e||!t.readable){a.destroy(t,e)}this.callback=null;this.runInAsyncScope(A,null,e||null,{opaque:r,trailers:s});if(e){o()}}))}C.on("drain",t);this.res=C;const h=C.writableNeedDrain!==undefined?C.writableNeedDrain:C._writableState&&C._writableState.needDrain;return h!==true}onData(e){const{res:A}=this;return A?A.write(e):true}onComplete(e){const{res:A}=this;l(this);if(!A){return}this.trailers=a.parseHeaders(e);A.end()}onError(e){const{res:A,callback:t,opaque:r,body:s}=this;l(this);this.factory=null;if(A){this.res=null;a.destroy(A,e)}else if(t){this.callback=null;queueMicrotask((()=>{this.runInAsyncScope(t,null,e,{opaque:r})}))}if(s){this.body=null;a.destroy(s,e)}}}function stream(e,A,t){if(t===undefined){return new Promise(((t,r)=>{stream.call(this,e,A,((e,A)=>e?r(e):t(A)))}))}try{this.dispatch(e,new StreamHandler(e,A,t))}catch(A){if(typeof t!=="function"){throw A}const r=e&&e.opaque;queueMicrotask((()=>t(A,{opaque:r})))}}e.exports=stream},5836:(e,A,t)=>{"use strict";const{InvalidArgumentError:r,RequestAbortedError:s,SocketError:o}=t(7221);const{AsyncResource:n}=t(290);const i=t(2806);const{addSignal:a,removeSignal:c}=t(4844);const g=t(2613);class UpgradeHandler extends n{constructor(e,A){if(!e||typeof e!=="object"){throw new r("invalid opts")}if(typeof A!=="function"){throw new r("invalid callback")}const{signal:t,opaque:s,responseHeaders:o}=e;if(t&&typeof t.on!=="function"&&typeof t.addEventListener!=="function"){throw new r("signal must be an EventEmitter or EventTarget")}super("UNDICI_UPGRADE");this.responseHeaders=o||null;this.opaque=s||null;this.callback=A;this.abort=null;this.context=null;a(this,t)}onConnect(e,A){if(!this.callback){throw new s}this.abort=e;this.context=null}onHeaders(){throw new o("bad upgrade",null)}onUpgrade(e,A,t){const{callback:r,opaque:s,context:o}=this;g.strictEqual(e,101);c(this);this.callback=null;const n=this.responseHeaders==="raw"?i.parseRawHeaders(A):i.parseHeaders(A);this.runInAsyncScope(r,null,null,{headers:n,socket:t,opaque:s,context:o})}onError(e){const{callback:A,opaque:t}=this;c(this);if(A){this.callback=null;queueMicrotask((()=>{this.runInAsyncScope(A,null,e,{opaque:t})}))}}}function upgrade(e,A){if(A===undefined){return new Promise(((A,t)=>{upgrade.call(this,e,((e,r)=>e?t(e):A(r)))}))}try{const t=new UpgradeHandler(e,A);this.dispatch({...e,method:e.method||"GET",upgrade:e.protocol||"Websocket"},t)}catch(t){if(typeof A!=="function"){throw t}const r=e&&e.opaque;queueMicrotask((()=>A(t,{opaque:r})))}}e.exports=upgrade},2349:(e,A,t)=>{"use strict";e.exports.request=t(3933);e.exports.stream=t(9774);e.exports.pipeline=t(5144);e.exports.upgrade=t(5836);e.exports.connect=t(1186)},5509:(e,A,t)=>{"use strict";const r=t(2613);const{Readable:s}=t(2203);const{RequestAbortedError:o,NotSupportedError:n,InvalidArgumentError:i}=t(7221);const a=t(2806);const{ReadableStreamFrom:c,toUSVString:g}=t(2806);let E;const l=Symbol("kConsume");const u=Symbol("kReading");const Q=Symbol("kBody");const C=Symbol("abort");const h=Symbol("kContentType");const noop=()=>{};e.exports=class BodyReadable extends s{constructor({resume:e,abort:A,contentType:t="",highWaterMark:r=64*1024}){super({autoDestroy:true,read:e,highWaterMark:r});this._readableState.dataEmitted=false;this[C]=A;this[l]=null;this[Q]=null;this[h]=t;this[u]=false}destroy(e){if(this.destroyed){return this}if(!e&&!this._readableState.endEmitted){e=new o}if(e){this[C]()}return super.destroy(e)}emit(e,...A){if(e==="data"){this._readableState.dataEmitted=true}else if(e==="error"){this._readableState.errorEmitted=true}return super.emit(e,...A)}on(e,...A){if(e==="data"||e==="readable"){this[u]=true}return super.on(e,...A)}addListener(e,...A){return this.on(e,...A)}off(e,...A){const t=super.off(e,...A);if(e==="data"||e==="readable"){this[u]=this.listenerCount("data")>0||this.listenerCount("readable")>0}return t}removeListener(e,...A){return this.off(e,...A)}push(e){if(this[l]&&e!==null&&this.readableLength===0){consumePush(this[l],e);return this[u]?super.push(e):true}return super.push(e)}async text(){return consume(this,"text")}async json(){return consume(this,"json")}async blob(){return consume(this,"blob")}async arrayBuffer(){return consume(this,"arrayBuffer")}async formData(){throw new n}get bodyUsed(){return a.isDisturbed(this)}get body(){if(!this[Q]){this[Q]=c(this);if(this[l]){this[Q].getReader();r(this[Q].locked)}}return this[Q]}dump(e){let A=e&&Number.isFinite(e.limit)?e.limit:262144;const t=e&&e.signal;if(t){try{if(typeof t!=="object"||!("aborted"in t)){throw new i("signal must be an AbortSignal")}a.throwIfAborted(t)}catch(e){return Promise.reject(e)}}if(this.closed){return Promise.resolve(null)}return new Promise(((e,r)=>{const s=t?a.addAbortListener(t,(()=>{this.destroy()})):noop;this.on("close",(function(){s();if(t&&t.aborted){r(t.reason||Object.assign(new Error("The operation was aborted"),{name:"AbortError"}))}else{e(null)}})).on("error",noop).on("data",(function(e){A-=e.length;if(A<=0){this.destroy()}})).resume()}))}};function isLocked(e){return e[Q]&&e[Q].locked===true||e[l]}function isUnusable(e){return a.isDisturbed(e)||isLocked(e)}async function consume(e,A){if(isUnusable(e)){throw new TypeError("unusable")}r(!e[l]);return new Promise(((t,r)=>{e[l]={type:A,stream:e,resolve:t,reject:r,length:0,body:[]};e.on("error",(function(e){consumeFinish(this[l],e)})).on("close",(function(){if(this[l].body!==null){consumeFinish(this[l],new o)}}));process.nextTick(consumeStart,e[l])}))}function consumeStart(e){if(e.body===null){return}const{_readableState:A}=e.stream;for(const t of A.buffer){consumePush(e,t)}if(A.endEmitted){consumeEnd(this[l])}else{e.stream.on("end",(function(){consumeEnd(this[l])}))}e.stream.resume();while(e.stream.read()!=null){}}function consumeEnd(e){const{type:A,body:r,resolve:s,stream:o,length:n}=e;try{if(A==="text"){s(g(Buffer.concat(r)))}else if(A==="json"){s(JSON.parse(Buffer.concat(r)))}else if(A==="arrayBuffer"){const e=new Uint8Array(n);let A=0;for(const t of r){e.set(t,A);A+=t.byteLength}s(e.buffer)}else if(A==="blob"){if(!E){E=t(181).Blob}s(new E(r,{type:o[h]}))}consumeFinish(e)}catch(e){o.destroy(e)}}function consumePush(e,A){e.length+=A.length;e.body.push(A)}function consumeFinish(e,A){if(e.body===null){return}if(A){e.reject(A)}else{e.resolve()}e.type=null;e.stream=null;e.resolve=null;e.reject=null;e.length=0;e.body=null}},4061:(e,A,t)=>{const r=t(2613);const{ResponseStatusCodeError:s}=t(7221);const{toUSVString:o}=t(2806);async function getResolveErrorBodyCallback({callback:e,body:A,contentType:t,statusCode:n,statusMessage:i,headers:a}){r(A);let c=[];let g=0;for await(const e of A){c.push(e);g+=e.length;if(g>128*1024){c=null;break}}if(n===204||!t||!c){process.nextTick(e,new s(`Response status code ${n}${i?`: ${i}`:""}`,n,a));return}try{if(t.startsWith("application/json")){const A=JSON.parse(o(Buffer.concat(c)));process.nextTick(e,new s(`Response status code ${n}${i?`: ${i}`:""}`,n,a,A));return}if(t.startsWith("text/")){const A=o(Buffer.concat(c));process.nextTick(e,new s(`Response status code ${n}${i?`: ${i}`:""}`,n,a,A));return}}catch(e){}process.nextTick(e,new s(`Response status code ${n}${i?`: ${i}`:""}`,n,a))}e.exports={getResolveErrorBodyCallback:getResolveErrorBodyCallback}},2263:(e,A,t)=>{"use strict";const{BalancedPoolMissingUpstreamError:r,InvalidArgumentError:s}=t(7221);const{PoolBase:o,kClients:n,kNeedDrain:i,kAddClient:a,kRemoveClient:c,kGetDispatcher:g}=t(5934);const E=t(4094);const{kUrl:l,kInterceptors:u}=t(7781);const{parseOrigin:Q}=t(2806);const C=Symbol("factory");const h=Symbol("options");const B=Symbol("kGreatestCommonDivisor");const I=Symbol("kCurrentWeight");const d=Symbol("kIndex");const p=Symbol("kWeight");const m=Symbol("kMaxWeightPerServer");const y=Symbol("kErrorPenalty");function getGreatestCommonDivisor(e,A){if(A===0)return e;return getGreatestCommonDivisor(A,e%A)}function defaultFactory(e,A){return new E(e,A)}class BalancedPool extends o{constructor(e=[],{factory:A=defaultFactory,...t}={}){super();this[h]=t;this[d]=-1;this[I]=0;this[m]=this[h].maxWeightPerServer||100;this[y]=this[h].errorPenalty||15;if(!Array.isArray(e)){e=[e]}if(typeof A!=="function"){throw new s("factory must be a function.")}this[u]=t.interceptors&&t.interceptors.BalancedPool&&Array.isArray(t.interceptors.BalancedPool)?t.interceptors.BalancedPool:[];this[C]=A;for(const A of e){this.addUpstream(A)}this._updateBalancedPoolStats()}addUpstream(e){const A=Q(e).origin;if(this[n].find((e=>e[l].origin===A&&e.closed!==true&&e.destroyed!==true))){return this}const t=this[C](A,Object.assign({},this[h]));this[a](t);t.on("connect",(()=>{t[p]=Math.min(this[m],t[p]+this[y])}));t.on("connectionError",(()=>{t[p]=Math.max(1,t[p]-this[y]);this._updateBalancedPoolStats()}));t.on("disconnect",((...e)=>{const A=e[2];if(A&&A.code==="UND_ERR_SOCKET"){t[p]=Math.max(1,t[p]-this[y]);this._updateBalancedPoolStats()}}));for(const e of this[n]){e[p]=this[m]}this._updateBalancedPoolStats();return this}_updateBalancedPoolStats(){this[B]=this[n].map((e=>e[p])).reduce(getGreatestCommonDivisor,0)}removeUpstream(e){const A=Q(e).origin;const t=this[n].find((e=>e[l].origin===A&&e.closed!==true&&e.destroyed!==true));if(t){this[c](t)}return this}get upstreams(){return this[n].filter((e=>e.closed!==true&&e.destroyed!==true)).map((e=>e[l].origin))}[g](){if(this[n].length===0){throw new r}const e=this[n].find((e=>!e[i]&&e.closed!==true&&e.destroyed!==true));if(!e){return}const A=this[n].map((e=>e[i])).reduce(((e,A)=>e&&A),true);if(A){return}let t=0;let s=this[n].findIndex((e=>!e[i]));while(t++this[n][s][p]&&!e[i]){s=this[d]}if(this[d]===0){this[I]=this[I]-this[B];if(this[I]<=0){this[I]=this[m]}}if(e[p]>=this[I]&&!e[i]){return e}}this[I]=this[n][s][p];this[d]=s;return this[n][s]}}e.exports=BalancedPool},4937:(e,A,t)=>{"use strict";const{kConstruct:r}=t(7202);const{urlEquals:s,fieldValues:o}=t(4127);const{kEnumerableProperty:n,isDisturbed:i}=t(2806);const{kHeadersList:a}=t(7781);const{webidl:c}=t(6684);const{Response:g,cloneResponse:E}=t(7862);const{Request:l}=t(1940);const{kState:u,kHeaders:Q,kGuard:C,kRealm:h}=t(648);const{fetching:B}=t(5697);const{urlIsHttpHttpsScheme:I,createDeferredPromise:d,readAllBytes:p}=t(9913);const m=t(2613);const{getGlobalDispatcher:y}=t(6875);class Cache{#e;constructor(){if(arguments[0]!==r){c.illegalConstructor()}this.#e=arguments[1]}async match(e,A={}){c.brandCheck(this,Cache);c.argumentLengthCheck(arguments,1,{header:"Cache.match"});e=c.converters.RequestInfo(e);A=c.converters.CacheQueryOptions(A);const t=await this.matchAll(e,A);if(t.length===0){return}return t[0]}async matchAll(e=undefined,A={}){c.brandCheck(this,Cache);if(e!==undefined)e=c.converters.RequestInfo(e);A=c.converters.CacheQueryOptions(A);let t=null;if(e!==undefined){if(e instanceof l){t=e[u];if(t.method!=="GET"&&!A.ignoreMethod){return[]}}else if(typeof e==="string"){t=new l(e)[u]}}const r=[];if(e===undefined){for(const e of this.#e){r.push(e[1])}}else{const e=this.#A(t,A);for(const A of e){r.push(A[1])}}const s=[];for(const e of r){const A=new g(e.body?.source??null);const t=A[u].body;A[u]=e;A[u].body=t;A[Q][a]=e.headersList;A[Q][C]="immutable";s.push(A)}return Object.freeze(s)}async add(e){c.brandCheck(this,Cache);c.argumentLengthCheck(arguments,1,{header:"Cache.add"});e=c.converters.RequestInfo(e);const A=[e];const t=this.addAll(A);return await t}async addAll(e){c.brandCheck(this,Cache);c.argumentLengthCheck(arguments,1,{header:"Cache.addAll"});e=c.converters["sequence"](e);const A=[];const t=[];for(const A of e){if(typeof A==="string"){continue}const e=A[u];if(!I(e.url)||e.method!=="GET"){throw c.errors.exception({header:"Cache.addAll",message:"Expected http/s scheme when method is not GET."})}}const r=[];for(const s of e){const e=new l(s)[u];if(!I(e.url)){throw c.errors.exception({header:"Cache.addAll",message:"Expected http/s scheme."})}e.initiator="fetch";e.destination="subresource";t.push(e);const n=d();r.push(B({request:e,dispatcher:y(),processResponse(e){if(e.type==="error"||e.status===206||e.status<200||e.status>299){n.reject(c.errors.exception({header:"Cache.addAll",message:"Received an invalid status code or the request failed."}))}else if(e.headersList.contains("vary")){const A=o(e.headersList.get("vary"));for(const e of A){if(e==="*"){n.reject(c.errors.exception({header:"Cache.addAll",message:"invalid vary field value"}));for(const e of r){e.abort()}return}}}},processResponseEndOfBody(e){if(e.aborted){n.reject(new DOMException("aborted","AbortError"));return}n.resolve(e)}}));A.push(n.promise)}const s=Promise.all(A);const n=await s;const i=[];let a=0;for(const e of n){const A={type:"put",request:t[a],response:e};i.push(A);a++}const g=d();let E=null;try{this.#t(i)}catch(e){E=e}queueMicrotask((()=>{if(E===null){g.resolve(undefined)}else{g.reject(E)}}));return g.promise}async put(e,A){c.brandCheck(this,Cache);c.argumentLengthCheck(arguments,2,{header:"Cache.put"});e=c.converters.RequestInfo(e);A=c.converters.Response(A);let t=null;if(e instanceof l){t=e[u]}else{t=new l(e)[u]}if(!I(t.url)||t.method!=="GET"){throw c.errors.exception({header:"Cache.put",message:"Expected an http/s scheme when method is not GET"})}const r=A[u];if(r.status===206){throw c.errors.exception({header:"Cache.put",message:"Got 206 status"})}if(r.headersList.contains("vary")){const e=o(r.headersList.get("vary"));for(const A of e){if(A==="*"){throw c.errors.exception({header:"Cache.put",message:"Got * vary field value"})}}}if(r.body&&(i(r.body.stream)||r.body.stream.locked)){throw c.errors.exception({header:"Cache.put",message:"Response body is locked or disturbed"})}const s=E(r);const n=d();if(r.body!=null){const e=r.body.stream;const A=e.getReader();p(A).then(n.resolve,n.reject)}else{n.resolve(undefined)}const a=[];const g={type:"put",request:t,response:s};a.push(g);const Q=await n.promise;if(s.body!=null){s.body.source=Q}const C=d();let h=null;try{this.#t(a)}catch(e){h=e}queueMicrotask((()=>{if(h===null){C.resolve()}else{C.reject(h)}}));return C.promise}async delete(e,A={}){c.brandCheck(this,Cache);c.argumentLengthCheck(arguments,1,{header:"Cache.delete"});e=c.converters.RequestInfo(e);A=c.converters.CacheQueryOptions(A);let t=null;if(e instanceof l){t=e[u];if(t.method!=="GET"&&!A.ignoreMethod){return false}}else{m(typeof e==="string");t=new l(e)[u]}const r=[];const s={type:"delete",request:t,options:A};r.push(s);const o=d();let n=null;let i;try{i=this.#t(r)}catch(e){n=e}queueMicrotask((()=>{if(n===null){o.resolve(!!i?.length)}else{o.reject(n)}}));return o.promise}async keys(e=undefined,A={}){c.brandCheck(this,Cache);if(e!==undefined)e=c.converters.RequestInfo(e);A=c.converters.CacheQueryOptions(A);let t=null;if(e!==undefined){if(e instanceof l){t=e[u];if(t.method!=="GET"&&!A.ignoreMethod){return[]}}else if(typeof e==="string"){t=new l(e)[u]}}const r=d();const s=[];if(e===undefined){for(const e of this.#e){s.push(e[0])}}else{const e=this.#A(t,A);for(const A of e){s.push(A[0])}}queueMicrotask((()=>{const e=[];for(const A of s){const t=new l("https://a");t[u]=A;t[Q][a]=A.headersList;t[Q][C]="immutable";t[h]=A.client;e.push(t)}r.resolve(Object.freeze(e))}));return r.promise}#t(e){const A=this.#e;const t=[...A];const r=[];const s=[];try{for(const t of e){if(t.type!=="delete"&&t.type!=="put"){throw c.errors.exception({header:"Cache.#batchCacheOperations",message:'operation type does not match "delete" or "put"'})}if(t.type==="delete"&&t.response!=null){throw c.errors.exception({header:"Cache.#batchCacheOperations",message:"delete operation should not have an associated response"})}if(this.#A(t.request,t.options,r).length){throw new DOMException("???","InvalidStateError")}let e;if(t.type==="delete"){e=this.#A(t.request,t.options);if(e.length===0){return[]}for(const t of e){const e=A.indexOf(t);m(e!==-1);A.splice(e,1)}}else if(t.type==="put"){if(t.response==null){throw c.errors.exception({header:"Cache.#batchCacheOperations",message:"put operation should have an associated response"})}const s=t.request;if(!I(s.url)){throw c.errors.exception({header:"Cache.#batchCacheOperations",message:"expected http or https scheme"})}if(s.method!=="GET"){throw c.errors.exception({header:"Cache.#batchCacheOperations",message:"not get method"})}if(t.options!=null){throw c.errors.exception({header:"Cache.#batchCacheOperations",message:"options must not be defined"})}e=this.#A(t.request);for(const t of e){const e=A.indexOf(t);m(e!==-1);A.splice(e,1)}A.push([t.request,t.response]);r.push([t.request,t.response])}s.push([t.request,t.response])}return s}catch(e){this.#e.length=0;this.#e=t;throw e}}#A(e,A,t){const r=[];const s=t??this.#e;for(const t of s){const[s,o]=t;if(this.#r(e,s,o,A)){r.push(t)}}return r}#r(e,A,t=null,r){const n=new URL(e.url);const i=new URL(A.url);if(r?.ignoreSearch){i.search="";n.search=""}if(!s(n,i,true)){return false}if(t==null||r?.ignoreVary||!t.headersList.contains("vary")){return true}const a=o(t.headersList.get("vary"));for(const t of a){if(t==="*"){return false}const r=A.headersList.get(t);const s=e.headersList.get(t);if(r!==s){return false}}return true}}Object.defineProperties(Cache.prototype,{[Symbol.toStringTag]:{value:"Cache",configurable:true},match:n,matchAll:n,add:n,addAll:n,put:n,delete:n,keys:n});const w=[{key:"ignoreSearch",converter:c.converters.boolean,defaultValue:false},{key:"ignoreMethod",converter:c.converters.boolean,defaultValue:false},{key:"ignoreVary",converter:c.converters.boolean,defaultValue:false}];c.converters.CacheQueryOptions=c.dictionaryConverter(w);c.converters.MultiCacheQueryOptions=c.dictionaryConverter([...w,{key:"cacheName",converter:c.converters.DOMString}]);c.converters.Response=c.interfaceConverter(g);c.converters["sequence"]=c.sequenceConverter(c.converters.RequestInfo);e.exports={Cache:Cache}},2472:(e,A,t)=>{"use strict";const{kConstruct:r}=t(7202);const{Cache:s}=t(4937);const{webidl:o}=t(6684);const{kEnumerableProperty:n}=t(2806);class CacheStorage{#s=new Map;constructor(){if(arguments[0]!==r){o.illegalConstructor()}}async match(e,A={}){o.brandCheck(this,CacheStorage);o.argumentLengthCheck(arguments,1,{header:"CacheStorage.match"});e=o.converters.RequestInfo(e);A=o.converters.MultiCacheQueryOptions(A);if(A.cacheName!=null){if(this.#s.has(A.cacheName)){const t=this.#s.get(A.cacheName);const o=new s(r,t);return await o.match(e,A)}}else{for(const t of this.#s.values()){const o=new s(r,t);const n=await o.match(e,A);if(n!==undefined){return n}}}}async has(e){o.brandCheck(this,CacheStorage);o.argumentLengthCheck(arguments,1,{header:"CacheStorage.has"});e=o.converters.DOMString(e);return this.#s.has(e)}async open(e){o.brandCheck(this,CacheStorage);o.argumentLengthCheck(arguments,1,{header:"CacheStorage.open"});e=o.converters.DOMString(e);if(this.#s.has(e)){const A=this.#s.get(e);return new s(r,A)}const A=[];this.#s.set(e,A);return new s(r,A)}async delete(e){o.brandCheck(this,CacheStorage);o.argumentLengthCheck(arguments,1,{header:"CacheStorage.delete"});e=o.converters.DOMString(e);return this.#s.delete(e)}async keys(){o.brandCheck(this,CacheStorage);const e=this.#s.keys();return[...e]}}Object.defineProperties(CacheStorage.prototype,{[Symbol.toStringTag]:{value:"CacheStorage",configurable:true},match:n,has:n,open:n,delete:n,keys:n});e.exports={CacheStorage:CacheStorage}},7202:(e,A,t)=>{"use strict";e.exports={kConstruct:t(7781).kConstruct}},4127:(e,A,t)=>{"use strict";const r=t(2613);const{URLSerializer:s}=t(7160);const{isValidHeaderName:o}=t(9913);function urlEquals(e,A,t=false){const r=s(e,t);const o=s(A,t);return r===o}function fieldValues(e){r(e!==null);const A=[];for(let t of e.split(",")){t=t.trim();if(!t.length){continue}else if(!o(t)){continue}A.push(t)}return A}e.exports={urlEquals:urlEquals,fieldValues:fieldValues}},1247:(e,A,t)=>{"use strict";const r=t(2613);const s=t(9278);const o=t(8611);const{pipeline:n}=t(2203);const i=t(2806);const a=t(6190);const c=t(1289);const g=t(6915);const{RequestContentLengthMismatchError:E,ResponseContentLengthMismatchError:l,InvalidArgumentError:u,RequestAbortedError:Q,HeadersTimeoutError:C,HeadersOverflowError:h,SocketError:B,InformationalError:I,BodyTimeoutError:d,HTTPParserError:p,ResponseExceededMaxSizeError:m,ClientDestroyedError:y}=t(7221);const w=t(4470);const{kUrl:R,kReset:b,kServerName:D,kClient:k,kBusy:F,kParser:S,kConnect:T,kBlocking:N,kResuming:U,kRunning:L,kPending:G,kSize:v,kWriting:M,kQueue:H,kConnected:Y,kConnecting:O,kNeedDrain:_,kNoRef:P,kKeepAliveDefaultTimeout:J,kHostHeader:V,kPendingIdx:x,kRunningIdx:q,kError:W,kPipelining:j,kSocket:Z,kKeepAliveTimeoutValue:X,kMaxHeadersSize:K,kKeepAliveMaxTimeout:z,kKeepAliveTimeoutThreshold:$,kHeadersTimeout:ee,kBodyTimeout:Ae,kStrictContentLength:te,kConnector:re,kMaxRedirections:se,kMaxRequests:oe,kCounter:ne,kClose:ie,kDestroy:ae,kDispatch:ce,kInterceptors:ge,kLocalAddress:Ee,kMaxResponseSize:le,kHTTPConnVersion:ue,kHost:Qe,kHTTP2Session:Ce,kHTTP2SessionState:he,kHTTP2BuildRequest:Be,kHTTP2CopyHeaders:Ie,kHTTP1BuildRequest:de}=t(7781);let pe;try{pe=t(5675)}catch{pe={constants:{}}}const{constants:{HTTP2_HEADER_AUTHORITY:fe,HTTP2_HEADER_METHOD:me,HTTP2_HEADER_PATH:ye,HTTP2_HEADER_SCHEME:we,HTTP2_HEADER_CONTENT_LENGTH:Re,HTTP2_HEADER_EXPECT:be,HTTP2_HEADER_STATUS:De}}=pe;let ke=false;const Fe=Buffer[Symbol.species];const Se=Symbol("kClosedResolve");const Te={};try{const e=t(1637);Te.sendHeaders=e.channel("undici:client:sendHeaders");Te.beforeConnect=e.channel("undici:client:beforeConnect");Te.connectError=e.channel("undici:client:connectError");Te.connected=e.channel("undici:client:connected")}catch{Te.sendHeaders={hasSubscribers:false};Te.beforeConnect={hasSubscribers:false};Te.connectError={hasSubscribers:false};Te.connected={hasSubscribers:false}}class Client extends g{constructor(e,{interceptors:A,maxHeaderSize:t,headersTimeout:r,socketTimeout:n,requestTimeout:a,connectTimeout:c,bodyTimeout:g,idleTimeout:E,keepAlive:l,keepAliveTimeout:Q,maxKeepAliveTimeout:C,keepAliveMaxTimeout:h,keepAliveTimeoutThreshold:B,socketPath:I,pipelining:d,tls:p,strictContentLength:m,maxCachedSessions:y,maxRedirections:b,connect:k,maxRequestsPerClient:F,localAddress:S,maxResponseSize:T,autoSelectFamily:N,autoSelectFamilyAttemptTimeout:L,allowH2:G,maxConcurrentStreams:v}={}){super();if(l!==undefined){throw new u("unsupported keepAlive, use pipelining=0 instead")}if(n!==undefined){throw new u("unsupported socketTimeout, use headersTimeout & bodyTimeout instead")}if(a!==undefined){throw new u("unsupported requestTimeout, use headersTimeout & bodyTimeout instead")}if(E!==undefined){throw new u("unsupported idleTimeout, use keepAliveTimeout instead")}if(C!==undefined){throw new u("unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead")}if(t!=null&&!Number.isFinite(t)){throw new u("invalid maxHeaderSize")}if(I!=null&&typeof I!=="string"){throw new u("invalid socketPath")}if(c!=null&&(!Number.isFinite(c)||c<0)){throw new u("invalid connectTimeout")}if(Q!=null&&(!Number.isFinite(Q)||Q<=0)){throw new u("invalid keepAliveTimeout")}if(h!=null&&(!Number.isFinite(h)||h<=0)){throw new u("invalid keepAliveMaxTimeout")}if(B!=null&&!Number.isFinite(B)){throw new u("invalid keepAliveTimeoutThreshold")}if(r!=null&&(!Number.isInteger(r)||r<0)){throw new u("headersTimeout must be a positive integer or zero")}if(g!=null&&(!Number.isInteger(g)||g<0)){throw new u("bodyTimeout must be a positive integer or zero")}if(k!=null&&typeof k!=="function"&&typeof k!=="object"){throw new u("connect must be a function or an object")}if(b!=null&&(!Number.isInteger(b)||b<0)){throw new u("maxRedirections must be a positive number")}if(F!=null&&(!Number.isInteger(F)||F<0)){throw new u("maxRequestsPerClient must be a positive number")}if(S!=null&&(typeof S!=="string"||s.isIP(S)===0)){throw new u("localAddress must be valid string IP address")}if(T!=null&&(!Number.isInteger(T)||T<-1)){throw new u("maxResponseSize must be a positive number")}if(L!=null&&(!Number.isInteger(L)||L<-1)){throw new u("autoSelectFamilyAttemptTimeout must be a positive number")}if(G!=null&&typeof G!=="boolean"){throw new u("allowH2 must be a valid boolean value")}if(v!=null&&(typeof v!=="number"||v<1)){throw new u("maxConcurrentStreams must be a possitive integer, greater than 0")}if(typeof k!=="function"){k=w({...p,maxCachedSessions:y,allowH2:G,socketPath:I,timeout:c,...i.nodeHasAutoSelectFamily&&N?{autoSelectFamily:N,autoSelectFamilyAttemptTimeout:L}:undefined,...k})}this[ge]=A&&A.Client&&Array.isArray(A.Client)?A.Client:[Ue({maxRedirections:b})];this[R]=i.parseOrigin(e);this[re]=k;this[Z]=null;this[j]=d!=null?d:1;this[K]=t||o.maxHeaderSize;this[J]=Q==null?4e3:Q;this[z]=h==null?6e5:h;this[$]=B==null?1e3:B;this[X]=this[J];this[D]=null;this[Ee]=S!=null?S:null;this[U]=0;this[_]=0;this[V]=`host: ${this[R].hostname}${this[R].port?`:${this[R].port}`:""}\r\n`;this[Ae]=g!=null?g:3e5;this[ee]=r!=null?r:3e5;this[te]=m==null?true:m;this[se]=b;this[oe]=F;this[Se]=null;this[le]=T>-1?T:-1;this[ue]="h1";this[Ce]=null;this[he]=!G?null:{openStreams:0,maxConcurrentStreams:v!=null?v:100};this[Qe]=`${this[R].hostname}${this[R].port?`:${this[R].port}`:""}`;this[H]=[];this[q]=0;this[x]=0}get pipelining(){return this[j]}set pipelining(e){this[j]=e;resume(this,true)}get[G](){return this[H].length-this[x]}get[L](){return this[x]-this[q]}get[v](){return this[H].length-this[q]}get[Y](){return!!this[Z]&&!this[O]&&!this[Z].destroyed}get[F](){const e=this[Z];return e&&(e[b]||e[M]||e[N])||this[v]>=(this[j]||1)||this[G]>0}[T](e){connect(this);this.once("connect",e)}[ce](e,A){const t=e.origin||this[R].origin;const r=this[ue]==="h2"?c[Be](t,e,A):c[de](t,e,A);this[H].push(r);if(this[U]){}else if(i.bodyLength(r.body)==null&&i.isIterable(r.body)){this[U]=1;process.nextTick(resume,this)}else{resume(this,true)}if(this[U]&&this[_]!==2&&this[F]){this[_]=2}return this[_]<2}async[ie](){return new Promise((e=>{if(!this[v]){e(null)}else{this[Se]=e}}))}async[ae](e){return new Promise((A=>{const t=this[H].splice(this[x]);for(let A=0;A{if(this[Se]){this[Se]();this[Se]=null}A()};if(this[Ce]!=null){i.destroy(this[Ce],e);this[Ce]=null;this[he]=null}if(!this[Z]){queueMicrotask(callback)}else{i.destroy(this[Z].on("close",callback),e)}resume(this)}))}}function onHttp2SessionError(e){r(e.code!=="ERR_TLS_CERT_ALTNAME_INVALID");this[Z][W]=e;onError(this[k],e)}function onHttp2FrameError(e,A,t){const r=new I(`HTTP/2: "frameError" received - type ${e}, code ${A}`);if(t===0){this[Z][W]=r;onError(this[k],r)}}function onHttp2SessionEnd(){i.destroy(this,new B("other side closed"));i.destroy(this[Z],new B("other side closed"))}function onHTTP2GoAway(e){const A=this[k];const t=new I(`HTTP/2: "GOAWAY" frame received with code ${e}`);A[Z]=null;A[Ce]=null;if(A.destroyed){r(this[G]===0);const e=A[H].splice(A[q]);for(let A=0;A0){const e=A[H][A[q]];A[H][A[q]++]=null;errorRequest(A,e,t)}A[x]=A[q];r(A[L]===0);A.emit("disconnect",A[R],[A],t);resume(A)}const Ne=t(5766);const Ue=t(9421);const Le=Buffer.alloc(0);async function lazyllhttp(){const e=process.env.JEST_WORKER_ID?t(2108):undefined;let A;try{A=await WebAssembly.compile(Buffer.from(t(2084),"base64"))}catch(r){A=await WebAssembly.compile(Buffer.from(e||t(2108),"base64"))}return await WebAssembly.instantiate(A,{env:{wasm_on_url:(e,A,t)=>0,wasm_on_status:(e,A,t)=>{r.strictEqual(Me.ptr,e);const s=A-Oe+He.byteOffset;return Me.onStatus(new Fe(He.buffer,s,t))||0},wasm_on_message_begin:e=>{r.strictEqual(Me.ptr,e);return Me.onMessageBegin()||0},wasm_on_header_field:(e,A,t)=>{r.strictEqual(Me.ptr,e);const s=A-Oe+He.byteOffset;return Me.onHeaderField(new Fe(He.buffer,s,t))||0},wasm_on_header_value:(e,A,t)=>{r.strictEqual(Me.ptr,e);const s=A-Oe+He.byteOffset;return Me.onHeaderValue(new Fe(He.buffer,s,t))||0},wasm_on_headers_complete:(e,A,t,s)=>{r.strictEqual(Me.ptr,e);return Me.onHeadersComplete(A,Boolean(t),Boolean(s))||0},wasm_on_body:(e,A,t)=>{r.strictEqual(Me.ptr,e);const s=A-Oe+He.byteOffset;return Me.onBody(new Fe(He.buffer,s,t))||0},wasm_on_message_complete:e=>{r.strictEqual(Me.ptr,e);return Me.onMessageComplete()||0}}})}let Ge=null;let ve=lazyllhttp();ve.catch();let Me=null;let He=null;let Ye=0;let Oe=null;const _e=1;const Pe=2;const Je=3;class Parser{constructor(e,A,{exports:t}){r(Number.isFinite(e[K])&&e[K]>0);this.llhttp=t;this.ptr=this.llhttp.llhttp_alloc(Ne.TYPE.RESPONSE);this.client=e;this.socket=A;this.timeout=null;this.timeoutValue=null;this.timeoutType=null;this.statusCode=null;this.statusText="";this.upgrade=false;this.headers=[];this.headersSize=0;this.headersMaxSize=e[K];this.shouldKeepAlive=false;this.paused=false;this.resume=this.resume.bind(this);this.bytesRead=0;this.keepAlive="";this.contentLength="";this.connection="";this.maxResponseSize=e[le]}setTimeout(e,A){this.timeoutType=A;if(e!==this.timeoutValue){a.clearTimeout(this.timeout);if(e){this.timeout=a.setTimeout(onParserTimeout,e,this);if(this.timeout.unref){this.timeout.unref()}}else{this.timeout=null}this.timeoutValue=e}else if(this.timeout){if(this.timeout.refresh){this.timeout.refresh()}}}resume(){if(this.socket.destroyed||!this.paused){return}r(this.ptr!=null);r(Me==null);this.llhttp.llhttp_resume(this.ptr);r(this.timeoutType===Pe);if(this.timeout){if(this.timeout.refresh){this.timeout.refresh()}}this.paused=false;this.execute(this.socket.read()||Le);this.readMore()}readMore(){while(!this.paused&&this.ptr){const e=this.socket.read();if(e===null){break}this.execute(e)}}execute(e){r(this.ptr!=null);r(Me==null);r(!this.paused);const{socket:A,llhttp:t}=this;if(e.length>Ye){if(Oe){t.free(Oe)}Ye=Math.ceil(e.length/4096)*4096;Oe=t.malloc(Ye)}new Uint8Array(t.memory.buffer,Oe,Ye).set(e);try{let r;try{He=e;Me=this;r=t.llhttp_execute(this.ptr,Oe,e.length)}catch(e){throw e}finally{Me=null;He=null}const s=t.llhttp_get_error_pos(this.ptr)-Oe;if(r===Ne.ERROR.PAUSED_UPGRADE){this.onUpgrade(e.slice(s))}else if(r===Ne.ERROR.PAUSED){this.paused=true;A.unshift(e.slice(s))}else if(r!==Ne.ERROR.OK){const A=t.llhttp_get_error_reason(this.ptr);let o="";if(A){const e=new Uint8Array(t.memory.buffer,A).indexOf(0);o="Response does not match the HTTP/1.1 protocol ("+Buffer.from(t.memory.buffer,A,e).toString()+")"}throw new p(o,Ne.ERROR[r],e.slice(s))}}catch(e){i.destroy(A,e)}}destroy(){r(this.ptr!=null);r(Me==null);this.llhttp.llhttp_free(this.ptr);this.ptr=null;a.clearTimeout(this.timeout);this.timeout=null;this.timeoutValue=null;this.timeoutType=null;this.paused=false}onStatus(e){this.statusText=e.toString()}onMessageBegin(){const{socket:e,client:A}=this;if(e.destroyed){return-1}const t=A[H][A[q]];if(!t){return-1}}onHeaderField(e){const A=this.headers.length;if((A&1)===0){this.headers.push(e)}else{this.headers[A-1]=Buffer.concat([this.headers[A-1],e])}this.trackHeader(e.length)}onHeaderValue(e){let A=this.headers.length;if((A&1)===1){this.headers.push(e);A+=1}else{this.headers[A-1]=Buffer.concat([this.headers[A-1],e])}const t=this.headers[A-2];if(t.length===10&&t.toString().toLowerCase()==="keep-alive"){this.keepAlive+=e.toString()}else if(t.length===10&&t.toString().toLowerCase()==="connection"){this.connection+=e.toString()}else if(t.length===14&&t.toString().toLowerCase()==="content-length"){this.contentLength+=e.toString()}this.trackHeader(e.length)}trackHeader(e){this.headersSize+=e;if(this.headersSize>=this.headersMaxSize){i.destroy(this.socket,new h)}}onUpgrade(e){const{upgrade:A,client:t,socket:s,headers:o,statusCode:n}=this;r(A);const a=t[H][t[q]];r(a);r(!s.destroyed);r(s===t[Z]);r(!this.paused);r(a.upgrade||a.method==="CONNECT");this.statusCode=null;this.statusText="";this.shouldKeepAlive=null;r(this.headers.length%2===0);this.headers=[];this.headersSize=0;s.unshift(e);s[S].destroy();s[S]=null;s[k]=null;s[W]=null;s.removeListener("error",onSocketError).removeListener("readable",onSocketReadable).removeListener("end",onSocketEnd).removeListener("close",onSocketClose);t[Z]=null;t[H][t[q]++]=null;t.emit("disconnect",t[R],[t],new I("upgrade"));try{a.onUpgrade(n,o,s)}catch(e){i.destroy(s,e)}resume(t)}onHeadersComplete(e,A,t){const{client:s,socket:o,headers:n,statusText:a}=this;if(o.destroyed){return-1}const c=s[H][s[q]];if(!c){return-1}r(!this.upgrade);r(this.statusCode<200);if(e===100){i.destroy(o,new B("bad response",i.getSocketInfo(o)));return-1}if(A&&!c.upgrade){i.destroy(o,new B("bad upgrade",i.getSocketInfo(o)));return-1}r.strictEqual(this.timeoutType,_e);this.statusCode=e;this.shouldKeepAlive=t||c.method==="HEAD"&&!o[b]&&this.connection.toLowerCase()==="keep-alive";if(this.statusCode>=200){const e=c.bodyTimeout!=null?c.bodyTimeout:s[Ae];this.setTimeout(e,Pe)}else if(this.timeout){if(this.timeout.refresh){this.timeout.refresh()}}if(c.method==="CONNECT"){r(s[L]===1);this.upgrade=true;return 2}if(A){r(s[L]===1);this.upgrade=true;return 2}r(this.headers.length%2===0);this.headers=[];this.headersSize=0;if(this.shouldKeepAlive&&s[j]){const e=this.keepAlive?i.parseKeepAliveTimeout(this.keepAlive):null;if(e!=null){const A=Math.min(e-s[$],s[z]);if(A<=0){o[b]=true}else{s[X]=A}}else{s[X]=s[J]}}else{o[b]=true}const g=c.onHeaders(e,n,this.resume,a)===false;if(c.aborted){return-1}if(c.method==="HEAD"){return 1}if(e<200){return 1}if(o[N]){o[N]=false;resume(s)}return g?Ne.ERROR.PAUSED:0}onBody(e){const{client:A,socket:t,statusCode:s,maxResponseSize:o}=this;if(t.destroyed){return-1}const n=A[H][A[q]];r(n);r.strictEqual(this.timeoutType,Pe);if(this.timeout){if(this.timeout.refresh){this.timeout.refresh()}}r(s>=200);if(o>-1&&this.bytesRead+e.length>o){i.destroy(t,new m);return-1}this.bytesRead+=e.length;if(n.onData(e)===false){return Ne.ERROR.PAUSED}}onMessageComplete(){const{client:e,socket:A,statusCode:t,upgrade:s,headers:o,contentLength:n,bytesRead:a,shouldKeepAlive:c}=this;if(A.destroyed&&(!t||c)){return-1}if(s){return}const g=e[H][e[q]];r(g);r(t>=100);this.statusCode=null;this.statusText="";this.bytesRead=0;this.contentLength="";this.keepAlive="";this.connection="";r(this.headers.length%2===0);this.headers=[];this.headersSize=0;if(t<200){return}if(g.method!=="HEAD"&&n&&a!==parseInt(n,10)){i.destroy(A,new l);return-1}g.onComplete(o);e[H][e[q]++]=null;if(A[M]){r.strictEqual(e[L],0);i.destroy(A,new I("reset"));return Ne.ERROR.PAUSED}else if(!c){i.destroy(A,new I("reset"));return Ne.ERROR.PAUSED}else if(A[b]&&e[L]===0){i.destroy(A,new I("reset"));return Ne.ERROR.PAUSED}else if(e[j]===1){setImmediate(resume,e)}else{resume(e)}}}function onParserTimeout(e){const{socket:A,timeoutType:t,client:s}=e;if(t===_e){if(!A[M]||A.writableNeedDrain||s[L]>1){r(!e.paused,"cannot be paused while waiting for headers");i.destroy(A,new C)}}else if(t===Pe){if(!e.paused){i.destroy(A,new d)}}else if(t===Je){r(s[L]===0&&s[X]);i.destroy(A,new I("socket idle timeout"))}}function onSocketReadable(){const{[S]:e}=this;if(e){e.readMore()}}function onSocketError(e){const{[k]:A,[S]:t}=this;r(e.code!=="ERR_TLS_CERT_ALTNAME_INVALID");if(A[ue]!=="h2"){if(e.code==="ECONNRESET"&&t.statusCode&&!t.shouldKeepAlive){t.onMessageComplete();return}}this[W]=e;onError(this[k],e)}function onError(e,A){if(e[L]===0&&A.code!=="UND_ERR_INFO"&&A.code!=="UND_ERR_SOCKET"){r(e[x]===e[q]);const t=e[H].splice(e[q]);for(let r=0;r0&&t.code!=="UND_ERR_INFO"){const A=e[H][e[q]];e[H][e[q]++]=null;errorRequest(e,A,t)}e[x]=e[q];r(e[L]===0);e.emit("disconnect",e[R],[e],t);resume(e)}async function connect(e){r(!e[O]);r(!e[Z]);let{host:A,hostname:t,protocol:o,port:n}=e[R];if(t[0]==="["){const e=t.indexOf("]");r(e!==-1);const A=t.substring(1,e);r(s.isIP(A));t=A}e[O]=true;if(Te.beforeConnect.hasSubscribers){Te.beforeConnect.publish({connectParams:{host:A,hostname:t,protocol:o,port:n,servername:e[D],localAddress:e[Ee]},connector:e[re]})}try{const s=await new Promise(((r,s)=>{e[re]({host:A,hostname:t,protocol:o,port:n,servername:e[D],localAddress:e[Ee]},((e,A)=>{if(e){s(e)}else{r(A)}}))}));if(e.destroyed){i.destroy(s.on("error",(()=>{})),new y);return}e[O]=false;r(s);const a=s.alpnProtocol==="h2";if(a){if(!ke){ke=true;process.emitWarning("H2 support is experimental, expect them to change at any time.",{code:"UNDICI-H2"})}const A=pe.connect(e[R],{createConnection:()=>s,peerMaxConcurrentStreams:e[he].maxConcurrentStreams});e[ue]="h2";A[k]=e;A[Z]=s;A.on("error",onHttp2SessionError);A.on("frameError",onHttp2FrameError);A.on("end",onHttp2SessionEnd);A.on("goaway",onHTTP2GoAway);A.on("close",onSocketClose);A.unref();e[Ce]=A;s[Ce]=A}else{if(!Ge){Ge=await ve;ve=null}s[P]=false;s[M]=false;s[b]=false;s[N]=false;s[S]=new Parser(e,s,Ge)}s[ne]=0;s[oe]=e[oe];s[k]=e;s[W]=null;s.on("error",onSocketError).on("readable",onSocketReadable).on("end",onSocketEnd).on("close",onSocketClose);e[Z]=s;if(Te.connected.hasSubscribers){Te.connected.publish({connectParams:{host:A,hostname:t,protocol:o,port:n,servername:e[D],localAddress:e[Ee]},connector:e[re],socket:s})}e.emit("connect",e[R],[e])}catch(s){if(e.destroyed){return}e[O]=false;if(Te.connectError.hasSubscribers){Te.connectError.publish({connectParams:{host:A,hostname:t,protocol:o,port:n,servername:e[D],localAddress:e[Ee]},connector:e[re],error:s})}if(s.code==="ERR_TLS_CERT_ALTNAME_INVALID"){r(e[L]===0);while(e[G]>0&&e[H][e[x]].servername===e[D]){const A=e[H][e[x]++];errorRequest(e,A,s)}}else{onError(e,s)}e.emit("connectionError",e[R],[e],s)}resume(e)}function emitDrain(e){e[_]=0;e.emit("drain",e[R],[e])}function resume(e,A){if(e[U]===2){return}e[U]=2;_resume(e,A);e[U]=0;if(e[q]>256){e[H].splice(0,e[q]);e[x]-=e[q];e[q]=0}}function _resume(e,A){while(true){if(e.destroyed){r(e[G]===0);return}if(e[Se]&&!e[v]){e[Se]();e[Se]=null;return}const t=e[Z];if(t&&!t.destroyed&&t.alpnProtocol!=="h2"){if(e[v]===0){if(!t[P]&&t.unref){t.unref();t[P]=true}}else if(t[P]&&t.ref){t.ref();t[P]=false}if(e[v]===0){if(t[S].timeoutType!==Je){t[S].setTimeout(e[X],Je)}}else if(e[L]>0&&t[S].statusCode<200){if(t[S].timeoutType!==_e){const A=e[H][e[q]];const r=A.headersTimeout!=null?A.headersTimeout:e[ee];t[S].setTimeout(r,_e)}}}if(e[F]){e[_]=2}else if(e[_]===2){if(A){e[_]=1;process.nextTick(emitDrain,e)}else{emitDrain(e)}continue}if(e[G]===0){return}if(e[L]>=(e[j]||1)){return}const s=e[H][e[x]];if(e[R].protocol==="https:"&&e[D]!==s.servername){if(e[L]>0){return}e[D]=s.servername;if(t&&t.servername!==s.servername){i.destroy(t,new I("servername changed"));return}}if(e[O]){return}if(!t&&!e[Ce]){connect(e);return}if(t.destroyed||t[M]||t[b]||t[N]){return}if(e[L]>0&&!s.idempotent){return}if(e[L]>0&&(s.upgrade||s.method==="CONNECT")){return}if(e[L]>0&&i.bodyLength(s.body)!==0&&(i.isStream(s.body)||i.isAsyncIterable(s.body))){return}if(!s.aborted&&write(e,s)){e[x]++}else{e[H].splice(e[x],1)}}}function shouldSendContentLength(e){return e!=="GET"&&e!=="HEAD"&&e!=="OPTIONS"&&e!=="TRACE"&&e!=="CONNECT"}function write(e,A){if(e[ue]==="h2"){writeH2(e,e[Ce],A);return}const{body:t,method:s,path:o,host:n,upgrade:a,headers:c,blocking:g,reset:l}=A;const u=s==="PUT"||s==="POST"||s==="PATCH";if(t&&typeof t.read==="function"){t.read(0)}const C=i.bodyLength(t);let h=C;if(h===null){h=A.contentLength}if(h===0&&!u){h=null}if(shouldSendContentLength(s)&&h>0&&A.contentLength!==null&&A.contentLength!==h){if(e[te]){errorRequest(e,A,new E);return false}process.emitWarning(new E)}const B=e[Z];try{A.onConnect((t=>{if(A.aborted||A.completed){return}errorRequest(e,A,t||new Q);i.destroy(B,new I("aborted"))}))}catch(t){errorRequest(e,A,t)}if(A.aborted){return false}if(s==="HEAD"){B[b]=true}if(a||s==="CONNECT"){B[b]=true}if(l!=null){B[b]=l}if(e[oe]&&B[ne]++>=e[oe]){B[b]=true}if(g){B[N]=true}let d=`${s} ${o} HTTP/1.1\r\n`;if(typeof n==="string"){d+=`host: ${n}\r\n`}else{d+=e[V]}if(a){d+=`connection: upgrade\r\nupgrade: ${a}\r\n`}else if(e[j]&&!B[b]){d+="connection: keep-alive\r\n"}else{d+="connection: close\r\n"}if(c){d+=c}if(Te.sendHeaders.hasSubscribers){Te.sendHeaders.publish({request:A,headers:d,socket:B})}if(!t||C===0){if(h===0){B.write(`${d}content-length: 0\r\n\r\n`,"latin1")}else{r(h===null,"no body must not have content length");B.write(`${d}\r\n`,"latin1")}A.onRequestSent()}else if(i.isBuffer(t)){r(h===t.byteLength,"buffer body must have content length");B.cork();B.write(`${d}content-length: ${h}\r\n\r\n`,"latin1");B.write(t);B.uncork();A.onBodySent(t);A.onRequestSent();if(!u){B[b]=true}}else if(i.isBlobLike(t)){if(typeof t.stream==="function"){writeIterable({body:t.stream(),client:e,request:A,socket:B,contentLength:h,header:d,expectsPayload:u})}else{writeBlob({body:t,client:e,request:A,socket:B,contentLength:h,header:d,expectsPayload:u})}}else if(i.isStream(t)){writeStream({body:t,client:e,request:A,socket:B,contentLength:h,header:d,expectsPayload:u})}else if(i.isIterable(t)){writeIterable({body:t,client:e,request:A,socket:B,contentLength:h,header:d,expectsPayload:u})}else{r(false)}return true}function writeH2(e,A,t){const{body:s,method:o,path:n,host:a,upgrade:g,expectContinue:l,signal:u,headers:C}=t;let h;if(typeof C==="string")h=c[Ie](C.trim());else h=C;if(g){errorRequest(e,t,new Error("Upgrade not supported for H2"));return false}try{t.onConnect((A=>{if(t.aborted||t.completed){return}errorRequest(e,t,A||new Q)}))}catch(A){errorRequest(e,t,A)}if(t.aborted){return false}let B;const d=e[he];h[fe]=a||e[Qe];h[me]=o;if(o==="CONNECT"){A.ref();B=A.request(h,{endStream:false,signal:u});if(B.id&&!B.pending){t.onUpgrade(null,null,B);++d.openStreams}else{B.once("ready",(()=>{t.onUpgrade(null,null,B);++d.openStreams}))}B.once("close",(()=>{d.openStreams-=1;if(d.openStreams===0)A.unref()}));return true}h[ye]=n;h[we]="https";const p=o==="PUT"||o==="POST"||o==="PATCH";if(s&&typeof s.read==="function"){s.read(0)}let m=i.bodyLength(s);if(m==null){m=t.contentLength}if(m===0||!p){m=null}if(shouldSendContentLength(o)&&m>0&&t.contentLength!=null&&t.contentLength!==m){if(e[te]){errorRequest(e,t,new E);return false}process.emitWarning(new E)}if(m!=null){r(s,"no body must not have content length");h[Re]=`${m}`}A.ref();const y=o==="GET"||o==="HEAD";if(l){h[be]="100-continue";B=A.request(h,{endStream:y,signal:u});B.once("continue",writeBodyH2)}else{B=A.request(h,{endStream:y,signal:u});writeBodyH2()}++d.openStreams;B.once("response",(e=>{const{[De]:A,...r}=e;if(t.onHeaders(Number(A),r,B.resume.bind(B),"")===false){B.pause()}}));B.once("end",(()=>{t.onComplete([])}));B.on("data",(e=>{if(t.onData(e)===false){B.pause()}}));B.once("close",(()=>{d.openStreams-=1;if(d.openStreams===0){A.unref()}}));B.once("error",(function(A){if(e[Ce]&&!e[Ce].destroyed&&!this.closed&&!this.destroyed){d.streams-=1;i.destroy(B,A)}}));B.once("frameError",((A,r)=>{const s=new I(`HTTP/2: "frameError" received - type ${A}, code ${r}`);errorRequest(e,t,s);if(e[Ce]&&!e[Ce].destroyed&&!this.closed&&!this.destroyed){d.streams-=1;i.destroy(B,s)}}));return true;function writeBodyH2(){if(!s){t.onRequestSent()}else if(i.isBuffer(s)){r(m===s.byteLength,"buffer body must have content length");B.cork();B.write(s);B.uncork();B.end();t.onBodySent(s);t.onRequestSent()}else if(i.isBlobLike(s)){if(typeof s.stream==="function"){writeIterable({client:e,request:t,contentLength:m,h2stream:B,expectsPayload:p,body:s.stream(),socket:e[Z],header:""})}else{writeBlob({body:s,client:e,request:t,contentLength:m,expectsPayload:p,h2stream:B,header:"",socket:e[Z]})}}else if(i.isStream(s)){writeStream({body:s,client:e,request:t,contentLength:m,expectsPayload:p,socket:e[Z],h2stream:B,header:""})}else if(i.isIterable(s)){writeIterable({body:s,client:e,request:t,contentLength:m,expectsPayload:p,header:"",h2stream:B,socket:e[Z]})}else{r(false)}}}function writeStream({h2stream:e,body:A,client:t,request:s,socket:o,contentLength:a,header:c,expectsPayload:g}){r(a!==0||t[L]===0,"stream body cannot be pipelined");if(t[ue]==="h2"){const u=n(A,e,(t=>{if(t){i.destroy(A,t);i.destroy(e,t)}else{s.onRequestSent()}}));u.on("data",onPipeData);u.once("end",(()=>{u.removeListener("data",onPipeData);i.destroy(u)}));function onPipeData(e){s.onBodySent(e)}return}let E=false;const l=new AsyncWriter({socket:o,request:s,contentLength:a,client:t,expectsPayload:g,header:c});const onData=function(e){if(E){return}try{if(!l.write(e)&&this.pause){this.pause()}}catch(e){i.destroy(this,e)}};const onDrain=function(){if(E){return}if(A.resume){A.resume()}};const onAbort=function(){if(E){return}const e=new Q;queueMicrotask((()=>onFinished(e)))};const onFinished=function(e){if(E){return}E=true;r(o.destroyed||o[M]&&t[L]<=1);o.off("drain",onDrain).off("error",onFinished);A.removeListener("data",onData).removeListener("end",onFinished).removeListener("error",onFinished).removeListener("close",onAbort);if(!e){try{l.end()}catch(A){e=A}}l.destroy(e);if(e&&(e.code!=="UND_ERR_INFO"||e.message!=="reset")){i.destroy(A,e)}else{i.destroy(A)}};A.on("data",onData).on("end",onFinished).on("error",onFinished).on("close",onAbort);if(A.resume){A.resume()}o.on("drain",onDrain).on("error",onFinished)}async function writeBlob({h2stream:e,body:A,client:t,request:s,socket:o,contentLength:n,header:a,expectsPayload:c}){r(n===A.size,"blob body must have content length");const g=t[ue]==="h2";try{if(n!=null&&n!==A.size){throw new E}const r=Buffer.from(await A.arrayBuffer());if(g){e.cork();e.write(r);e.uncork()}else{o.cork();o.write(`${a}content-length: ${n}\r\n\r\n`,"latin1");o.write(r);o.uncork()}s.onBodySent(r);s.onRequestSent();if(!c){o[b]=true}resume(t)}catch(A){i.destroy(g?e:o,A)}}async function writeIterable({h2stream:e,body:A,client:t,request:s,socket:o,contentLength:n,header:i,expectsPayload:a}){r(n!==0||t[L]===0,"iterator body cannot be pipelined");let c=null;function onDrain(){if(c){const e=c;c=null;e()}}const waitForDrain=()=>new Promise(((e,A)=>{r(c===null);if(o[W]){A(o[W])}else{c=e}}));if(t[ue]==="h2"){e.on("close",onDrain).on("drain",onDrain);try{for await(const t of A){if(o[W]){throw o[W]}const A=e.write(t);s.onBodySent(t);if(!A){await waitForDrain()}}}catch(A){e.destroy(A)}finally{s.onRequestSent();e.end();e.off("close",onDrain).off("drain",onDrain)}return}o.on("close",onDrain).on("drain",onDrain);const g=new AsyncWriter({socket:o,request:s,contentLength:n,client:t,expectsPayload:a,header:i});try{for await(const e of A){if(o[W]){throw o[W]}if(!g.write(e)){await waitForDrain()}}g.end()}catch(e){g.destroy(e)}finally{o.off("close",onDrain).off("drain",onDrain)}}class AsyncWriter{constructor({socket:e,request:A,contentLength:t,client:r,expectsPayload:s,header:o}){this.socket=e;this.request=A;this.contentLength=t;this.client=r;this.bytesWritten=0;this.expectsPayload=s;this.header=o;e[M]=true}write(e){const{socket:A,request:t,contentLength:r,client:s,bytesWritten:o,expectsPayload:n,header:i}=this;if(A[W]){throw A[W]}if(A.destroyed){return false}const a=Buffer.byteLength(e);if(!a){return true}if(r!==null&&o+a>r){if(s[te]){throw new E}process.emitWarning(new E)}A.cork();if(o===0){if(!n){A[b]=true}if(r===null){A.write(`${i}transfer-encoding: chunked\r\n`,"latin1")}else{A.write(`${i}content-length: ${r}\r\n\r\n`,"latin1")}}if(r===null){A.write(`\r\n${a.toString(16)}\r\n`,"latin1")}this.bytesWritten+=a;const c=A.write(e);A.uncork();t.onBodySent(e);if(!c){if(A[S].timeout&&A[S].timeoutType===_e){if(A[S].timeout.refresh){A[S].timeout.refresh()}}}return c}end(){const{socket:e,contentLength:A,client:t,bytesWritten:r,expectsPayload:s,header:o,request:n}=this;n.onRequestSent();e[M]=false;if(e[W]){throw e[W]}if(e.destroyed){return}if(r===0){if(s){e.write(`${o}content-length: 0\r\n\r\n`,"latin1")}else{e.write(`${o}\r\n`,"latin1")}}else if(A===null){e.write("\r\n0\r\n\r\n","latin1")}if(A!==null&&r!==A){if(t[te]){throw new E}else{process.emitWarning(new E)}}if(e[S].timeout&&e[S].timeoutType===_e){if(e[S].timeout.refresh){e[S].timeout.refresh()}}resume(t)}destroy(e){const{socket:A,client:t}=this;A[M]=false;if(e){r(t[L]<=1,"pipeline should only contain this request");i.destroy(A,e)}}}function errorRequest(e,A,t){try{A.onError(t);r(A.aborted)}catch(t){e.emit("error",t)}}e.exports=Client},4904:(e,A,t)=>{"use strict";const{kConnected:r,kSize:s}=t(7781);class CompatWeakRef{constructor(e){this.value=e}deref(){return this.value[r]===0&&this.value[s]===0?undefined:this.value}}class CompatFinalizer{constructor(e){this.finalizer=e}register(e,A){if(e.on){e.on("disconnect",(()=>{if(e[r]===0&&e[s]===0){this.finalizer(A)}}))}}}e.exports=function(){if(process.env.NODE_V8_COVERAGE){return{WeakRef:CompatWeakRef,FinalizationRegistry:CompatFinalizer}}return{WeakRef:global.WeakRef||CompatWeakRef,FinalizationRegistry:global.FinalizationRegistry||CompatFinalizer}}},9387:e=>{"use strict";const A=1024;const t=4096;e.exports={maxAttributeValueSize:A,maxNameValuePairSize:t}},4346:(e,A,t)=>{"use strict";const{parseSetCookie:r}=t(8249);const{stringify:s,getHeadersList:o}=t(7556);const{webidl:n}=t(6684);const{Headers:i}=t(1815);function getCookies(e){n.argumentLengthCheck(arguments,1,{header:"getCookies"});n.brandCheck(e,i,{strict:false});const A=e.get("cookie");const t={};if(!A){return t}for(const e of A.split(";")){const[A,...r]=e.split("=");t[A.trim()]=r.join("=")}return t}function deleteCookie(e,A,t){n.argumentLengthCheck(arguments,2,{header:"deleteCookie"});n.brandCheck(e,i,{strict:false});A=n.converters.DOMString(A);t=n.converters.DeleteCookieAttributes(t);setCookie(e,{name:A,value:"",expires:new Date(0),...t})}function getSetCookies(e){n.argumentLengthCheck(arguments,1,{header:"getSetCookies"});n.brandCheck(e,i,{strict:false});const A=o(e).cookies;if(!A){return[]}return A.map((e=>r(Array.isArray(e)?e[1]:e)))}function setCookie(e,A){n.argumentLengthCheck(arguments,2,{header:"setCookie"});n.brandCheck(e,i,{strict:false});A=n.converters.Cookie(A);const t=s(A);if(t){e.append("Set-Cookie",s(A))}}n.converters.DeleteCookieAttributes=n.dictionaryConverter([{converter:n.nullableConverter(n.converters.DOMString),key:"path",defaultValue:null},{converter:n.nullableConverter(n.converters.DOMString),key:"domain",defaultValue:null}]);n.converters.Cookie=n.dictionaryConverter([{converter:n.converters.DOMString,key:"name"},{converter:n.converters.DOMString,key:"value"},{converter:n.nullableConverter((e=>{if(typeof e==="number"){return n.converters["unsigned long long"](e)}return new Date(e)})),key:"expires",defaultValue:null},{converter:n.nullableConverter(n.converters["long long"]),key:"maxAge",defaultValue:null},{converter:n.nullableConverter(n.converters.DOMString),key:"domain",defaultValue:null},{converter:n.nullableConverter(n.converters.DOMString),key:"path",defaultValue:null},{converter:n.nullableConverter(n.converters.boolean),key:"secure",defaultValue:null},{converter:n.nullableConverter(n.converters.boolean),key:"httpOnly",defaultValue:null},{converter:n.converters.USVString,key:"sameSite",allowedValues:["Strict","Lax","None"]},{converter:n.sequenceConverter(n.converters.DOMString),key:"unparsed",defaultValue:[]}]);e.exports={getCookies:getCookies,deleteCookie:deleteCookie,getSetCookies:getSetCookies,setCookie:setCookie}},8249:(e,A,t)=>{"use strict";const{maxNameValuePairSize:r,maxAttributeValueSize:s}=t(9387);const{isCTLExcludingHtab:o}=t(7556);const{collectASequenceOfCodePointsFast:n}=t(7160);const i=t(2613);function parseSetCookie(e){if(o(e)){return null}let A="";let t="";let s="";let i="";if(e.includes(";")){const r={position:0};A=n(";",e,r);t=e.slice(r.position)}else{A=e}if(!A.includes("=")){i=A}else{const e={position:0};s=n("=",A,e);i=A.slice(e.position+1)}s=s.trim();i=i.trim();if(s.length+i.length>r){return null}return{name:s,value:i,...parseUnparsedAttributes(t)}}function parseUnparsedAttributes(e,A={}){if(e.length===0){return A}i(e[0]===";");e=e.slice(1);let t="";if(e.includes(";")){t=n(";",e,{position:0});e=e.slice(t.length)}else{t=e;e=""}let r="";let o="";if(t.includes("=")){const e={position:0};r=n("=",t,e);o=t.slice(e.position+1)}else{r=t}r=r.trim();o=o.trim();if(o.length>s){return parseUnparsedAttributes(e,A)}const a=r.toLowerCase();if(a==="expires"){const e=new Date(o);A.expires=e}else if(a==="max-age"){const t=o.charCodeAt(0);if((t<48||t>57)&&o[0]!=="-"){return parseUnparsedAttributes(e,A)}if(!/^\d+$/.test(o)){return parseUnparsedAttributes(e,A)}const r=Number(o);A.maxAge=r}else if(a==="domain"){let e=o;if(e[0]==="."){e=e.slice(1)}e=e.toLowerCase();A.domain=e}else if(a==="path"){let e="";if(o.length===0||o[0]!=="/"){e="/"}else{e=o}A.path=e}else if(a==="secure"){A.secure=true}else if(a==="httponly"){A.httpOnly=true}else if(a==="samesite"){let e="Default";const t=o.toLowerCase();if(t.includes("none")){e="None"}if(t.includes("strict")){e="Strict"}if(t.includes("lax")){e="Lax"}A.sameSite=e}else{A.unparsed??=[];A.unparsed.push(`${r}=${o}`)}return parseUnparsedAttributes(e,A)}e.exports={parseSetCookie:parseSetCookie,parseUnparsedAttributes:parseUnparsedAttributes}},7556:(e,A,t)=>{"use strict";const r=t(2613);const{kHeadersList:s}=t(7781);function isCTLExcludingHtab(e){if(e.length===0){return false}for(const A of e){const e=A.charCodeAt(0);if(e>=0||e<=8||(e>=10||e<=31)||e===127){return false}}}function validateCookieName(e){for(const A of e){const e=A.charCodeAt(0);if(e<=32||e>127||A==="("||A===")"||A===">"||A==="<"||A==="@"||A===","||A===";"||A===":"||A==="\\"||A==='"'||A==="/"||A==="["||A==="]"||A==="?"||A==="="||A==="{"||A==="}"){throw new Error("Invalid cookie name")}}}function validateCookieValue(e){for(const A of e){const e=A.charCodeAt(0);if(e<33||e===34||e===44||e===59||e===92||e>126){throw new Error("Invalid header value")}}}function validateCookiePath(e){for(const A of e){const e=A.charCodeAt(0);if(e<33||A===";"){throw new Error("Invalid cookie path")}}}function validateCookieDomain(e){if(e.startsWith("-")||e.endsWith(".")||e.endsWith("-")){throw new Error("Invalid cookie domain")}}function toIMFDate(e){if(typeof e==="number"){e=new Date(e)}const A=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"];const t=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];const r=A[e.getUTCDay()];const s=e.getUTCDate().toString().padStart(2,"0");const o=t[e.getUTCMonth()];const n=e.getUTCFullYear();const i=e.getUTCHours().toString().padStart(2,"0");const a=e.getUTCMinutes().toString().padStart(2,"0");const c=e.getUTCSeconds().toString().padStart(2,"0");return`${r}, ${s} ${o} ${n} ${i}:${a}:${c} GMT`}function validateCookieMaxAge(e){if(e<0){throw new Error("Invalid cookie max-age")}}function stringify(e){if(e.name.length===0){return null}validateCookieName(e.name);validateCookieValue(e.value);const A=[`${e.name}=${e.value}`];if(e.name.startsWith("__Secure-")){e.secure=true}if(e.name.startsWith("__Host-")){e.secure=true;e.domain=null;e.path="/"}if(e.secure){A.push("Secure")}if(e.httpOnly){A.push("HttpOnly")}if(typeof e.maxAge==="number"){validateCookieMaxAge(e.maxAge);A.push(`Max-Age=${e.maxAge}`)}if(e.domain){validateCookieDomain(e.domain);A.push(`Domain=${e.domain}`)}if(e.path){validateCookiePath(e.path);A.push(`Path=${e.path}`)}if(e.expires&&e.expires.toString()!=="Invalid Date"){A.push(`Expires=${toIMFDate(e.expires)}`)}if(e.sameSite){A.push(`SameSite=${e.sameSite}`)}for(const t of e.unparsed){if(!t.includes("=")){throw new Error("Invalid unparsed")}const[e,...r]=t.split("=");A.push(`${e.trim()}=${r.join("=")}`)}return A.join("; ")}let o;function getHeadersList(e){if(e[s]){return e[s]}if(!o){o=Object.getOwnPropertySymbols(e).find((e=>e.description==="headers list"));r(o,"Headers cannot be parsed")}const A=e[o];r(A);return A}e.exports={isCTLExcludingHtab:isCTLExcludingHtab,stringify:stringify,getHeadersList:getHeadersList}},4470:(e,A,t)=>{"use strict";const r=t(9278);const s=t(2613);const o=t(2806);const{InvalidArgumentError:n,ConnectTimeoutError:i}=t(7221);let a;let c;if(global.FinalizationRegistry&&!process.env.NODE_V8_COVERAGE){c=class WeakSessionCache{constructor(e){this._maxCachedSessions=e;this._sessionCache=new Map;this._sessionRegistry=new global.FinalizationRegistry((e=>{if(this._sessionCache.size=this._maxCachedSessions){const{value:e}=this._sessionCache.keys().next();this._sessionCache.delete(e)}this._sessionCache.set(e,A)}}}function buildConnector({allowH2:e,maxCachedSessions:A,socketPath:i,timeout:g,...E}){if(A!=null&&(!Number.isInteger(A)||A<0)){throw new n("maxCachedSessions must be a positive integer or zero")}const l={path:i,...E};const u=new c(A==null?100:A);g=g==null?1e4:g;e=e!=null?e:false;return function connect({hostname:A,host:n,protocol:i,port:c,servername:E,localAddress:Q,httpSocket:C},h){let B;if(i==="https:"){if(!a){a=t(4756)}E=E||l.servername||o.getServerName(n)||null;const r=E||A;const i=u.get(r)||null;s(r);B=a.connect({highWaterMark:16384,...l,servername:E,session:i,localAddress:Q,ALPNProtocols:e?["http/1.1","h2"]:["http/1.1"],socket:C,port:c||443,host:A});B.on("session",(function(e){u.set(r,e)}))}else{s(!C,"httpSocket can only be sent on TLS update");B=r.connect({highWaterMark:64*1024,...l,localAddress:Q,port:c||80,host:A})}if(l.keepAlive==null||l.keepAlive){const e=l.keepAliveInitialDelay===undefined?6e4:l.keepAliveInitialDelay;B.setKeepAlive(true,e)}const I=setupTimeout((()=>onConnectTimeout(B)),g);B.setNoDelay(true).once(i==="https:"?"secureConnect":"connect",(function(){I();if(h){const e=h;h=null;e(null,this)}})).on("error",(function(e){I();if(h){const A=h;h=null;A(e)}}));return B}}function setupTimeout(e,A){if(!A){return()=>{}}let t=null;let r=null;const s=setTimeout((()=>{t=setImmediate((()=>{if(process.platform==="win32"){r=setImmediate((()=>e()))}else{e()}}))}),A);return()=>{clearTimeout(s);clearImmediate(t);clearImmediate(r)}}function onConnectTimeout(e){o.destroy(e,new i)}e.exports=buildConnector},4457:e=>{"use strict";const A={};const t=["Accept","Accept-Encoding","Accept-Language","Accept-Ranges","Access-Control-Allow-Credentials","Access-Control-Allow-Headers","Access-Control-Allow-Methods","Access-Control-Allow-Origin","Access-Control-Expose-Headers","Access-Control-Max-Age","Access-Control-Request-Headers","Access-Control-Request-Method","Age","Allow","Alt-Svc","Alt-Used","Authorization","Cache-Control","Clear-Site-Data","Connection","Content-Disposition","Content-Encoding","Content-Language","Content-Length","Content-Location","Content-Range","Content-Security-Policy","Content-Security-Policy-Report-Only","Content-Type","Cookie","Cross-Origin-Embedder-Policy","Cross-Origin-Opener-Policy","Cross-Origin-Resource-Policy","Date","Device-Memory","Downlink","ECT","ETag","Expect","Expect-CT","Expires","Forwarded","From","Host","If-Match","If-Modified-Since","If-None-Match","If-Range","If-Unmodified-Since","Keep-Alive","Last-Modified","Link","Location","Max-Forwards","Origin","Permissions-Policy","Pragma","Proxy-Authenticate","Proxy-Authorization","RTT","Range","Referer","Referrer-Policy","Refresh","Retry-After","Sec-WebSocket-Accept","Sec-WebSocket-Extensions","Sec-WebSocket-Key","Sec-WebSocket-Protocol","Sec-WebSocket-Version","Server","Server-Timing","Service-Worker-Allowed","Service-Worker-Navigation-Preload","Set-Cookie","SourceMap","Strict-Transport-Security","Supports-Loading-Mode","TE","Timing-Allow-Origin","Trailer","Transfer-Encoding","Upgrade","Upgrade-Insecure-Requests","User-Agent","Vary","Via","WWW-Authenticate","X-Content-Type-Options","X-DNS-Prefetch-Control","X-Frame-Options","X-Permitted-Cross-Domain-Policies","X-Powered-By","X-Requested-With","X-XSS-Protection"];for(let e=0;e{"use strict";class UndiciError extends Error{constructor(e){super(e);this.name="UndiciError";this.code="UND_ERR"}}class ConnectTimeoutError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,ConnectTimeoutError);this.name="ConnectTimeoutError";this.message=e||"Connect Timeout Error";this.code="UND_ERR_CONNECT_TIMEOUT"}}class HeadersTimeoutError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,HeadersTimeoutError);this.name="HeadersTimeoutError";this.message=e||"Headers Timeout Error";this.code="UND_ERR_HEADERS_TIMEOUT"}}class HeadersOverflowError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,HeadersOverflowError);this.name="HeadersOverflowError";this.message=e||"Headers Overflow Error";this.code="UND_ERR_HEADERS_OVERFLOW"}}class BodyTimeoutError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,BodyTimeoutError);this.name="BodyTimeoutError";this.message=e||"Body Timeout Error";this.code="UND_ERR_BODY_TIMEOUT"}}class ResponseStatusCodeError extends UndiciError{constructor(e,A,t,r){super(e);Error.captureStackTrace(this,ResponseStatusCodeError);this.name="ResponseStatusCodeError";this.message=e||"Response Status Code Error";this.code="UND_ERR_RESPONSE_STATUS_CODE";this.body=r;this.status=A;this.statusCode=A;this.headers=t}}class InvalidArgumentError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,InvalidArgumentError);this.name="InvalidArgumentError";this.message=e||"Invalid Argument Error";this.code="UND_ERR_INVALID_ARG"}}class InvalidReturnValueError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,InvalidReturnValueError);this.name="InvalidReturnValueError";this.message=e||"Invalid Return Value Error";this.code="UND_ERR_INVALID_RETURN_VALUE"}}class RequestAbortedError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,RequestAbortedError);this.name="AbortError";this.message=e||"Request aborted";this.code="UND_ERR_ABORTED"}}class InformationalError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,InformationalError);this.name="InformationalError";this.message=e||"Request information";this.code="UND_ERR_INFO"}}class RequestContentLengthMismatchError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,RequestContentLengthMismatchError);this.name="RequestContentLengthMismatchError";this.message=e||"Request body length does not match content-length header";this.code="UND_ERR_REQ_CONTENT_LENGTH_MISMATCH"}}class ResponseContentLengthMismatchError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,ResponseContentLengthMismatchError);this.name="ResponseContentLengthMismatchError";this.message=e||"Response body length does not match content-length header";this.code="UND_ERR_RES_CONTENT_LENGTH_MISMATCH"}}class ClientDestroyedError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,ClientDestroyedError);this.name="ClientDestroyedError";this.message=e||"The client is destroyed";this.code="UND_ERR_DESTROYED"}}class ClientClosedError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,ClientClosedError);this.name="ClientClosedError";this.message=e||"The client is closed";this.code="UND_ERR_CLOSED"}}class SocketError extends UndiciError{constructor(e,A){super(e);Error.captureStackTrace(this,SocketError);this.name="SocketError";this.message=e||"Socket error";this.code="UND_ERR_SOCKET";this.socket=A}}class NotSupportedError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,NotSupportedError);this.name="NotSupportedError";this.message=e||"Not supported error";this.code="UND_ERR_NOT_SUPPORTED"}}class BalancedPoolMissingUpstreamError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,NotSupportedError);this.name="MissingUpstreamError";this.message=e||"No upstream has been added to the BalancedPool";this.code="UND_ERR_BPL_MISSING_UPSTREAM"}}class HTTPParserError extends Error{constructor(e,A,t){super(e);Error.captureStackTrace(this,HTTPParserError);this.name="HTTPParserError";this.code=A?`HPE_${A}`:undefined;this.data=t?t.toString():undefined}}class ResponseExceededMaxSizeError extends UndiciError{constructor(e){super(e);Error.captureStackTrace(this,ResponseExceededMaxSizeError);this.name="ResponseExceededMaxSizeError";this.message=e||"Response content exceeded max size";this.code="UND_ERR_RES_EXCEEDED_MAX_SIZE"}}class RequestRetryError extends UndiciError{constructor(e,A,{headers:t,data:r}){super(e);Error.captureStackTrace(this,RequestRetryError);this.name="RequestRetryError";this.message=e||"Request retry error";this.code="UND_ERR_REQ_RETRY";this.statusCode=A;this.data=r;this.headers=t}}e.exports={HTTPParserError:HTTPParserError,UndiciError:UndiciError,HeadersTimeoutError:HeadersTimeoutError,HeadersOverflowError:HeadersOverflowError,BodyTimeoutError:BodyTimeoutError,RequestContentLengthMismatchError:RequestContentLengthMismatchError,ConnectTimeoutError:ConnectTimeoutError,ResponseStatusCodeError:ResponseStatusCodeError,InvalidArgumentError:InvalidArgumentError,InvalidReturnValueError:InvalidReturnValueError,RequestAbortedError:RequestAbortedError,ClientDestroyedError:ClientDestroyedError,ClientClosedError:ClientClosedError,InformationalError:InformationalError,SocketError:SocketError,NotSupportedError:NotSupportedError,ResponseContentLengthMismatchError:ResponseContentLengthMismatchError,BalancedPoolMissingUpstreamError:BalancedPoolMissingUpstreamError,ResponseExceededMaxSizeError:ResponseExceededMaxSizeError,RequestRetryError:RequestRetryError}},1289:(e,A,t)=>{"use strict";const{InvalidArgumentError:r,NotSupportedError:s}=t(7221);const o=t(2613);const{kHTTP2BuildRequest:n,kHTTP2CopyHeaders:i,kHTTP1BuildRequest:a}=t(7781);const c=t(2806);const g=/^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/;const E=/[^\t\x20-\x7e\x80-\xff]/;const l=/[^\u0021-\u00ff]/;const u=Symbol("handler");const Q={};let C;try{const e=t(1637);Q.create=e.channel("undici:request:create");Q.bodySent=e.channel("undici:request:bodySent");Q.headers=e.channel("undici:request:headers");Q.trailers=e.channel("undici:request:trailers");Q.error=e.channel("undici:request:error")}catch{Q.create={hasSubscribers:false};Q.bodySent={hasSubscribers:false};Q.headers={hasSubscribers:false};Q.trailers={hasSubscribers:false};Q.error={hasSubscribers:false}}class Request{constructor(e,{path:A,method:s,body:o,headers:n,query:i,idempotent:a,blocking:E,upgrade:h,headersTimeout:B,bodyTimeout:I,reset:d,throwOnError:p,expectContinue:m},y){if(typeof A!=="string"){throw new r("path must be a string")}else if(A[0]!=="/"&&!(A.startsWith("http://")||A.startsWith("https://"))&&s!=="CONNECT"){throw new r("path must be an absolute URL or start with a slash")}else if(l.exec(A)!==null){throw new r("invalid request path")}if(typeof s!=="string"){throw new r("method must be a string")}else if(g.exec(s)===null){throw new r("invalid request method")}if(h&&typeof h!=="string"){throw new r("upgrade must be a string")}if(B!=null&&(!Number.isFinite(B)||B<0)){throw new r("invalid headersTimeout")}if(I!=null&&(!Number.isFinite(I)||I<0)){throw new r("invalid bodyTimeout")}if(d!=null&&typeof d!=="boolean"){throw new r("invalid reset")}if(m!=null&&typeof m!=="boolean"){throw new r("invalid expectContinue")}this.headersTimeout=B;this.bodyTimeout=I;this.throwOnError=p===true;this.method=s;this.abort=null;if(o==null){this.body=null}else if(c.isStream(o)){this.body=o;const e=this.body._readableState;if(!e||!e.autoDestroy){this.endHandler=function autoDestroy(){c.destroy(this)};this.body.on("end",this.endHandler)}this.errorHandler=e=>{if(this.abort){this.abort(e)}else{this.error=e}};this.body.on("error",this.errorHandler)}else if(c.isBuffer(o)){this.body=o.byteLength?o:null}else if(ArrayBuffer.isView(o)){this.body=o.buffer.byteLength?Buffer.from(o.buffer,o.byteOffset,o.byteLength):null}else if(o instanceof ArrayBuffer){this.body=o.byteLength?Buffer.from(o):null}else if(typeof o==="string"){this.body=o.length?Buffer.from(o):null}else if(c.isFormDataLike(o)||c.isIterable(o)||c.isBlobLike(o)){this.body=o}else{throw new r("body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable")}this.completed=false;this.aborted=false;this.upgrade=h||null;this.path=i?c.buildURL(A,i):A;this.origin=e;this.idempotent=a==null?s==="HEAD"||s==="GET":a;this.blocking=E==null?false:E;this.reset=d==null?null:d;this.host=null;this.contentLength=null;this.contentType=null;this.headers="";this.expectContinue=m!=null?m:false;if(Array.isArray(n)){if(n.length%2!==0){throw new r("headers array must be even")}for(let e=0;e{e.exports={kClose:Symbol("close"),kDestroy:Symbol("destroy"),kDispatch:Symbol("dispatch"),kUrl:Symbol("url"),kWriting:Symbol("writing"),kResuming:Symbol("resuming"),kQueue:Symbol("queue"),kConnect:Symbol("connect"),kConnecting:Symbol("connecting"),kHeadersList:Symbol("headers list"),kKeepAliveDefaultTimeout:Symbol("default keep alive timeout"),kKeepAliveMaxTimeout:Symbol("max keep alive timeout"),kKeepAliveTimeoutThreshold:Symbol("keep alive timeout threshold"),kKeepAliveTimeoutValue:Symbol("keep alive timeout"),kKeepAlive:Symbol("keep alive"),kHeadersTimeout:Symbol("headers timeout"),kBodyTimeout:Symbol("body timeout"),kServerName:Symbol("server name"),kLocalAddress:Symbol("local address"),kHost:Symbol("host"),kNoRef:Symbol("no ref"),kBodyUsed:Symbol("used"),kRunning:Symbol("running"),kBlocking:Symbol("blocking"),kPending:Symbol("pending"),kSize:Symbol("size"),kBusy:Symbol("busy"),kQueued:Symbol("queued"),kFree:Symbol("free"),kConnected:Symbol("connected"),kClosed:Symbol("closed"),kNeedDrain:Symbol("need drain"),kReset:Symbol("reset"),kDestroyed:Symbol.for("nodejs.stream.destroyed"),kMaxHeadersSize:Symbol("max headers size"),kRunningIdx:Symbol("running index"),kPendingIdx:Symbol("pending index"),kError:Symbol("error"),kClients:Symbol("clients"),kClient:Symbol("client"),kParser:Symbol("parser"),kOnDestroyed:Symbol("destroy callbacks"),kPipelining:Symbol("pipelining"),kSocket:Symbol("socket"),kHostHeader:Symbol("host header"),kConnector:Symbol("connector"),kStrictContentLength:Symbol("strict content length"),kMaxRedirections:Symbol("maxRedirections"),kMaxRequests:Symbol("maxRequestsPerClient"),kProxy:Symbol("proxy agent options"),kCounter:Symbol("socket request counter"),kInterceptors:Symbol("dispatch interceptors"),kMaxResponseSize:Symbol("max response size"),kHTTP2Session:Symbol("http2Session"),kHTTP2SessionState:Symbol("http2Session state"),kHTTP2BuildRequest:Symbol("http2 build request"),kHTTP1BuildRequest:Symbol("http1 build request"),kHTTP2CopyHeaders:Symbol("http2 copy headers"),kHTTPConnVersion:Symbol("http connection version"),kRetryHandlerDefaultRetry:Symbol("retry agent default retry"),kConstruct:Symbol("constructable")}},2806:(e,A,t)=>{"use strict";const r=t(2613);const{kDestroyed:s,kBodyUsed:o}=t(7781);const{IncomingMessage:n}=t(8611);const i=t(2203);const a=t(9278);const{InvalidArgumentError:c}=t(7221);const{Blob:g}=t(181);const E=t(9023);const{stringify:l}=t(3480);const{headerNameLowerCasedRecord:u}=t(4457);const[Q,C]=process.versions.node.split(".").map((e=>Number(e)));function nop(){}function isStream(e){return e&&typeof e==="object"&&typeof e.pipe==="function"&&typeof e.on==="function"}function isBlobLike(e){return g&&e instanceof g||e&&typeof e==="object"&&(typeof e.stream==="function"||typeof e.arrayBuffer==="function")&&/^(Blob|File)$/.test(e[Symbol.toStringTag])}function buildURL(e,A){if(e.includes("?")||e.includes("#")){throw new Error('Query params cannot be passed when url already contains "?" or "#".')}const t=l(A);if(t){e+="?"+t}return e}function parseURL(e){if(typeof e==="string"){e=new URL(e);if(!/^https?:/.test(e.origin||e.protocol)){throw new c("Invalid URL protocol: the URL must start with `http:` or `https:`.")}return e}if(!e||typeof e!=="object"){throw new c("Invalid URL: The URL argument must be a non-null object.")}if(!/^https?:/.test(e.origin||e.protocol)){throw new c("Invalid URL protocol: the URL must start with `http:` or `https:`.")}if(!(e instanceof URL)){if(e.port!=null&&e.port!==""&&!Number.isFinite(parseInt(e.port))){throw new c("Invalid URL: port must be a valid integer or a string representation of an integer.")}if(e.path!=null&&typeof e.path!=="string"){throw new c("Invalid URL path: the path must be a string or null/undefined.")}if(e.pathname!=null&&typeof e.pathname!=="string"){throw new c("Invalid URL pathname: the pathname must be a string or null/undefined.")}if(e.hostname!=null&&typeof e.hostname!=="string"){throw new c("Invalid URL hostname: the hostname must be a string or null/undefined.")}if(e.origin!=null&&typeof e.origin!=="string"){throw new c("Invalid URL origin: the origin must be a string or null/undefined.")}const A=e.port!=null?e.port:e.protocol==="https:"?443:80;let t=e.origin!=null?e.origin:`${e.protocol}//${e.hostname}:${A}`;let r=e.path!=null?e.path:`${e.pathname||""}${e.search||""}`;if(t.endsWith("/")){t=t.substring(0,t.length-1)}if(r&&!r.startsWith("/")){r=`/${r}`}e=new URL(t+r)}return e}function parseOrigin(e){e=parseURL(e);if(e.pathname!=="/"||e.search||e.hash){throw new c("invalid url")}return e}function getHostname(e){if(e[0]==="["){const A=e.indexOf("]");r(A!==-1);return e.substring(1,A)}const A=e.indexOf(":");if(A===-1)return e;return e.substring(0,A)}function getServerName(e){if(!e){return null}r.strictEqual(typeof e,"string");const A=getHostname(e);if(a.isIP(A)){return""}return A}function deepClone(e){return JSON.parse(JSON.stringify(e))}function isAsyncIterable(e){return!!(e!=null&&typeof e[Symbol.asyncIterator]==="function")}function isIterable(e){return!!(e!=null&&(typeof e[Symbol.iterator]==="function"||typeof e[Symbol.asyncIterator]==="function"))}function bodyLength(e){if(e==null){return 0}else if(isStream(e)){const A=e._readableState;return A&&A.objectMode===false&&A.ended===true&&Number.isFinite(A.length)?A.length:null}else if(isBlobLike(e)){return e.size!=null?e.size:null}else if(isBuffer(e)){return e.byteLength}return null}function isDestroyed(e){return!e||!!(e.destroyed||e[s])}function isReadableAborted(e){const A=e&&e._readableState;return isDestroyed(e)&&A&&!A.endEmitted}function destroy(e,A){if(e==null||!isStream(e)||isDestroyed(e)){return}if(typeof e.destroy==="function"){if(Object.getPrototypeOf(e).constructor===n){e.socket=null}e.destroy(A)}else if(A){process.nextTick(((e,A)=>{e.emit("error",A)}),e,A)}if(e.destroyed!==true){e[s]=true}}const h=/timeout=(\d+)/;function parseKeepAliveTimeout(e){const A=e.toString().match(h);return A?parseInt(A[1],10)*1e3:null}function headerNameToString(e){return u[e]||e.toLowerCase()}function parseHeaders(e,A={}){if(!Array.isArray(e))return e;for(let t=0;te.toString("utf8")))}else{A[r]=e[t+1].toString("utf8")}}else{if(!Array.isArray(s)){s=[s];A[r]=s}s.push(e[t+1].toString("utf8"))}}if("content-length"in A&&"content-disposition"in A){A["content-disposition"]=Buffer.from(A["content-disposition"]).toString("latin1")}return A}function parseRawHeaders(e){const A=[];let t=false;let r=-1;for(let s=0;s{e.close()}))}else{const A=Buffer.isBuffer(r)?r:Buffer.from(r);e.enqueue(new Uint8Array(A))}return e.desiredSize>0},async cancel(e){await A.return()}},0)}function isFormDataLike(e){return e&&typeof e==="object"&&typeof e.append==="function"&&typeof e.delete==="function"&&typeof e.get==="function"&&typeof e.getAll==="function"&&typeof e.has==="function"&&typeof e.set==="function"&&e[Symbol.toStringTag]==="FormData"}function throwIfAborted(e){if(!e){return}if(typeof e.throwIfAborted==="function"){e.throwIfAborted()}else{if(e.aborted){const e=new Error("The operation was aborted");e.name="AbortError";throw e}}}function addAbortListener(e,A){if("addEventListener"in e){e.addEventListener("abort",A,{once:true});return()=>e.removeEventListener("abort",A)}e.addListener("abort",A);return()=>e.removeListener("abort",A)}const I=!!String.prototype.toWellFormed;function toUSVString(e){if(I){return`${e}`.toWellFormed()}else if(E.toUSVString){return E.toUSVString(e)}return`${e}`}function parseRangeHeader(e){if(e==null||e==="")return{start:0,end:null,size:null};const A=e?e.match(/^bytes (\d+)-(\d+)\/(\d+)?$/):null;return A?{start:parseInt(A[1]),end:A[2]?parseInt(A[2]):null,size:A[3]?parseInt(A[3]):null}:null}const d=Object.create(null);d.enumerable=true;e.exports={kEnumerableProperty:d,nop:nop,isDisturbed:isDisturbed,isErrored:isErrored,isReadable:isReadable,toUSVString:toUSVString,isReadableAborted:isReadableAborted,isBlobLike:isBlobLike,parseOrigin:parseOrigin,parseURL:parseURL,getServerName:getServerName,isStream:isStream,isIterable:isIterable,isAsyncIterable:isAsyncIterable,isDestroyed:isDestroyed,headerNameToString:headerNameToString,parseRawHeaders:parseRawHeaders,parseHeaders:parseHeaders,parseKeepAliveTimeout:parseKeepAliveTimeout,destroy:destroy,bodyLength:bodyLength,deepClone:deepClone,ReadableStreamFrom:ReadableStreamFrom,isBuffer:isBuffer,validateHandler:validateHandler,getSocketInfo:getSocketInfo,isFormDataLike:isFormDataLike,buildURL:buildURL,throwIfAborted:throwIfAborted,addAbortListener:addAbortListener,parseRangeHeader:parseRangeHeader,nodeMajor:Q,nodeMinor:C,nodeHasAutoSelectFamily:Q>18||Q===18&&C>=13,safeHTTPMethods:["GET","HEAD","OPTIONS","TRACE"]}},6915:(e,A,t)=>{"use strict";const r=t(8841);const{ClientDestroyedError:s,ClientClosedError:o,InvalidArgumentError:n}=t(7221);const{kDestroy:i,kClose:a,kDispatch:c,kInterceptors:g}=t(7781);const E=Symbol("destroyed");const l=Symbol("closed");const u=Symbol("onDestroyed");const Q=Symbol("onClosed");const C=Symbol("Intercepted Dispatch");class DispatcherBase extends r{constructor(){super();this[E]=false;this[u]=null;this[l]=false;this[Q]=[]}get destroyed(){return this[E]}get closed(){return this[l]}get interceptors(){return this[g]}set interceptors(e){if(e){for(let A=e.length-1;A>=0;A--){const e=this[g][A];if(typeof e!=="function"){throw new n("interceptor must be an function")}}}this[g]=e}close(e){if(e===undefined){return new Promise(((e,A)=>{this.close(((t,r)=>t?A(t):e(r)))}))}if(typeof e!=="function"){throw new n("invalid callback")}if(this[E]){queueMicrotask((()=>e(new s,null)));return}if(this[l]){if(this[Q]){this[Q].push(e)}else{queueMicrotask((()=>e(null,null)))}return}this[l]=true;this[Q].push(e);const onClosed=()=>{const e=this[Q];this[Q]=null;for(let A=0;Athis.destroy())).then((()=>{queueMicrotask(onClosed)}))}destroy(e,A){if(typeof e==="function"){A=e;e=null}if(A===undefined){return new Promise(((A,t)=>{this.destroy(e,((e,r)=>e?t(e):A(r)))}))}if(typeof A!=="function"){throw new n("invalid callback")}if(this[E]){if(this[u]){this[u].push(A)}else{queueMicrotask((()=>A(null,null)))}return}if(!e){e=new s}this[E]=true;this[u]=this[u]||[];this[u].push(A);const onDestroyed=()=>{const e=this[u];this[u]=null;for(let A=0;A{queueMicrotask(onDestroyed)}))}[C](e,A){if(!this[g]||this[g].length===0){this[C]=this[c];return this[c](e,A)}let t=this[c].bind(this);for(let e=this[g].length-1;e>=0;e--){t=this[g][e](t)}this[C]=t;return t(e,A)}dispatch(e,A){if(!A||typeof A!=="object"){throw new n("handler must be an object")}try{if(!e||typeof e!=="object"){throw new n("opts must be an object.")}if(this[E]||this[u]){throw new s}if(this[l]){throw new o}return this[C](e,A)}catch(e){if(typeof A.onError!=="function"){throw new n("invalid onError method")}A.onError(e);return false}}}e.exports=DispatcherBase},8841:(e,A,t)=>{"use strict";const r=t(4434);class Dispatcher extends r{dispatch(){throw new Error("not implemented")}close(){throw new Error("not implemented")}destroy(){throw new Error("not implemented")}}e.exports=Dispatcher},8689:(e,A,t)=>{"use strict";const r=t(1597);const s=t(2806);const{ReadableStreamFrom:o,isBlobLike:n,isReadableStreamLike:i,readableStreamClose:a,createDeferredPromise:c,fullyReadBody:g}=t(9913);const{FormData:E}=t(1499);const{kState:l}=t(648);const{webidl:u}=t(6684);const{DOMException:Q,structuredClone:C}=t(6040);const{Blob:h,File:B}=t(181);const{kBodyUsed:I}=t(7781);const d=t(2613);const{isErrored:p}=t(2806);const{isUint8Array:m,isArrayBuffer:y}=t(8253);const{File:w}=t(4019);const{parseMIMEType:R,serializeAMimeType:b}=t(7160);let D=globalThis.ReadableStream;const k=B??w;const F=new TextEncoder;const S=new TextDecoder;function extractBody(e,A=false){if(!D){D=t(3774).ReadableStream}let r=null;if(e instanceof D){r=e}else if(n(e)){r=e.stream()}else{r=new D({async pull(e){e.enqueue(typeof g==="string"?F.encode(g):g);queueMicrotask((()=>a(e)))},start(){},type:undefined})}d(i(r));let c=null;let g=null;let E=null;let l=null;if(typeof e==="string"){g=e;l="text/plain;charset=UTF-8"}else if(e instanceof URLSearchParams){g=e.toString();l="application/x-www-form-urlencoded;charset=UTF-8"}else if(y(e)){g=new Uint8Array(e.slice())}else if(ArrayBuffer.isView(e)){g=new Uint8Array(e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength))}else if(s.isFormDataLike(e)){const A=`----formdata-undici-0${`${Math.floor(Math.random()*1e11)}`.padStart(11,"0")}`;const t=`--${A}\r\nContent-Disposition: form-data` +/*! formdata-polyfill. MIT License. Jimmy Wärting */;const escape=e=>e.replace(/\n/g,"%0A").replace(/\r/g,"%0D").replace(/"/g,"%22");const normalizeLinefeeds=e=>e.replace(/\r?\n|\r/g,"\r\n");const r=[];const s=new Uint8Array([13,10]);E=0;let o=false;for(const[A,n]of e){if(typeof n==="string"){const e=F.encode(t+`; name="${escape(normalizeLinefeeds(A))}"`+`\r\n\r\n${normalizeLinefeeds(n)}\r\n`);r.push(e);E+=e.byteLength}else{const e=F.encode(`${t}; name="${escape(normalizeLinefeeds(A))}"`+(n.name?`; filename="${escape(n.name)}"`:"")+"\r\n"+`Content-Type: ${n.type||"application/octet-stream"}\r\n\r\n`);r.push(e,n,s);if(typeof n.size==="number"){E+=e.byteLength+n.size+s.byteLength}else{o=true}}}const n=F.encode(`--${A}--`);r.push(n);E+=n.byteLength;if(o){E=null}g=e;c=async function*(){for(const e of r){if(e.stream){yield*e.stream()}else{yield e}}};l="multipart/form-data; boundary="+A}else if(n(e)){g=e;E=e.size;if(e.type){l=e.type}}else if(typeof e[Symbol.asyncIterator]==="function"){if(A){throw new TypeError("keepalive")}if(s.isDisturbed(e)||e.locked){throw new TypeError("Response body object should not be disturbed or locked")}r=e instanceof D?e:o(e)}if(typeof g==="string"||s.isBuffer(g)){E=Buffer.byteLength(g)}if(c!=null){let A;r=new D({async start(){A=c(e)[Symbol.asyncIterator]()},async pull(e){const{value:t,done:s}=await A.next();if(s){queueMicrotask((()=>{e.close()}))}else{if(!p(r)){e.enqueue(new Uint8Array(t))}}return e.desiredSize>0},async cancel(e){await A.return()},type:undefined})}const u={stream:r,source:g,length:E};return[u,l]}function safelyExtractBody(e,A=false){if(!D){D=t(3774).ReadableStream}if(e instanceof D){d(!s.isDisturbed(e),"The body has already been consumed.");d(!e.locked,"The stream is locked.")}return extractBody(e,A)}function cloneBody(e){const[A,t]=e.stream.tee();const r=C(t,{transfer:[t]});const[,s]=r.tee();e.stream=A;return{stream:s,length:e.length,source:e.source}}async function*consumeBody(e){if(e){if(m(e)){yield e}else{const A=e.stream;if(s.isDisturbed(A)){throw new TypeError("The body has already been consumed.")}if(A.locked){throw new TypeError("The stream is locked.")}A[I]=true;yield*A}}}function throwIfAborted(e){if(e.aborted){throw new Q("The operation was aborted.","AbortError")}}function bodyMixinMethods(e){const A={blob(){return specConsumeBody(this,(e=>{let A=bodyMimeType(this);if(A==="failure"){A=""}else if(A){A=b(A)}return new h([e],{type:A})}),e)},arrayBuffer(){return specConsumeBody(this,(e=>new Uint8Array(e).buffer),e)},text(){return specConsumeBody(this,utf8DecodeBytes,e)},json(){return specConsumeBody(this,parseJSONFromBytes,e)},async formData(){u.brandCheck(this,e);throwIfAborted(this[l]);const A=this.headers.get("Content-Type");if(/multipart\/form-data/.test(A)){const e={};for(const[A,t]of this.headers)e[A.toLowerCase()]=t;const A=new E;let t;try{t=new r({headers:e,preservePath:true})}catch(e){throw new Q(`${e}`,"AbortError")}t.on("field",((e,t)=>{A.append(e,t)}));t.on("file",((e,t,r,s,o)=>{const n=[];if(s==="base64"||s.toLowerCase()==="base64"){let s="";t.on("data",(e=>{s+=e.toString().replace(/[\r\n]/gm,"");const A=s.length-s.length%4;n.push(Buffer.from(s.slice(0,A),"base64"));s=s.slice(A)}));t.on("end",(()=>{n.push(Buffer.from(s,"base64"));A.append(e,new k(n,r,{type:o}))}))}else{t.on("data",(e=>{n.push(e)}));t.on("end",(()=>{A.append(e,new k(n,r,{type:o}))}))}}));const s=new Promise(((e,A)=>{t.on("finish",e);t.on("error",(e=>A(new TypeError(e))))}));if(this.body!==null)for await(const e of consumeBody(this[l].body))t.write(e);t.end();await s;return A}else if(/application\/x-www-form-urlencoded/.test(A)){let e;try{let A="";const t=new TextDecoder("utf-8",{ignoreBOM:true});for await(const e of consumeBody(this[l].body)){if(!m(e)){throw new TypeError("Expected Uint8Array chunk")}A+=t.decode(e,{stream:true})}A+=t.decode();e=new URLSearchParams(A)}catch(e){throw Object.assign(new TypeError,{cause:e})}const A=new E;for(const[t,r]of e){A.append(t,r)}return A}else{await Promise.resolve();throwIfAborted(this[l]);throw u.errors.exception({header:`${e.name}.formData`,message:"Could not parse content as FormData."})}}};return A}function mixinBody(e){Object.assign(e.prototype,bodyMixinMethods(e))}async function specConsumeBody(e,A,t){u.brandCheck(e,t);throwIfAborted(e[l]);if(bodyUnusable(e[l].body)){throw new TypeError("Body is unusable")}const r=c();const errorSteps=e=>r.reject(e);const successSteps=e=>{try{r.resolve(A(e))}catch(e){errorSteps(e)}};if(e[l].body==null){successSteps(new Uint8Array);return r.promise}await g(e[l].body,successSteps,errorSteps);return r.promise}function bodyUnusable(e){return e!=null&&(e.stream.locked||s.isDisturbed(e.stream))}function utf8DecodeBytes(e){if(e.length===0){return""}if(e[0]===239&&e[1]===187&&e[2]===191){e=e.subarray(3)}const A=S.decode(e);return A}function parseJSONFromBytes(e){return JSON.parse(utf8DecodeBytes(e))}function bodyMimeType(e){const{headersList:A}=e[l];const t=A.get("content-type");if(t===null){return"failure"}return R(t)}e.exports={extractBody:extractBody,safelyExtractBody:safelyExtractBody,cloneBody:cloneBody,mixinBody:mixinBody}},6040:(e,A,t)=>{"use strict";const{MessageChannel:r,receiveMessageOnPort:s}=t(8167);const o=["GET","HEAD","POST"];const n=new Set(o);const i=[101,204,205,304];const a=[301,302,303,307,308];const c=new Set(a);const g=["1","7","9","11","13","15","17","19","20","21","22","23","25","37","42","43","53","69","77","79","87","95","101","102","103","104","109","110","111","113","115","117","119","123","135","137","139","143","161","179","389","427","465","512","513","514","515","526","530","531","532","540","548","554","556","563","587","601","636","989","990","993","995","1719","1720","1723","2049","3659","4045","5060","5061","6000","6566","6665","6666","6667","6668","6669","6697","10080"];const E=new Set(g);const l=["","no-referrer","no-referrer-when-downgrade","same-origin","origin","strict-origin","origin-when-cross-origin","strict-origin-when-cross-origin","unsafe-url"];const u=new Set(l);const Q=["follow","manual","error"];const C=["GET","HEAD","OPTIONS","TRACE"];const h=new Set(C);const B=["navigate","same-origin","no-cors","cors"];const I=["omit","same-origin","include"];const d=["default","no-store","reload","no-cache","force-cache","only-if-cached"];const p=["content-encoding","content-language","content-location","content-type","content-length"];const m=["half"];const y=["CONNECT","TRACE","TRACK"];const w=new Set(y);const R=["audio","audioworklet","font","image","manifest","paintworklet","script","style","track","video","xslt",""];const b=new Set(R);const D=globalThis.DOMException??(()=>{try{atob("~")}catch(e){return Object.getPrototypeOf(e).constructor}})();let k;const F=globalThis.structuredClone??function structuredClone(e,A=undefined){if(arguments.length===0){throw new TypeError("missing argument")}if(!k){k=new r}k.port1.unref();k.port2.unref();k.port1.postMessage(e,A?.transfer);return s(k.port2).message};e.exports={DOMException:D,structuredClone:F,subresource:R,forbiddenMethods:y,requestBodyHeader:p,referrerPolicy:l,requestRedirect:Q,requestMode:B,requestCredentials:I,requestCache:d,redirectStatus:a,corsSafeListedMethods:o,nullBodyStatus:i,safeMethods:C,badPorts:g,requestDuplex:m,subresourceSet:b,badPortsSet:E,redirectStatusSet:c,corsSafeListedMethodsSet:n,safeMethodsSet:h,forbiddenMethodsSet:w,referrerPolicySet:u}},7160:(e,A,t)=>{const r=t(2613);const{atob:s}=t(181);const{isomorphicDecode:o}=t(9913);const n=new TextEncoder;const i=/^[!#$%&'*+-.^_|~A-Za-z0-9]+$/;const a=/(\u000A|\u000D|\u0009|\u0020)/;const c=/[\u0009|\u0020-\u007E|\u0080-\u00FF]/;function dataURLProcessor(e){r(e.protocol==="data:");let A=URLSerializer(e,true);A=A.slice(5);const t={position:0};let s=collectASequenceOfCodePointsFast(",",A,t);const n=s.length;s=removeASCIIWhitespace(s,true,true);if(t.position>=A.length){return"failure"}t.position++;const i=A.slice(n+1);let a=stringPercentDecode(i);if(/;(\u0020){0,}base64$/i.test(s)){const e=o(a);a=forgivingBase64(e);if(a==="failure"){return"failure"}s=s.slice(0,-6);s=s.replace(/(\u0020)+$/,"");s=s.slice(0,-1)}if(s.startsWith(";")){s="text/plain"+s}let c=parseMIMEType(s);if(c==="failure"){c=parseMIMEType("text/plain;charset=US-ASCII")}return{mimeType:c,body:a}}function URLSerializer(e,A=false){if(!A){return e.href}const t=e.href;const r=e.hash.length;return r===0?t:t.substring(0,t.length-r)}function collectASequenceOfCodePoints(e,A,t){let r="";while(t.positione.length){return"failure"}A.position++;let r=collectASequenceOfCodePointsFast(";",e,A);r=removeHTTPWhitespace(r,false,true);if(r.length===0||!i.test(r)){return"failure"}const s=t.toLowerCase();const o=r.toLowerCase();const n={type:s,subtype:o,parameters:new Map,essence:`${s}/${o}`};while(A.positiona.test(e)),e,A);let t=collectASequenceOfCodePoints((e=>e!==";"&&e!=="="),e,A);t=t.toLowerCase();if(A.positione.length){break}let r=null;if(e[A.position]==='"'){r=collectAnHTTPQuotedString(e,A,true);collectASequenceOfCodePointsFast(";",e,A)}else{r=collectASequenceOfCodePointsFast(";",e,A);r=removeHTTPWhitespace(r,false,true);if(r.length===0){continue}}if(t.length!==0&&i.test(t)&&(r.length===0||c.test(r))&&!n.parameters.has(t)){n.parameters.set(t,r)}}return n}function forgivingBase64(e){e=e.replace(/[\u0009\u000A\u000C\u000D\u0020]/g,"");if(e.length%4===0){e=e.replace(/=?=$/,"")}if(e.length%4===1){return"failure"}if(/[^+/0-9A-Za-z]/.test(e)){return"failure"}const A=s(e);const t=new Uint8Array(A.length);for(let e=0;ee!=='"'&&e!=="\\"),e,A);if(A.position>=e.length){break}const t=e[A.position];A.position++;if(t==="\\"){if(A.position>=e.length){o+="\\";break}o+=e[A.position];A.position++}else{r(t==='"');break}}if(t){return o}return e.slice(s,A.position)}function serializeAMimeType(e){r(e!=="failure");const{parameters:A,essence:t}=e;let s=t;for(let[e,t]of A.entries()){s+=";";s+=e;s+="=";if(!i.test(t)){t=t.replace(/(\\|")/g,"\\$1");t='"'+t;t+='"'}s+=t}return s}function isHTTPWhiteSpace(e){return e==="\r"||e==="\n"||e==="\t"||e===" "}function removeHTTPWhitespace(e,A=true,t=true){let r=0;let s=e.length-1;if(A){for(;r0&&isHTTPWhiteSpace(e[s]);s--);}return e.slice(r,s+1)}function isASCIIWhitespace(e){return e==="\r"||e==="\n"||e==="\t"||e==="\f"||e===" "}function removeASCIIWhitespace(e,A=true,t=true){let r=0;let s=e.length-1;if(A){for(;r0&&isASCIIWhitespace(e[s]);s--);}return e.slice(r,s+1)}e.exports={dataURLProcessor:dataURLProcessor,URLSerializer:URLSerializer,collectASequenceOfCodePoints:collectASequenceOfCodePoints,collectASequenceOfCodePointsFast:collectASequenceOfCodePointsFast,stringPercentDecode:stringPercentDecode,parseMIMEType:parseMIMEType,collectAnHTTPQuotedString:collectAnHTTPQuotedString,serializeAMimeType:serializeAMimeType}},4019:(e,A,t)=>{"use strict";const{Blob:r,File:s}=t(181);const{types:o}=t(9023);const{kState:n}=t(648);const{isBlobLike:i}=t(9913);const{webidl:a}=t(6684);const{parseMIMEType:c,serializeAMimeType:g}=t(7160);const{kEnumerableProperty:E}=t(2806);const l=new TextEncoder;class File extends r{constructor(e,A,t={}){a.argumentLengthCheck(arguments,2,{header:"File constructor"});e=a.converters["sequence"](e);A=a.converters.USVString(A);t=a.converters.FilePropertyBag(t);const r=A;let s=t.type;let o;e:{if(s){s=c(s);if(s==="failure"){s="";break e}s=g(s).toLowerCase()}o=t.lastModified}super(processBlobParts(e,t),{type:s});this[n]={name:r,lastModified:o,type:s}}get name(){a.brandCheck(this,File);return this[n].name}get lastModified(){a.brandCheck(this,File);return this[n].lastModified}get type(){a.brandCheck(this,File);return this[n].type}}class FileLike{constructor(e,A,t={}){const r=A;const s=t.type;const o=t.lastModified??Date.now();this[n]={blobLike:e,name:r,type:s,lastModified:o}}stream(...e){a.brandCheck(this,FileLike);return this[n].blobLike.stream(...e)}arrayBuffer(...e){a.brandCheck(this,FileLike);return this[n].blobLike.arrayBuffer(...e)}slice(...e){a.brandCheck(this,FileLike);return this[n].blobLike.slice(...e)}text(...e){a.brandCheck(this,FileLike);return this[n].blobLike.text(...e)}get size(){a.brandCheck(this,FileLike);return this[n].blobLike.size}get type(){a.brandCheck(this,FileLike);return this[n].blobLike.type}get name(){a.brandCheck(this,FileLike);return this[n].name}get lastModified(){a.brandCheck(this,FileLike);return this[n].lastModified}get[Symbol.toStringTag](){return"File"}}Object.defineProperties(File.prototype,{[Symbol.toStringTag]:{value:"File",configurable:true},name:E,lastModified:E});a.converters.Blob=a.interfaceConverter(r);a.converters.BlobPart=function(e,A){if(a.util.Type(e)==="Object"){if(i(e)){return a.converters.Blob(e,{strict:false})}if(ArrayBuffer.isView(e)||o.isAnyArrayBuffer(e)){return a.converters.BufferSource(e,A)}}return a.converters.USVString(e,A)};a.converters["sequence"]=a.sequenceConverter(a.converters.BlobPart);a.converters.FilePropertyBag=a.dictionaryConverter([{key:"lastModified",converter:a.converters["long long"],get defaultValue(){return Date.now()}},{key:"type",converter:a.converters.DOMString,defaultValue:""},{key:"endings",converter:e=>{e=a.converters.DOMString(e);e=e.toLowerCase();if(e!=="native"){e="transparent"}return e},defaultValue:"transparent"}]);function processBlobParts(e,A){const t=[];for(const r of e){if(typeof r==="string"){let e=r;if(A.endings==="native"){e=convertLineEndingsNative(e)}t.push(l.encode(e))}else if(o.isAnyArrayBuffer(r)||o.isTypedArray(r)){if(!r.buffer){t.push(new Uint8Array(r))}else{t.push(new Uint8Array(r.buffer,r.byteOffset,r.byteLength))}}else if(i(r)){t.push(r)}}return t}function convertLineEndingsNative(e){let A="\n";if(process.platform==="win32"){A="\r\n"}return e.replace(/\r?\n/g,A)}function isFileLike(e){return s&&e instanceof s||e instanceof File||e&&(typeof e.stream==="function"||typeof e.arrayBuffer==="function")&&e[Symbol.toStringTag]==="File"}e.exports={File:File,FileLike:FileLike,isFileLike:isFileLike}},1499:(e,A,t)=>{"use strict";const{isBlobLike:r,toUSVString:s,makeIterator:o}=t(9913);const{kState:n}=t(648);const{File:i,FileLike:a,isFileLike:c}=t(4019);const{webidl:g}=t(6684);const{Blob:E,File:l}=t(181);const u=l??i;class FormData{constructor(e){if(e!==undefined){throw g.errors.conversionFailed({prefix:"FormData constructor",argument:"Argument 1",types:["undefined"]})}this[n]=[]}append(e,A,t=undefined){g.brandCheck(this,FormData);g.argumentLengthCheck(arguments,2,{header:"FormData.append"});if(arguments.length===3&&!r(A)){throw new TypeError("Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'")}e=g.converters.USVString(e);A=r(A)?g.converters.Blob(A,{strict:false}):g.converters.USVString(A);t=arguments.length===3?g.converters.USVString(t):undefined;const s=makeEntry(e,A,t);this[n].push(s)}delete(e){g.brandCheck(this,FormData);g.argumentLengthCheck(arguments,1,{header:"FormData.delete"});e=g.converters.USVString(e);this[n]=this[n].filter((A=>A.name!==e))}get(e){g.brandCheck(this,FormData);g.argumentLengthCheck(arguments,1,{header:"FormData.get"});e=g.converters.USVString(e);const A=this[n].findIndex((A=>A.name===e));if(A===-1){return null}return this[n][A].value}getAll(e){g.brandCheck(this,FormData);g.argumentLengthCheck(arguments,1,{header:"FormData.getAll"});e=g.converters.USVString(e);return this[n].filter((A=>A.name===e)).map((e=>e.value))}has(e){g.brandCheck(this,FormData);g.argumentLengthCheck(arguments,1,{header:"FormData.has"});e=g.converters.USVString(e);return this[n].findIndex((A=>A.name===e))!==-1}set(e,A,t=undefined){g.brandCheck(this,FormData);g.argumentLengthCheck(arguments,2,{header:"FormData.set"});if(arguments.length===3&&!r(A)){throw new TypeError("Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'")}e=g.converters.USVString(e);A=r(A)?g.converters.Blob(A,{strict:false}):g.converters.USVString(A);t=arguments.length===3?s(t):undefined;const o=makeEntry(e,A,t);const i=this[n].findIndex((A=>A.name===e));if(i!==-1){this[n]=[...this[n].slice(0,i),o,...this[n].slice(i+1).filter((A=>A.name!==e))]}else{this[n].push(o)}}entries(){g.brandCheck(this,FormData);return o((()=>this[n].map((e=>[e.name,e.value]))),"FormData","key+value")}keys(){g.brandCheck(this,FormData);return o((()=>this[n].map((e=>[e.name,e.value]))),"FormData","key")}values(){g.brandCheck(this,FormData);return o((()=>this[n].map((e=>[e.name,e.value]))),"FormData","value")}forEach(e,A=globalThis){g.brandCheck(this,FormData);g.argumentLengthCheck(arguments,1,{header:"FormData.forEach"});if(typeof e!=="function"){throw new TypeError("Failed to execute 'forEach' on 'FormData': parameter 1 is not of type 'Function'.")}for(const[t,r]of this){e.apply(A,[r,t,this])}}}FormData.prototype[Symbol.iterator]=FormData.prototype.entries;Object.defineProperties(FormData.prototype,{[Symbol.toStringTag]:{value:"FormData",configurable:true}});function makeEntry(e,A,t){e=Buffer.from(e).toString("utf8");if(typeof A==="string"){A=Buffer.from(A).toString("utf8")}else{if(!c(A)){A=A instanceof E?new u([A],"blob",{type:A.type}):new a(A,"blob",{type:A.type})}if(t!==undefined){const e={type:A.type,lastModified:A.lastModified};A=l&&A instanceof l||A instanceof i?new u([A],t,e):new a(A,t,e)}}return{name:e,value:A}}e.exports={FormData:FormData}},574:e=>{"use strict";const A=Symbol.for("undici.globalOrigin.1");function getGlobalOrigin(){return globalThis[A]}function setGlobalOrigin(e){if(e===undefined){Object.defineProperty(globalThis,A,{value:undefined,writable:true,enumerable:false,configurable:false});return}const t=new URL(e);if(t.protocol!=="http:"&&t.protocol!=="https:"){throw new TypeError(`Only http & https urls are allowed, received ${t.protocol}`)}Object.defineProperty(globalThis,A,{value:t,writable:true,enumerable:false,configurable:false})}e.exports={getGlobalOrigin:getGlobalOrigin,setGlobalOrigin:setGlobalOrigin}},1815:(e,A,t)=>{"use strict";const{kHeadersList:r,kConstruct:s}=t(7781);const{kGuard:o}=t(648);const{kEnumerableProperty:n}=t(2806);const{makeIterator:i,isValidHeaderName:a,isValidHeaderValue:c}=t(9913);const{webidl:g}=t(6684);const E=t(2613);const l=Symbol("headers map");const u=Symbol("headers map sorted");function isHTTPWhiteSpaceCharCode(e){return e===10||e===13||e===9||e===32}function headerValueNormalize(e){let A=0;let t=e.length;while(t>A&&isHTTPWhiteSpaceCharCode(e.charCodeAt(t-1)))--t;while(t>A&&isHTTPWhiteSpaceCharCode(e.charCodeAt(A)))++A;return A===0&&t===e.length?e:e.substring(A,t)}function fill(e,A){if(Array.isArray(A)){for(let t=0;t>","record"]})}}function appendHeader(e,A,t){t=headerValueNormalize(t);if(!a(A)){throw g.errors.invalidArgument({prefix:"Headers.append",value:A,type:"header name"})}else if(!c(t)){throw g.errors.invalidArgument({prefix:"Headers.append",value:t,type:"header value"})}if(e[o]==="immutable"){throw new TypeError("immutable")}else if(e[o]==="request-no-cors"){}return e[r].append(A,t)}class HeadersList{cookies=null;constructor(e){if(e instanceof HeadersList){this[l]=new Map(e[l]);this[u]=e[u];this.cookies=e.cookies===null?null:[...e.cookies]}else{this[l]=new Map(e);this[u]=null}}contains(e){e=e.toLowerCase();return this[l].has(e)}clear(){this[l].clear();this[u]=null;this.cookies=null}append(e,A){this[u]=null;const t=e.toLowerCase();const r=this[l].get(t);if(r){const e=t==="cookie"?"; ":", ";this[l].set(t,{name:r.name,value:`${r.value}${e}${A}`})}else{this[l].set(t,{name:e,value:A})}if(t==="set-cookie"){this.cookies??=[];this.cookies.push(A)}}set(e,A){this[u]=null;const t=e.toLowerCase();if(t==="set-cookie"){this.cookies=[A]}this[l].set(t,{name:e,value:A})}delete(e){this[u]=null;e=e.toLowerCase();if(e==="set-cookie"){this.cookies=null}this[l].delete(e)}get(e){const A=this[l].get(e.toLowerCase());return A===undefined?null:A.value}*[Symbol.iterator](){for(const[e,{value:A}]of this[l]){yield[e,A]}}get entries(){const e={};if(this[l].size){for(const{name:A,value:t}of this[l].values()){e[A]=t}}return e}}class Headers{constructor(e=undefined){if(e===s){return}this[r]=new HeadersList;this[o]="none";if(e!==undefined){e=g.converters.HeadersInit(e);fill(this,e)}}append(e,A){g.brandCheck(this,Headers);g.argumentLengthCheck(arguments,2,{header:"Headers.append"});e=g.converters.ByteString(e);A=g.converters.ByteString(A);return appendHeader(this,e,A)}delete(e){g.brandCheck(this,Headers);g.argumentLengthCheck(arguments,1,{header:"Headers.delete"});e=g.converters.ByteString(e);if(!a(e)){throw g.errors.invalidArgument({prefix:"Headers.delete",value:e,type:"header name"})}if(this[o]==="immutable"){throw new TypeError("immutable")}else if(this[o]==="request-no-cors"){}if(!this[r].contains(e)){return}this[r].delete(e)}get(e){g.brandCheck(this,Headers);g.argumentLengthCheck(arguments,1,{header:"Headers.get"});e=g.converters.ByteString(e);if(!a(e)){throw g.errors.invalidArgument({prefix:"Headers.get",value:e,type:"header name"})}return this[r].get(e)}has(e){g.brandCheck(this,Headers);g.argumentLengthCheck(arguments,1,{header:"Headers.has"});e=g.converters.ByteString(e);if(!a(e)){throw g.errors.invalidArgument({prefix:"Headers.has",value:e,type:"header name"})}return this[r].contains(e)}set(e,A){g.brandCheck(this,Headers);g.argumentLengthCheck(arguments,2,{header:"Headers.set"});e=g.converters.ByteString(e);A=g.converters.ByteString(A);A=headerValueNormalize(A);if(!a(e)){throw g.errors.invalidArgument({prefix:"Headers.set",value:e,type:"header name"})}else if(!c(A)){throw g.errors.invalidArgument({prefix:"Headers.set",value:A,type:"header value"})}if(this[o]==="immutable"){throw new TypeError("immutable")}else if(this[o]==="request-no-cors"){}this[r].set(e,A)}getSetCookie(){g.brandCheck(this,Headers);const e=this[r].cookies;if(e){return[...e]}return[]}get[u](){if(this[r][u]){return this[r][u]}const e=[];const A=[...this[r]].sort(((e,A)=>e[0]e),"Headers","key")}return i((()=>[...this[u].values()]),"Headers","key")}values(){g.brandCheck(this,Headers);if(this[o]==="immutable"){const e=this[u];return i((()=>e),"Headers","value")}return i((()=>[...this[u].values()]),"Headers","value")}entries(){g.brandCheck(this,Headers);if(this[o]==="immutable"){const e=this[u];return i((()=>e),"Headers","key+value")}return i((()=>[...this[u].values()]),"Headers","key+value")}forEach(e,A=globalThis){g.brandCheck(this,Headers);g.argumentLengthCheck(arguments,1,{header:"Headers.forEach"});if(typeof e!=="function"){throw new TypeError("Failed to execute 'forEach' on 'Headers': parameter 1 is not of type 'Function'.")}for(const[t,r]of this){e.apply(A,[r,t,this])}}[Symbol.for("nodejs.util.inspect.custom")](){g.brandCheck(this,Headers);return this[r]}}Headers.prototype[Symbol.iterator]=Headers.prototype.entries;Object.defineProperties(Headers.prototype,{append:n,delete:n,get:n,has:n,set:n,getSetCookie:n,keys:n,values:n,entries:n,forEach:n,[Symbol.iterator]:{enumerable:false},[Symbol.toStringTag]:{value:"Headers",configurable:true}});g.converters.HeadersInit=function(e){if(g.util.Type(e)==="Object"){if(e[Symbol.iterator]){return g.converters["sequence>"](e)}return g.converters["record"](e)}throw g.errors.conversionFailed({prefix:"Headers constructor",argument:"Argument 1",types:["sequence>","record"]})};e.exports={fill:fill,Headers:Headers,HeadersList:HeadersList}},5697:(e,A,t)=>{"use strict";const{Response:r,makeNetworkError:s,makeAppropriateNetworkError:o,filterResponse:n,makeResponse:i}=t(7862);const{Headers:a}=t(1815);const{Request:c,makeRequest:g}=t(1940);const E=t(3106);const{bytesMatch:l,makePolicyContainer:u,clonePolicyContainer:Q,requestBadPort:C,TAOCheck:h,appendRequestOriginHeader:B,responseLocationURL:I,requestCurrentURL:d,setRequestReferrerPolicyOnRedirect:p,tryUpgradeRequestToAPotentiallyTrustworthyURL:m,createOpaqueTimingInfo:y,appendFetchMetadata:w,corsCheck:R,crossOriginResourcePolicyCheck:b,determineRequestsReferrer:D,coarsenedSharedCurrentTime:k,createDeferredPromise:F,isBlobLike:S,sameOrigin:T,isCancelled:N,isAborted:U,isErrorLike:L,fullyReadBody:G,readableStreamClose:v,isomorphicEncode:M,urlIsLocal:H,urlIsHttpHttpsScheme:Y,urlHasHttpsScheme:O}=t(9913);const{kState:_,kHeaders:P,kGuard:J,kRealm:V}=t(648);const x=t(2613);const{safelyExtractBody:q}=t(8689);const{redirectStatusSet:W,nullBodyStatus:j,safeMethodsSet:Z,requestBodyHeader:X,subresourceSet:K,DOMException:z}=t(6040);const{kHeadersList:$}=t(7781);const ee=t(4434);const{Readable:Ae,pipeline:te}=t(2203);const{addAbortListener:re,isErrored:se,isReadable:oe,nodeMajor:ne,nodeMinor:ie}=t(2806);const{dataURLProcessor:ae,serializeAMimeType:ce}=t(7160);const{TransformStream:ge}=t(3774);const{getGlobalDispatcher:Ee}=t(6875);const{webidl:le}=t(6684);const{STATUS_CODES:ue}=t(8611);const Qe=["GET","HEAD"];let Ce;let he=globalThis.ReadableStream;class Fetch extends ee{constructor(e){super();this.dispatcher=e;this.connection=null;this.dump=false;this.state="ongoing";this.setMaxListeners(21)}terminate(e){if(this.state!=="ongoing"){return}this.state="terminated";this.connection?.destroy(e);this.emit("terminated",e)}abort(e){if(this.state!=="ongoing"){return}this.state="aborted";if(!e){e=new z("The operation was aborted.","AbortError")}this.serializedAbortReason=e;this.connection?.destroy(e);this.emit("terminated",e)}}function fetch(e,A={}){le.argumentLengthCheck(arguments,1,{header:"globalThis.fetch"});const t=F();let s;try{s=new c(e,A)}catch(e){t.reject(e);return t.promise}const o=s[_];if(s.signal.aborted){abortFetch(t,o,null,s.signal.reason);return t.promise}const n=o.client.globalObject;if(n?.constructor?.name==="ServiceWorkerGlobalScope"){o.serviceWorkers="none"}let i=null;const a=null;let g=false;let E=null;re(s.signal,(()=>{g=true;x(E!=null);E.abort(s.signal.reason);abortFetch(t,o,i,s.signal.reason)}));const handleFetchDone=e=>finalizeAndReportTiming(e,"fetch");const processResponse=e=>{if(g){return Promise.resolve()}if(e.aborted){abortFetch(t,o,i,E.serializedAbortReason);return Promise.resolve()}if(e.type==="error"){t.reject(Object.assign(new TypeError("fetch failed"),{cause:e.error}));return Promise.resolve()}i=new r;i[_]=e;i[V]=a;i[P][$]=e.headersList;i[P][J]="immutable";i[P][V]=a;t.resolve(i)};E=fetching({request:o,processResponseEndOfBody:handleFetchDone,processResponse:processResponse,dispatcher:A.dispatcher??Ee()});return t.promise}function finalizeAndReportTiming(e,A="other"){if(e.type==="error"&&e.aborted){return}if(!e.urlList?.length){return}const t=e.urlList[0];let r=e.timingInfo;let s=e.cacheState;if(!Y(t)){return}if(r===null){return}if(!e.timingAllowPassed){r=y({startTime:r.startTime});s=""}r.endTime=k();e.timingInfo=r;markResourceTiming(r,t,A,globalThis,s)}function markResourceTiming(e,A,t,r,s){if(ne>18||ne===18&&ie>=2){performance.markResourceTiming(e,A.href,t,r,s)}}function abortFetch(e,A,t,r){if(!r){r=new z("The operation was aborted.","AbortError")}e.reject(r);if(A.body!=null&&oe(A.body?.stream)){A.body.stream.cancel(r).catch((e=>{if(e.code==="ERR_INVALID_STATE"){return}throw e}))}if(t==null){return}const s=t[_];if(s.body!=null&&oe(s.body?.stream)){s.body.stream.cancel(r).catch((e=>{if(e.code==="ERR_INVALID_STATE"){return}throw e}))}}function fetching({request:e,processRequestBodyChunkLength:A,processRequestEndOfBody:t,processResponse:r,processResponseEndOfBody:s,processResponseConsumeBody:o,useParallelQueue:n=false,dispatcher:i}){let a=null;let c=false;if(e.client!=null){a=e.client.globalObject;c=e.client.crossOriginIsolatedCapability}const g=k(c);const E=y({startTime:g});const l={controller:new Fetch(i),request:e,timingInfo:E,processRequestBodyChunkLength:A,processRequestEndOfBody:t,processResponse:r,processResponseConsumeBody:o,processResponseEndOfBody:s,taskDestination:a,crossOriginIsolatedCapability:c};x(!e.body||e.body.stream);if(e.window==="client"){e.window=e.client?.globalObject?.constructor?.name==="Window"?e.client:"no-window"}if(e.origin==="client"){e.origin=e.client?.origin}if(e.policyContainer==="client"){if(e.client!=null){e.policyContainer=Q(e.client.policyContainer)}else{e.policyContainer=u()}}if(!e.headersList.contains("accept")){const A="*/*";e.headersList.append("accept",A)}if(!e.headersList.contains("accept-language")){e.headersList.append("accept-language","*")}if(e.priority===null){}if(K.has(e.destination)){}mainFetch(l).catch((e=>{l.controller.terminate(e)}));return l.controller}async function mainFetch(e,A=false){const t=e.request;let r=null;if(t.localURLsOnly&&!H(d(t))){r=s("local URLs only")}m(t);if(C(t)==="blocked"){r=s("bad port")}if(t.referrerPolicy===""){t.referrerPolicy=t.policyContainer.referrerPolicy}if(t.referrer!=="no-referrer"){t.referrer=D(t)}if(r===null){r=await(async()=>{const A=d(t);if(T(A,t.url)&&t.responseTainting==="basic"||A.protocol==="data:"||(t.mode==="navigate"||t.mode==="websocket")){t.responseTainting="basic";return await schemeFetch(e)}if(t.mode==="same-origin"){return s('request mode cannot be "same-origin"')}if(t.mode==="no-cors"){if(t.redirect!=="follow"){return s('redirect mode cannot be "follow" for "no-cors" request')}t.responseTainting="opaque";return await schemeFetch(e)}if(!Y(d(t))){return s("URL scheme must be a HTTP(S) scheme")}t.responseTainting="cors";return await httpFetch(e)})()}if(A){return r}if(r.status!==0&&!r.internalResponse){if(t.responseTainting==="cors"){}if(t.responseTainting==="basic"){r=n(r,"basic")}else if(t.responseTainting==="cors"){r=n(r,"cors")}else if(t.responseTainting==="opaque"){r=n(r,"opaque")}else{x(false)}}let o=r.status===0?r:r.internalResponse;if(o.urlList.length===0){o.urlList.push(...t.urlList)}if(!t.timingAllowFailed){r.timingAllowPassed=true}if(r.type==="opaque"&&o.status===206&&o.rangeRequested&&!t.headers.contains("range")){r=o=s()}if(r.status!==0&&(t.method==="HEAD"||t.method==="CONNECT"||j.includes(o.status))){o.body=null;e.controller.dump=true}if(t.integrity){const processBodyError=A=>fetchFinale(e,s(A));if(t.responseTainting==="opaque"||r.body==null){processBodyError(r.error);return}const processBody=A=>{if(!l(A,t.integrity)){processBodyError("integrity mismatch");return}r.body=q(A)[0];fetchFinale(e,r)};await G(r.body,processBody,processBodyError)}else{fetchFinale(e,r)}}function schemeFetch(e){if(N(e)&&e.request.redirectCount===0){return Promise.resolve(o(e))}const{request:A}=e;const{protocol:r}=d(A);switch(r){case"about:":{return Promise.resolve(s("about scheme is not supported"))}case"blob:":{if(!Ce){Ce=t(181).resolveObjectURL}const e=d(A);if(e.search.length!==0){return Promise.resolve(s("NetworkError when attempting to fetch resource."))}const r=Ce(e.toString());if(A.method!=="GET"||!S(r)){return Promise.resolve(s("invalid method"))}const o=q(r);const n=o[0];const a=M(`${n.length}`);const c=o[1]??"";const g=i({statusText:"OK",headersList:[["content-length",{name:"Content-Length",value:a}],["content-type",{name:"Content-Type",value:c}]]});g.body=n;return Promise.resolve(g)}case"data:":{const e=d(A);const t=ae(e);if(t==="failure"){return Promise.resolve(s("failed to fetch the data URL"))}const r=ce(t.mimeType);return Promise.resolve(i({statusText:"OK",headersList:[["content-type",{name:"Content-Type",value:r}]],body:q(t.body)[0]}))}case"file:":{return Promise.resolve(s("not implemented... yet..."))}case"http:":case"https:":{return httpFetch(e).catch((e=>s(e)))}default:{return Promise.resolve(s("unknown scheme"))}}}function finalizeResponse(e,A){e.request.done=true;if(e.processResponseDone!=null){queueMicrotask((()=>e.processResponseDone(A)))}}function fetchFinale(e,A){if(A.type==="error"){A.urlList=[e.request.urlList[0]];A.timingInfo=y({startTime:e.timingInfo.startTime})}const processResponseEndOfBody=()=>{e.request.done=true;if(e.processResponseEndOfBody!=null){queueMicrotask((()=>e.processResponseEndOfBody(A)))}};if(e.processResponse!=null){queueMicrotask((()=>e.processResponse(A)))}if(A.body==null){processResponseEndOfBody()}else{const identityTransformAlgorithm=(e,A)=>{A.enqueue(e)};const e=new ge({start(){},transform:identityTransformAlgorithm,flush:processResponseEndOfBody},{size(){return 1}},{size(){return 1}});A.body={stream:A.body.stream.pipeThrough(e)}}if(e.processResponseConsumeBody!=null){const processBody=t=>e.processResponseConsumeBody(A,t);const processBodyError=t=>e.processResponseConsumeBody(A,t);if(A.body==null){queueMicrotask((()=>processBody(null)))}else{return G(A.body,processBody,processBodyError)}return Promise.resolve()}}async function httpFetch(e){const A=e.request;let t=null;let r=null;const o=e.timingInfo;if(A.serviceWorkers==="all"){}if(t===null){if(A.redirect==="follow"){A.serviceWorkers="none"}r=t=await httpNetworkOrCacheFetch(e);if(A.responseTainting==="cors"&&R(A,t)==="failure"){return s("cors failure")}if(h(A,t)==="failure"){A.timingAllowFailed=true}}if((A.responseTainting==="opaque"||t.type==="opaque")&&b(A.origin,A.client,A.destination,r)==="blocked"){return s("blocked")}if(W.has(r.status)){if(A.redirect!=="manual"){e.controller.connection.destroy()}if(A.redirect==="error"){t=s("unexpected redirect")}else if(A.redirect==="manual"){t=r}else if(A.redirect==="follow"){t=await httpRedirectFetch(e,t)}else{x(false)}}t.timingInfo=o;return t}function httpRedirectFetch(e,A){const t=e.request;const r=A.internalResponse?A.internalResponse:A;let o;try{o=I(r,d(t).hash);if(o==null){return A}}catch(e){return Promise.resolve(s(e))}if(!Y(o)){return Promise.resolve(s("URL scheme must be a HTTP(S) scheme"))}if(t.redirectCount===20){return Promise.resolve(s("redirect count exceeded"))}t.redirectCount+=1;if(t.mode==="cors"&&(o.username||o.password)&&!T(t,o)){return Promise.resolve(s('cross origin not allowed for request mode "cors"'))}if(t.responseTainting==="cors"&&(o.username||o.password)){return Promise.resolve(s('URL cannot contain credentials for request mode "cors"'))}if(r.status!==303&&t.body!=null&&t.body.source==null){return Promise.resolve(s())}if([301,302].includes(r.status)&&t.method==="POST"||r.status===303&&!Qe.includes(t.method)){t.method="GET";t.body=null;for(const e of X){t.headersList.delete(e)}}if(!T(d(t),o)){t.headersList.delete("authorization");t.headersList.delete("proxy-authorization",true);t.headersList.delete("cookie");t.headersList.delete("host")}if(t.body!=null){x(t.body.source!=null);t.body=q(t.body.source)[0]}const n=e.timingInfo;n.redirectEndTime=n.postRedirectStartTime=k(e.crossOriginIsolatedCapability);if(n.redirectStartTime===0){n.redirectStartTime=n.startTime}t.urlList.push(o);p(t,r);return mainFetch(e,true)}async function httpNetworkOrCacheFetch(e,A=false,t=false){const r=e.request;let n=null;let i=null;let a=null;const c=null;const E=false;if(r.window==="no-window"&&r.redirect==="error"){n=e;i=r}else{i=g(r);n={...e};n.request=i}const l=r.credentials==="include"||r.credentials==="same-origin"&&r.responseTainting==="basic";const u=i.body?i.body.length:null;let Q=null;if(i.body==null&&["POST","PUT"].includes(i.method)){Q="0"}if(u!=null){Q=M(`${u}`)}if(Q!=null){i.headersList.append("content-length",Q)}if(u!=null&&i.keepalive){}if(i.referrer instanceof URL){i.headersList.append("referer",M(i.referrer.href))}B(i);w(i);if(!i.headersList.contains("user-agent")){i.headersList.append("user-agent",typeof esbuildDetection==="undefined"?"undici":"node")}if(i.cache==="default"&&(i.headersList.contains("if-modified-since")||i.headersList.contains("if-none-match")||i.headersList.contains("if-unmodified-since")||i.headersList.contains("if-match")||i.headersList.contains("if-range"))){i.cache="no-store"}if(i.cache==="no-cache"&&!i.preventNoCacheCacheControlHeaderModification&&!i.headersList.contains("cache-control")){i.headersList.append("cache-control","max-age=0")}if(i.cache==="no-store"||i.cache==="reload"){if(!i.headersList.contains("pragma")){i.headersList.append("pragma","no-cache")}if(!i.headersList.contains("cache-control")){i.headersList.append("cache-control","no-cache")}}if(i.headersList.contains("range")){i.headersList.append("accept-encoding","identity")}if(!i.headersList.contains("accept-encoding")){if(O(d(i))){i.headersList.append("accept-encoding","br, gzip, deflate")}else{i.headersList.append("accept-encoding","gzip, deflate")}}i.headersList.delete("host");if(l){}if(c==null){i.cache="no-store"}if(i.mode!=="no-store"&&i.mode!=="reload"){}if(a==null){if(i.mode==="only-if-cached"){return s("only if cached")}const e=await httpNetworkFetch(n,l,t);if(!Z.has(i.method)&&e.status>=200&&e.status<=399){}if(E&&e.status===304){}if(a==null){a=e}}a.urlList=[...i.urlList];if(i.headersList.contains("range")){a.rangeRequested=true}a.requestIncludesCredentials=l;if(a.status===407){if(r.window==="no-window"){return s()}if(N(e)){return o(e)}return s("proxy authentication required")}if(a.status===421&&!t&&(r.body==null||r.body.source!=null)){if(N(e)){return o(e)}e.controller.connection.destroy();a=await httpNetworkOrCacheFetch(e,A,true)}if(A){}return a}async function httpNetworkFetch(e,A=false,r=false){x(!e.controller.connection||e.controller.connection.destroyed);e.controller.connection={abort:null,destroyed:false,destroy(e){if(!this.destroyed){this.destroyed=true;this.abort?.(e??new z("The operation was aborted.","AbortError"))}}};const n=e.request;let c=null;const g=e.timingInfo;const l=null;if(l==null){n.cache="no-store"}const u=r?"yes":"no";if(n.mode==="websocket"){}else{}let Q=null;if(n.body==null&&e.processRequestEndOfBody){queueMicrotask((()=>e.processRequestEndOfBody()))}else if(n.body!=null){const processBodyChunk=async function*(A){if(N(e)){return}yield A;e.processRequestBodyChunkLength?.(A.byteLength)};const processEndOfBody=()=>{if(N(e)){return}if(e.processRequestEndOfBody){e.processRequestEndOfBody()}};const processBodyError=A=>{if(N(e)){return}if(A.name==="AbortError"){e.controller.abort()}else{e.controller.terminate(A)}};Q=async function*(){try{for await(const e of n.body.stream){yield*processBodyChunk(e)}processEndOfBody()}catch(e){processBodyError(e)}}()}try{const{body:A,status:t,statusText:r,headersList:s,socket:o}=await dispatch({body:Q});if(o){c=i({status:t,statusText:r,headersList:s,socket:o})}else{const o=A[Symbol.asyncIterator]();e.controller.next=()=>o.next();c=i({status:t,statusText:r,headersList:s})}}catch(A){if(A.name==="AbortError"){e.controller.connection.destroy();return o(e,A)}return s(A)}const pullAlgorithm=()=>{e.controller.resume()};const cancelAlgorithm=A=>{e.controller.abort(A)};if(!he){he=t(3774).ReadableStream}const C=new he({async start(A){e.controller.controller=A},async pull(e){await pullAlgorithm(e)},async cancel(e){await cancelAlgorithm(e)}},{highWaterMark:0,size(){return 1}});c.body={stream:C};e.controller.on("terminated",onAborted);e.controller.resume=async()=>{while(true){let A;let t;try{const{done:t,value:r}=await e.controller.next();if(U(e)){break}A=t?undefined:r}catch(r){if(e.controller.ended&&!g.encodedBodySize){A=undefined}else{A=r;t=true}}if(A===undefined){v(e.controller.controller);finalizeResponse(e,c);return}g.decodedBodySize+=A?.byteLength??0;if(t){e.controller.terminate(A);return}e.controller.controller.enqueue(new Uint8Array(A));if(se(C)){e.controller.terminate();return}if(!e.controller.controller.desiredSize){return}}};function onAborted(A){if(U(e)){c.aborted=true;if(oe(C)){e.controller.controller.error(e.controller.serializedAbortReason)}}else{if(oe(C)){e.controller.controller.error(new TypeError("terminated",{cause:L(A)?A:undefined}))}}e.controller.connection.destroy()}return c;async function dispatch({body:A}){const t=d(n);const r=e.controller.dispatcher;return new Promise(((s,o)=>r.dispatch({path:t.pathname+t.search,origin:t.origin,method:n.method,body:e.controller.dispatcher.isMockActive?n.body&&(n.body.source||n.body.stream):A,headers:n.headersList.entries,maxRedirections:0,upgrade:n.mode==="websocket"?"websocket":undefined},{body:null,abort:null,onConnect(A){const{connection:t}=e.controller;if(t.destroyed){A(new z("The operation was aborted.","AbortError"))}else{e.controller.on("terminated",A);this.abort=t.abort=A}},onHeaders(e,A,t,r){if(e<200){return}let o=[];let i="";const c=new a;if(Array.isArray(A)){for(let e=0;ee.trim()))}else if(t.toLowerCase()==="location"){i=r}c[$].append(t,r)}}else{const e=Object.keys(A);for(const t of e){const e=A[t];if(t.toLowerCase()==="content-encoding"){o=e.toLowerCase().split(",").map((e=>e.trim())).reverse()}else if(t.toLowerCase()==="location"){i=e}c[$].append(t,e)}}this.body=new Ae({read:t});const g=[];const l=n.redirect==="follow"&&i&&W.has(e);if(n.method!=="HEAD"&&n.method!=="CONNECT"&&!j.includes(e)&&!l){for(const e of o){if(e==="x-gzip"||e==="gzip"){g.push(E.createGunzip({flush:E.constants.Z_SYNC_FLUSH,finishFlush:E.constants.Z_SYNC_FLUSH}))}else if(e==="deflate"){g.push(E.createInflate())}else if(e==="br"){g.push(E.createBrotliDecompress())}else{g.length=0;break}}}s({status:e,statusText:r,headersList:c[$],body:g.length?te(this.body,...g,(()=>{})):this.body.on("error",(()=>{}))});return true},onData(A){if(e.controller.dump){return}const t=A;g.encodedBodySize+=t.byteLength;return this.body.push(t)},onComplete(){if(this.abort){e.controller.off("terminated",this.abort)}e.controller.ended=true;this.body.push(null)},onError(A){if(this.abort){e.controller.off("terminated",this.abort)}this.body?.destroy(A);e.controller.terminate(A);o(A)},onUpgrade(e,A,t){if(e!==101){return}const r=new a;for(let e=0;e{"use strict";const{extractBody:r,mixinBody:s,cloneBody:o}=t(8689);const{Headers:n,fill:i,HeadersList:a}=t(1815);const{FinalizationRegistry:c}=t(4904)();const g=t(2806);const{isValidHTTPToken:E,sameOrigin:l,normalizeMethod:u,makePolicyContainer:Q,normalizeMethodRecord:C}=t(9913);const{forbiddenMethodsSet:h,corsSafeListedMethodsSet:B,referrerPolicy:I,requestRedirect:d,requestMode:p,requestCredentials:m,requestCache:y,requestDuplex:w}=t(6040);const{kEnumerableProperty:R}=g;const{kHeaders:b,kSignal:D,kState:k,kGuard:F,kRealm:S}=t(648);const{webidl:T}=t(6684);const{getGlobalOrigin:N}=t(574);const{URLSerializer:U}=t(7160);const{kHeadersList:L,kConstruct:G}=t(7781);const v=t(2613);const{getMaxListeners:M,setMaxListeners:H,getEventListeners:Y,defaultMaxListeners:O}=t(4434);let _=globalThis.TransformStream;const P=Symbol("abortController");const J=new c((({signal:e,abort:A})=>{e.removeEventListener("abort",A)}));class Request{constructor(e,A={}){if(e===G){return}T.argumentLengthCheck(arguments,1,{header:"Request constructor"});e=T.converters.RequestInfo(e);A=T.converters.RequestInit(A);this[S]={settingsObject:{baseUrl:N(),get origin(){return this.baseUrl?.origin},policyContainer:Q()}};let s=null;let o=null;const c=this[S].settingsObject.baseUrl;let I=null;if(typeof e==="string"){let A;try{A=new URL(e,c)}catch(A){throw new TypeError("Failed to parse URL from "+e,{cause:A})}if(A.username||A.password){throw new TypeError("Request cannot be constructed from a URL that includes credentials: "+e)}s=makeRequest({urlList:[A]});o="cors"}else{v(e instanceof Request);s=e[k];I=e[D]}const d=this[S].settingsObject.origin;let p="client";if(s.window?.constructor?.name==="EnvironmentSettingsObject"&&l(s.window,d)){p=s.window}if(A.window!=null){throw new TypeError(`'window' option '${p}' must be null`)}if("window"in A){p="no-window"}s=makeRequest({method:s.method,headersList:s.headersList,unsafeRequest:s.unsafeRequest,client:this[S].settingsObject,window:p,priority:s.priority,origin:s.origin,referrer:s.referrer,referrerPolicy:s.referrerPolicy,mode:s.mode,credentials:s.credentials,cache:s.cache,redirect:s.redirect,integrity:s.integrity,keepalive:s.keepalive,reloadNavigation:s.reloadNavigation,historyNavigation:s.historyNavigation,urlList:[...s.urlList]});const m=Object.keys(A).length!==0;if(m){if(s.mode==="navigate"){s.mode="same-origin"}s.reloadNavigation=false;s.historyNavigation=false;s.origin="client";s.referrer="client";s.referrerPolicy="";s.url=s.urlList[s.urlList.length-1];s.urlList=[s.url]}if(A.referrer!==undefined){const e=A.referrer;if(e===""){s.referrer="no-referrer"}else{let A;try{A=new URL(e,c)}catch(A){throw new TypeError(`Referrer "${e}" is not a valid URL.`,{cause:A})}if(A.protocol==="about:"&&A.hostname==="client"||d&&!l(A,this[S].settingsObject.baseUrl)){s.referrer="client"}else{s.referrer=A}}}if(A.referrerPolicy!==undefined){s.referrerPolicy=A.referrerPolicy}let y;if(A.mode!==undefined){y=A.mode}else{y=o}if(y==="navigate"){throw T.errors.exception({header:"Request constructor",message:"invalid request mode navigate."})}if(y!=null){s.mode=y}if(A.credentials!==undefined){s.credentials=A.credentials}if(A.cache!==undefined){s.cache=A.cache}if(s.cache==="only-if-cached"&&s.mode!=="same-origin"){throw new TypeError("'only-if-cached' can be set only with 'same-origin' mode")}if(A.redirect!==undefined){s.redirect=A.redirect}if(A.integrity!=null){s.integrity=String(A.integrity)}if(A.keepalive!==undefined){s.keepalive=Boolean(A.keepalive)}if(A.method!==undefined){let e=A.method;if(!E(e)){throw new TypeError(`'${e}' is not a valid HTTP method.`)}if(h.has(e.toUpperCase())){throw new TypeError(`'${e}' HTTP method is unsupported.`)}e=C[e]??u(e);s.method=e}if(A.signal!==undefined){I=A.signal}this[k]=s;const w=new AbortController;this[D]=w.signal;this[D][S]=this[S];if(I!=null){if(!I||typeof I.aborted!=="boolean"||typeof I.addEventListener!=="function"){throw new TypeError("Failed to construct 'Request': member signal is not of type AbortSignal.")}if(I.aborted){w.abort(I.reason)}else{this[P]=w;const e=new WeakRef(w);const abort=function(){const A=e.deref();if(A!==undefined){A.abort(this.reason)}};try{if(typeof M==="function"&&M(I)===O){H(100,I)}else if(Y(I,"abort").length>=O){H(100,I)}}catch{}g.addAbortListener(I,abort);J.register(w,{signal:I,abort:abort})}}this[b]=new n(G);this[b][L]=s.headersList;this[b][F]="request";this[b][S]=this[S];if(y==="no-cors"){if(!B.has(s.method)){throw new TypeError(`'${s.method} is unsupported in no-cors mode.`)}this[b][F]="request-no-cors"}if(m){const e=this[b][L];const t=A.headers!==undefined?A.headers:new a(e);e.clear();if(t instanceof a){for(const[A,r]of t){e.append(A,r)}e.cookies=t.cookies}else{i(this[b],t)}}const R=e instanceof Request?e[k].body:null;if((A.body!=null||R!=null)&&(s.method==="GET"||s.method==="HEAD")){throw new TypeError("Request with GET/HEAD method cannot have body.")}let U=null;if(A.body!=null){const[e,t]=r(A.body,s.keepalive);U=e;if(t&&!this[b][L].contains("content-type")){this[b].append("content-type",t)}}const V=U??R;if(V!=null&&V.source==null){if(U!=null&&A.duplex==null){throw new TypeError("RequestInit: duplex option is required when sending a body.")}if(s.mode!=="same-origin"&&s.mode!=="cors"){throw new TypeError('If request is made from ReadableStream, mode should be "same-origin" or "cors"')}s.useCORSPreflightFlag=true}let x=V;if(U==null&&R!=null){if(g.isDisturbed(R.stream)||R.stream.locked){throw new TypeError("Cannot construct a Request with a Request object that has already been used.")}if(!_){_=t(3774).TransformStream}const e=new _;R.stream.pipeThrough(e);x={source:R.source,length:R.length,stream:e.readable}}this[k].body=x}get method(){T.brandCheck(this,Request);return this[k].method}get url(){T.brandCheck(this,Request);return U(this[k].url)}get headers(){T.brandCheck(this,Request);return this[b]}get destination(){T.brandCheck(this,Request);return this[k].destination}get referrer(){T.brandCheck(this,Request);if(this[k].referrer==="no-referrer"){return""}if(this[k].referrer==="client"){return"about:client"}return this[k].referrer.toString()}get referrerPolicy(){T.brandCheck(this,Request);return this[k].referrerPolicy}get mode(){T.brandCheck(this,Request);return this[k].mode}get credentials(){return this[k].credentials}get cache(){T.brandCheck(this,Request);return this[k].cache}get redirect(){T.brandCheck(this,Request);return this[k].redirect}get integrity(){T.brandCheck(this,Request);return this[k].integrity}get keepalive(){T.brandCheck(this,Request);return this[k].keepalive}get isReloadNavigation(){T.brandCheck(this,Request);return this[k].reloadNavigation}get isHistoryNavigation(){T.brandCheck(this,Request);return this[k].historyNavigation}get signal(){T.brandCheck(this,Request);return this[D]}get body(){T.brandCheck(this,Request);return this[k].body?this[k].body.stream:null}get bodyUsed(){T.brandCheck(this,Request);return!!this[k].body&&g.isDisturbed(this[k].body.stream)}get duplex(){T.brandCheck(this,Request);return"half"}clone(){T.brandCheck(this,Request);if(this.bodyUsed||this.body?.locked){throw new TypeError("unusable")}const e=cloneRequest(this[k]);const A=new Request(G);A[k]=e;A[S]=this[S];A[b]=new n(G);A[b][L]=e.headersList;A[b][F]=this[b][F];A[b][S]=this[b][S];const t=new AbortController;if(this.signal.aborted){t.abort(this.signal.reason)}else{g.addAbortListener(this.signal,(()=>{t.abort(this.signal.reason)}))}A[D]=t.signal;return A}}s(Request);function makeRequest(e){const A={method:"GET",localURLsOnly:false,unsafeRequest:false,body:null,client:null,reservedClient:null,replacesClientId:"",window:"client",keepalive:false,serviceWorkers:"all",initiator:"",destination:"",priority:null,origin:"client",policyContainer:"client",referrer:"client",referrerPolicy:"",mode:"no-cors",useCORSPreflightFlag:false,credentials:"same-origin",useCredentials:false,cache:"default",redirect:"follow",integrity:"",cryptoGraphicsNonceMetadata:"",parserMetadata:"",reloadNavigation:false,historyNavigation:false,userActivation:false,taintedOrigin:false,redirectCount:0,responseTainting:"basic",preventNoCacheCacheControlHeaderModification:false,done:false,timingAllowFailed:false,...e,headersList:e.headersList?new a(e.headersList):new a};A.url=A.urlList[0];return A}function cloneRequest(e){const A=makeRequest({...e,body:null});if(e.body!=null){A.body=o(e.body)}return A}Object.defineProperties(Request.prototype,{method:R,url:R,headers:R,redirect:R,clone:R,signal:R,duplex:R,destination:R,body:R,bodyUsed:R,isHistoryNavigation:R,isReloadNavigation:R,keepalive:R,integrity:R,cache:R,credentials:R,attribute:R,referrerPolicy:R,referrer:R,mode:R,[Symbol.toStringTag]:{value:"Request",configurable:true}});T.converters.Request=T.interfaceConverter(Request);T.converters.RequestInfo=function(e){if(typeof e==="string"){return T.converters.USVString(e)}if(e instanceof Request){return T.converters.Request(e)}return T.converters.USVString(e)};T.converters.AbortSignal=T.interfaceConverter(AbortSignal);T.converters.RequestInit=T.dictionaryConverter([{key:"method",converter:T.converters.ByteString},{key:"headers",converter:T.converters.HeadersInit},{key:"body",converter:T.nullableConverter(T.converters.BodyInit)},{key:"referrer",converter:T.converters.USVString},{key:"referrerPolicy",converter:T.converters.DOMString,allowedValues:I},{key:"mode",converter:T.converters.DOMString,allowedValues:p},{key:"credentials",converter:T.converters.DOMString,allowedValues:m},{key:"cache",converter:T.converters.DOMString,allowedValues:y},{key:"redirect",converter:T.converters.DOMString,allowedValues:d},{key:"integrity",converter:T.converters.DOMString},{key:"keepalive",converter:T.converters.boolean},{key:"signal",converter:T.nullableConverter((e=>T.converters.AbortSignal(e,{strict:false})))},{key:"window",converter:T.converters.any},{key:"duplex",converter:T.converters.DOMString,allowedValues:w}]);e.exports={Request:Request,makeRequest:makeRequest}},7862:(e,A,t)=>{"use strict";const{Headers:r,HeadersList:s,fill:o}=t(1815);const{extractBody:n,cloneBody:i,mixinBody:a}=t(8689);const c=t(2806);const{kEnumerableProperty:g}=c;const{isValidReasonPhrase:E,isCancelled:l,isAborted:u,isBlobLike:Q,serializeJavascriptValueToJSONString:C,isErrorLike:h,isomorphicEncode:B}=t(9913);const{redirectStatusSet:I,nullBodyStatus:d,DOMException:p}=t(6040);const{kState:m,kHeaders:y,kGuard:w,kRealm:R}=t(648);const{webidl:b}=t(6684);const{FormData:D}=t(1499);const{getGlobalOrigin:k}=t(574);const{URLSerializer:F}=t(7160);const{kHeadersList:S,kConstruct:T}=t(7781);const N=t(2613);const{types:U}=t(9023);const L=globalThis.ReadableStream||t(3774).ReadableStream;const G=new TextEncoder("utf-8");class Response{static error(){const e={settingsObject:{}};const A=new Response;A[m]=makeNetworkError();A[R]=e;A[y][S]=A[m].headersList;A[y][w]="immutable";A[y][R]=e;return A}static json(e,A={}){b.argumentLengthCheck(arguments,1,{header:"Response.json"});if(A!==null){A=b.converters.ResponseInit(A)}const t=G.encode(C(e));const r=n(t);const s={settingsObject:{}};const o=new Response;o[R]=s;o[y][w]="response";o[y][R]=s;initializeResponse(o,A,{body:r[0],type:"application/json"});return o}static redirect(e,A=302){const t={settingsObject:{}};b.argumentLengthCheck(arguments,1,{header:"Response.redirect"});e=b.converters.USVString(e);A=b.converters["unsigned short"](A);let r;try{r=new URL(e,k())}catch(A){throw Object.assign(new TypeError("Failed to parse URL from "+e),{cause:A})}if(!I.has(A)){throw new RangeError("Invalid status code "+A)}const s=new Response;s[R]=t;s[y][w]="immutable";s[y][R]=t;s[m].status=A;const o=B(F(r));s[m].headersList.append("location",o);return s}constructor(e=null,A={}){if(e!==null){e=b.converters.BodyInit(e)}A=b.converters.ResponseInit(A);this[R]={settingsObject:{}};this[m]=makeResponse({});this[y]=new r(T);this[y][w]="response";this[y][S]=this[m].headersList;this[y][R]=this[R];let t=null;if(e!=null){const[A,r]=n(e);t={body:A,type:r}}initializeResponse(this,A,t)}get type(){b.brandCheck(this,Response);return this[m].type}get url(){b.brandCheck(this,Response);const e=this[m].urlList;const A=e[e.length-1]??null;if(A===null){return""}return F(A,true)}get redirected(){b.brandCheck(this,Response);return this[m].urlList.length>1}get status(){b.brandCheck(this,Response);return this[m].status}get ok(){b.brandCheck(this,Response);return this[m].status>=200&&this[m].status<=299}get statusText(){b.brandCheck(this,Response);return this[m].statusText}get headers(){b.brandCheck(this,Response);return this[y]}get body(){b.brandCheck(this,Response);return this[m].body?this[m].body.stream:null}get bodyUsed(){b.brandCheck(this,Response);return!!this[m].body&&c.isDisturbed(this[m].body.stream)}clone(){b.brandCheck(this,Response);if(this.bodyUsed||this.body&&this.body.locked){throw b.errors.exception({header:"Response.clone",message:"Body has already been consumed."})}const e=cloneResponse(this[m]);const A=new Response;A[m]=e;A[R]=this[R];A[y][S]=e.headersList;A[y][w]=this[y][w];A[y][R]=this[y][R];return A}}a(Response);Object.defineProperties(Response.prototype,{type:g,url:g,status:g,ok:g,redirected:g,statusText:g,headers:g,clone:g,body:g,bodyUsed:g,[Symbol.toStringTag]:{value:"Response",configurable:true}});Object.defineProperties(Response,{json:g,redirect:g,error:g});function cloneResponse(e){if(e.internalResponse){return filterResponse(cloneResponse(e.internalResponse),e.type)}const A=makeResponse({...e,body:null});if(e.body!=null){A.body=i(e.body)}return A}function makeResponse(e){return{aborted:false,rangeRequested:false,timingAllowPassed:false,requestIncludesCredentials:false,type:"default",status:200,timingInfo:null,cacheState:"",statusText:"",...e,headersList:e.headersList?new s(e.headersList):new s,urlList:e.urlList?[...e.urlList]:[]}}function makeNetworkError(e){const A=h(e);return makeResponse({type:"error",status:0,error:A?e:new Error(e?String(e):e),aborted:e&&e.name==="AbortError"})}function makeFilteredResponse(e,A){A={internalResponse:e,...A};return new Proxy(e,{get(e,t){return t in A?A[t]:e[t]},set(e,t,r){N(!(t in A));e[t]=r;return true}})}function filterResponse(e,A){if(A==="basic"){return makeFilteredResponse(e,{type:"basic",headersList:e.headersList})}else if(A==="cors"){return makeFilteredResponse(e,{type:"cors",headersList:e.headersList})}else if(A==="opaque"){return makeFilteredResponse(e,{type:"opaque",urlList:Object.freeze([]),status:0,statusText:"",body:null})}else if(A==="opaqueredirect"){return makeFilteredResponse(e,{type:"opaqueredirect",status:0,statusText:"",headersList:[],body:null})}else{N(false)}}function makeAppropriateNetworkError(e,A=null){N(l(e));return u(e)?makeNetworkError(Object.assign(new p("The operation was aborted.","AbortError"),{cause:A})):makeNetworkError(Object.assign(new p("Request was cancelled."),{cause:A}))}function initializeResponse(e,A,t){if(A.status!==null&&(A.status<200||A.status>599)){throw new RangeError('init["status"] must be in the range of 200 to 599, inclusive.')}if("statusText"in A&&A.statusText!=null){if(!E(String(A.statusText))){throw new TypeError("Invalid statusText")}}if("status"in A&&A.status!=null){e[m].status=A.status}if("statusText"in A&&A.statusText!=null){e[m].statusText=A.statusText}if("headers"in A&&A.headers!=null){o(e[y],A.headers)}if(t){if(d.includes(e.status)){throw b.errors.exception({header:"Response constructor",message:"Invalid response status code "+e.status})}e[m].body=t.body;if(t.type!=null&&!e[m].headersList.contains("Content-Type")){e[m].headersList.append("content-type",t.type)}}}b.converters.ReadableStream=b.interfaceConverter(L);b.converters.FormData=b.interfaceConverter(D);b.converters.URLSearchParams=b.interfaceConverter(URLSearchParams);b.converters.XMLHttpRequestBodyInit=function(e){if(typeof e==="string"){return b.converters.USVString(e)}if(Q(e)){return b.converters.Blob(e,{strict:false})}if(U.isArrayBuffer(e)||U.isTypedArray(e)||U.isDataView(e)){return b.converters.BufferSource(e)}if(c.isFormDataLike(e)){return b.converters.FormData(e,{strict:false})}if(e instanceof URLSearchParams){return b.converters.URLSearchParams(e)}return b.converters.DOMString(e)};b.converters.BodyInit=function(e){if(e instanceof L){return b.converters.ReadableStream(e)}if(e?.[Symbol.asyncIterator]){return e}return b.converters.XMLHttpRequestBodyInit(e)};b.converters.ResponseInit=b.dictionaryConverter([{key:"status",converter:b.converters["unsigned short"],defaultValue:200},{key:"statusText",converter:b.converters.ByteString,defaultValue:""},{key:"headers",converter:b.converters.HeadersInit}]);e.exports={makeNetworkError:makeNetworkError,makeResponse:makeResponse,makeAppropriateNetworkError:makeAppropriateNetworkError,filterResponse:filterResponse,Response:Response,cloneResponse:cloneResponse}},648:e=>{"use strict";e.exports={kUrl:Symbol("url"),kHeaders:Symbol("headers"),kSignal:Symbol("signal"),kState:Symbol("state"),kGuard:Symbol("guard"),kRealm:Symbol("realm")}},9913:(e,A,t)=>{"use strict";const{redirectStatusSet:r,referrerPolicySet:s,badPortsSet:o}=t(6040);const{getGlobalOrigin:n}=t(574);const{performance:i}=t(2987);const{isBlobLike:a,toUSVString:c,ReadableStreamFrom:g}=t(2806);const E=t(2613);const{isUint8Array:l}=t(8253);let u=[];let Q;try{Q=t(6982);const e=["sha256","sha384","sha512"];u=Q.getHashes().filter((A=>e.includes(A)))}catch{}function responseURL(e){const A=e.urlList;const t=A.length;return t===0?null:A[t-1].toString()}function responseLocationURL(e,A){if(!r.has(e.status)){return null}let t=e.headersList.get("location");if(t!==null&&isValidHeaderValue(t)){t=new URL(t,responseURL(e))}if(t&&!t.hash){t.hash=A}return t}function requestCurrentURL(e){return e.urlList[e.urlList.length-1]}function requestBadPort(e){const A=requestCurrentURL(e);if(urlIsHttpHttpsScheme(A)&&o.has(A.port)){return"blocked"}return"allowed"}function isErrorLike(e){return e instanceof Error||(e?.constructor?.name==="Error"||e?.constructor?.name==="DOMException")}function isValidReasonPhrase(e){for(let A=0;A=32&&t<=126||t>=128&&t<=255)){return false}}return true}function isTokenCharCode(e){switch(e){case 34:case 40:case 41:case 44:case 47:case 58:case 59:case 60:case 61:case 62:case 63:case 64:case 91:case 92:case 93:case 123:case 125:return false;default:return e>=33&&e<=126}}function isValidHTTPToken(e){if(e.length===0){return false}for(let A=0;A0){for(let e=r.length;e!==0;e--){const A=r[e-1].trim();if(s.has(A)){o=A;break}}}if(o!==""){e.referrerPolicy=o}}function crossOriginResourcePolicyCheck(){return"allowed"}function corsCheck(){return"success"}function TAOCheck(){return"success"}function appendFetchMetadata(e){let A=null;A=e.mode;e.headersList.set("sec-fetch-mode",A)}function appendRequestOriginHeader(e){let A=e.origin;if(e.responseTainting==="cors"||e.mode==="websocket"){if(A){e.headersList.append("origin",A)}}else if(e.method!=="GET"&&e.method!=="HEAD"){switch(e.referrerPolicy){case"no-referrer":A=null;break;case"no-referrer-when-downgrade":case"strict-origin":case"strict-origin-when-cross-origin":if(e.origin&&urlHasHttpsScheme(e.origin)&&!urlHasHttpsScheme(requestCurrentURL(e))){A=null}break;case"same-origin":if(!sameOrigin(e,requestCurrentURL(e))){A=null}break;default:}if(A){e.headersList.append("origin",A)}}}function coarsenedSharedCurrentTime(e){return i.now()}function createOpaqueTimingInfo(e){return{startTime:e.startTime??0,redirectStartTime:0,redirectEndTime:0,postRedirectStartTime:e.startTime??0,finalServiceWorkerStartTime:0,finalNetworkResponseStartTime:0,finalNetworkRequestStartTime:0,endTime:0,encodedBodySize:0,decodedBodySize:0,finalConnectionTimingInfo:null}}function makePolicyContainer(){return{referrerPolicy:"strict-origin-when-cross-origin"}}function clonePolicyContainer(e){return{referrerPolicy:e.referrerPolicy}}function determineRequestsReferrer(e){const A=e.referrerPolicy;E(A);let t=null;if(e.referrer==="client"){const e=n();if(!e||e.origin==="null"){return"no-referrer"}t=new URL(e)}else if(e.referrer instanceof URL){t=e.referrer}let r=stripURLForReferrer(t);const s=stripURLForReferrer(t,true);if(r.toString().length>4096){r=s}const o=sameOrigin(e,r);const i=isURLPotentiallyTrustworthy(r)&&!isURLPotentiallyTrustworthy(e.url);switch(A){case"origin":return s!=null?s:stripURLForReferrer(t,true);case"unsafe-url":return r;case"same-origin":return o?s:"no-referrer";case"origin-when-cross-origin":return o?r:s;case"strict-origin-when-cross-origin":{const A=requestCurrentURL(e);if(sameOrigin(r,A)){return r}if(isURLPotentiallyTrustworthy(r)&&!isURLPotentiallyTrustworthy(A)){return"no-referrer"}return s}case"strict-origin":case"no-referrer-when-downgrade":default:return i?"no-referrer":s}}function stripURLForReferrer(e,A){E(e instanceof URL);if(e.protocol==="file:"||e.protocol==="about:"||e.protocol==="blank:"){return"no-referrer"}e.username="";e.password="";e.hash="";if(A){e.pathname="";e.search=""}return e}function isURLPotentiallyTrustworthy(e){if(!(e instanceof URL)){return false}if(e.href==="about:blank"||e.href==="about:srcdoc"){return true}if(e.protocol==="data:")return true;if(e.protocol==="file:")return true;return isOriginPotentiallyTrustworthy(e.origin);function isOriginPotentiallyTrustworthy(e){if(e==null||e==="null")return false;const A=new URL(e);if(A.protocol==="https:"||A.protocol==="wss:"){return true}if(/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(A.hostname)||(A.hostname==="localhost"||A.hostname.includes("localhost."))||A.hostname.endsWith(".localhost")){return true}return false}}function bytesMatch(e,A){if(Q===undefined){return true}const t=parseMetadata(A);if(t==="no metadata"){return true}if(t.length===0){return true}const r=getStrongestMetadata(t);const s=filterMetadataListByAlgorithm(t,r);for(const A of s){const t=A.algo;const r=A.hash;let s=Q.createHash(t).update(e).digest("base64");if(s[s.length-1]==="="){if(s[s.length-2]==="="){s=s.slice(0,-2)}else{s=s.slice(0,-1)}}if(compareBase64Mixed(s,r)){return true}}return false}const C=/(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i;function parseMetadata(e){const A=[];let t=true;for(const r of e.split(" ")){t=false;const e=C.exec(r);if(e===null||e.groups===undefined||e.groups.algo===undefined){continue}const s=e.groups.algo.toLowerCase();if(u.includes(s)){A.push(e.groups)}}if(t===true){return"no metadata"}return A}function getStrongestMetadata(e){let A=e[0].algo;if(A[3]==="5"){return A}for(let t=1;t{e=t;A=r}));return{promise:t,resolve:e,reject:A}}function isAborted(e){return e.controller.state==="aborted"}function isCancelled(e){return e.controller.state==="aborted"||e.controller.state==="terminated"}const h={delete:"DELETE",DELETE:"DELETE",get:"GET",GET:"GET",head:"HEAD",HEAD:"HEAD",options:"OPTIONS",OPTIONS:"OPTIONS",post:"POST",POST:"POST",put:"PUT",PUT:"PUT"};Object.setPrototypeOf(h,null);function normalizeMethod(e){return h[e.toLowerCase()]??e}function serializeJavascriptValueToJSONString(e){const A=JSON.stringify(e);if(A===undefined){throw new TypeError("Value is not JSON serializable")}E(typeof A==="string");return A}const B=Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));function makeIterator(e,A,t){const r={index:0,kind:t,target:e};const s={next(){if(Object.getPrototypeOf(this)!==s){throw new TypeError(`'next' called on an object that does not implement interface ${A} Iterator.`)}const{index:e,kind:t,target:o}=r;const n=o();const i=n.length;if(e>=i){return{value:undefined,done:true}}const a=n[e];r.index=e+1;return iteratorResult(a,t)},[Symbol.toStringTag]:`${A} Iterator`};Object.setPrototypeOf(s,B);return Object.setPrototypeOf({},s)}function iteratorResult(e,A){let t;switch(A){case"key":{t=e[0];break}case"value":{t=e[1];break}case"key+value":{t=e;break}}return{value:t,done:false}}async function fullyReadBody(e,A,t){const r=A;const s=t;let o;try{o=e.stream.getReader()}catch(e){s(e);return}try{const e=await readAllBytes(o);r(e)}catch(e){s(e)}}let I=globalThis.ReadableStream;function isReadableStreamLike(e){if(!I){I=t(3774).ReadableStream}return e instanceof I||e[Symbol.toStringTag]==="ReadableStream"&&typeof e.tee==="function"}const d=65535;function isomorphicDecode(e){if(e.lengthe+String.fromCharCode(A)),"")}function readableStreamClose(e){try{e.close()}catch(e){if(!e.message.includes("Controller is already closed")){throw e}}}function isomorphicEncode(e){for(let A=0;AObject.prototype.hasOwnProperty.call(e,A));e.exports={isAborted:isAborted,isCancelled:isCancelled,createDeferredPromise:createDeferredPromise,ReadableStreamFrom:g,toUSVString:c,tryUpgradeRequestToAPotentiallyTrustworthyURL:tryUpgradeRequestToAPotentiallyTrustworthyURL,coarsenedSharedCurrentTime:coarsenedSharedCurrentTime,determineRequestsReferrer:determineRequestsReferrer,makePolicyContainer:makePolicyContainer,clonePolicyContainer:clonePolicyContainer,appendFetchMetadata:appendFetchMetadata,appendRequestOriginHeader:appendRequestOriginHeader,TAOCheck:TAOCheck,corsCheck:corsCheck,crossOriginResourcePolicyCheck:crossOriginResourcePolicyCheck,createOpaqueTimingInfo:createOpaqueTimingInfo,setRequestReferrerPolicyOnRedirect:setRequestReferrerPolicyOnRedirect,isValidHTTPToken:isValidHTTPToken,requestBadPort:requestBadPort,requestCurrentURL:requestCurrentURL,responseURL:responseURL,responseLocationURL:responseLocationURL,isBlobLike:a,isURLPotentiallyTrustworthy:isURLPotentiallyTrustworthy,isValidReasonPhrase:isValidReasonPhrase,sameOrigin:sameOrigin,normalizeMethod:normalizeMethod,serializeJavascriptValueToJSONString:serializeJavascriptValueToJSONString,makeIterator:makeIterator,isValidHeaderName:isValidHeaderName,isValidHeaderValue:isValidHeaderValue,hasOwn:p,isErrorLike:isErrorLike,fullyReadBody:fullyReadBody,bytesMatch:bytesMatch,isReadableStreamLike:isReadableStreamLike,readableStreamClose:readableStreamClose,isomorphicEncode:isomorphicEncode,isomorphicDecode:isomorphicDecode,urlIsLocal:urlIsLocal,urlHasHttpsScheme:urlHasHttpsScheme,urlIsHttpHttpsScheme:urlIsHttpHttpsScheme,readAllBytes:readAllBytes,normalizeMethodRecord:h,parseMetadata:parseMetadata}},6684:(e,A,t)=>{"use strict";const{types:r}=t(9023);const{hasOwn:s,toUSVString:o}=t(9913);const n={};n.converters={};n.util={};n.errors={};n.errors.exception=function(e){return new TypeError(`${e.header}: ${e.message}`)};n.errors.conversionFailed=function(e){const A=e.types.length===1?"":" one of";const t=`${e.argument} could not be converted to`+`${A}: ${e.types.join(", ")}.`;return n.errors.exception({header:e.prefix,message:t})};n.errors.invalidArgument=function(e){return n.errors.exception({header:e.prefix,message:`"${e.value}" is an invalid ${e.type}.`})};n.brandCheck=function(e,A,t=undefined){if(t?.strict!==false&&!(e instanceof A)){throw new TypeError("Illegal invocation")}else{return e?.[Symbol.toStringTag]===A.prototype[Symbol.toStringTag]}};n.argumentLengthCheck=function({length:e},A,t){if(es){throw n.errors.exception({header:"Integer conversion",message:`Value must be between ${o}-${s}, got ${i}.`})}return i}if(!Number.isNaN(i)&&r.clamp===true){i=Math.min(Math.max(i,o),s);if(Math.floor(i)%2===0){i=Math.floor(i)}else{i=Math.ceil(i)}return i}if(Number.isNaN(i)||i===0&&Object.is(0,i)||i===Number.POSITIVE_INFINITY||i===Number.NEGATIVE_INFINITY){return 0}i=n.util.IntegerPart(i);i=i%Math.pow(2,A);if(t==="signed"&&i>=Math.pow(2,A)-1){return i-Math.pow(2,A)}return i};n.util.IntegerPart=function(e){const A=Math.floor(Math.abs(e));if(e<0){return-1*A}return A};n.sequenceConverter=function(e){return A=>{if(n.util.Type(A)!=="Object"){throw n.errors.exception({header:"Sequence",message:`Value of type ${n.util.Type(A)} is not an Object.`})}const t=A?.[Symbol.iterator]?.();const r=[];if(t===undefined||typeof t.next!=="function"){throw n.errors.exception({header:"Sequence",message:"Object is not an iterator."})}while(true){const{done:A,value:s}=t.next();if(A){break}r.push(e(s))}return r}};n.recordConverter=function(e,A){return t=>{if(n.util.Type(t)!=="Object"){throw n.errors.exception({header:"Record",message:`Value of type ${n.util.Type(t)} is not an Object.`})}const s={};if(!r.isProxy(t)){const r=Object.keys(t);for(const o of r){const r=e(o);const n=A(t[o]);s[r]=n}return s}const o=Reflect.ownKeys(t);for(const r of o){const o=Reflect.getOwnPropertyDescriptor(t,r);if(o?.enumerable){const o=e(r);const n=A(t[r]);s[o]=n}}return s}};n.interfaceConverter=function(e){return(A,t={})=>{if(t.strict!==false&&!(A instanceof e)){throw n.errors.exception({header:e.name,message:`Expected ${A} to be an instance of ${e.name}.`})}return A}};n.dictionaryConverter=function(e){return A=>{const t=n.util.Type(A);const r={};if(t==="Null"||t==="Undefined"){return r}else if(t!=="Object"){throw n.errors.exception({header:"Dictionary",message:`Expected ${A} to be one of: Null, Undefined, Object.`})}for(const t of e){const{key:e,defaultValue:o,required:i,converter:a}=t;if(i===true){if(!s(A,e)){throw n.errors.exception({header:"Dictionary",message:`Missing required key "${e}".`})}}let c=A[e];const g=s(t,"defaultValue");if(g&&c!==null){c=c??o}if(i||g||c!==undefined){c=a(c);if(t.allowedValues&&!t.allowedValues.includes(c)){throw n.errors.exception({header:"Dictionary",message:`${c} is not an accepted type. Expected one of ${t.allowedValues.join(", ")}.`})}r[e]=c}}return r}};n.nullableConverter=function(e){return A=>{if(A===null){return A}return e(A)}};n.converters.DOMString=function(e,A={}){if(e===null&&A.legacyNullToEmptyString){return""}if(typeof e==="symbol"){throw new TypeError("Could not convert argument of type symbol to string.")}return String(e)};n.converters.ByteString=function(e){const A=n.converters.DOMString(e);for(let e=0;e255){throw new TypeError("Cannot convert argument to a ByteString because the character at "+`index ${e} has a value of ${A.charCodeAt(e)} which is greater than 255.`)}}return A};n.converters.USVString=o;n.converters.boolean=function(e){const A=Boolean(e);return A};n.converters.any=function(e){return e};n.converters["long long"]=function(e){const A=n.util.ConvertToInt(e,64,"signed");return A};n.converters["unsigned long long"]=function(e){const A=n.util.ConvertToInt(e,64,"unsigned");return A};n.converters["unsigned long"]=function(e){const A=n.util.ConvertToInt(e,32,"unsigned");return A};n.converters["unsigned short"]=function(e,A){const t=n.util.ConvertToInt(e,16,"unsigned",A);return t};n.converters.ArrayBuffer=function(e,A={}){if(n.util.Type(e)!=="Object"||!r.isAnyArrayBuffer(e)){throw n.errors.conversionFailed({prefix:`${e}`,argument:`${e}`,types:["ArrayBuffer"]})}if(A.allowShared===false&&r.isSharedArrayBuffer(e)){throw n.errors.exception({header:"ArrayBuffer",message:"SharedArrayBuffer is not allowed."})}return e};n.converters.TypedArray=function(e,A,t={}){if(n.util.Type(e)!=="Object"||!r.isTypedArray(e)||e.constructor.name!==A.name){throw n.errors.conversionFailed({prefix:`${A.name}`,argument:`${e}`,types:[A.name]})}if(t.allowShared===false&&r.isSharedArrayBuffer(e.buffer)){throw n.errors.exception({header:"ArrayBuffer",message:"SharedArrayBuffer is not allowed."})}return e};n.converters.DataView=function(e,A={}){if(n.util.Type(e)!=="Object"||!r.isDataView(e)){throw n.errors.exception({header:"DataView",message:"Object is not a DataView."})}if(A.allowShared===false&&r.isSharedArrayBuffer(e.buffer)){throw n.errors.exception({header:"ArrayBuffer",message:"SharedArrayBuffer is not allowed."})}return e};n.converters.BufferSource=function(e,A={}){if(r.isAnyArrayBuffer(e)){return n.converters.ArrayBuffer(e,A)}if(r.isTypedArray(e)){return n.converters.TypedArray(e,e.constructor)}if(r.isDataView(e)){return n.converters.DataView(e,A)}throw new TypeError(`Could not convert ${e} to a BufferSource.`)};n.converters["sequence"]=n.sequenceConverter(n.converters.ByteString);n.converters["sequence>"]=n.sequenceConverter(n.converters["sequence"]);n.converters["record"]=n.recordConverter(n.converters.ByteString,n.converters.ByteString);e.exports={webidl:n}},8450:e=>{"use strict";function getEncoding(e){if(!e){return"failure"}switch(e.trim().toLowerCase()){case"unicode-1-1-utf-8":case"unicode11utf8":case"unicode20utf8":case"utf-8":case"utf8":case"x-unicode20utf8":return"UTF-8";case"866":case"cp866":case"csibm866":case"ibm866":return"IBM866";case"csisolatin2":case"iso-8859-2":case"iso-ir-101":case"iso8859-2":case"iso88592":case"iso_8859-2":case"iso_8859-2:1987":case"l2":case"latin2":return"ISO-8859-2";case"csisolatin3":case"iso-8859-3":case"iso-ir-109":case"iso8859-3":case"iso88593":case"iso_8859-3":case"iso_8859-3:1988":case"l3":case"latin3":return"ISO-8859-3";case"csisolatin4":case"iso-8859-4":case"iso-ir-110":case"iso8859-4":case"iso88594":case"iso_8859-4":case"iso_8859-4:1988":case"l4":case"latin4":return"ISO-8859-4";case"csisolatincyrillic":case"cyrillic":case"iso-8859-5":case"iso-ir-144":case"iso8859-5":case"iso88595":case"iso_8859-5":case"iso_8859-5:1988":return"ISO-8859-5";case"arabic":case"asmo-708":case"csiso88596e":case"csiso88596i":case"csisolatinarabic":case"ecma-114":case"iso-8859-6":case"iso-8859-6-e":case"iso-8859-6-i":case"iso-ir-127":case"iso8859-6":case"iso88596":case"iso_8859-6":case"iso_8859-6:1987":return"ISO-8859-6";case"csisolatingreek":case"ecma-118":case"elot_928":case"greek":case"greek8":case"iso-8859-7":case"iso-ir-126":case"iso8859-7":case"iso88597":case"iso_8859-7":case"iso_8859-7:1987":case"sun_eu_greek":return"ISO-8859-7";case"csiso88598e":case"csisolatinhebrew":case"hebrew":case"iso-8859-8":case"iso-8859-8-e":case"iso-ir-138":case"iso8859-8":case"iso88598":case"iso_8859-8":case"iso_8859-8:1988":case"visual":return"ISO-8859-8";case"csiso88598i":case"iso-8859-8-i":case"logical":return"ISO-8859-8-I";case"csisolatin6":case"iso-8859-10":case"iso-ir-157":case"iso8859-10":case"iso885910":case"l6":case"latin6":return"ISO-8859-10";case"iso-8859-13":case"iso8859-13":case"iso885913":return"ISO-8859-13";case"iso-8859-14":case"iso8859-14":case"iso885914":return"ISO-8859-14";case"csisolatin9":case"iso-8859-15":case"iso8859-15":case"iso885915":case"iso_8859-15":case"l9":return"ISO-8859-15";case"iso-8859-16":return"ISO-8859-16";case"cskoi8r":case"koi":case"koi8":case"koi8-r":case"koi8_r":return"KOI8-R";case"koi8-ru":case"koi8-u":return"KOI8-U";case"csmacintosh":case"mac":case"macintosh":case"x-mac-roman":return"macintosh";case"iso-8859-11":case"iso8859-11":case"iso885911":case"tis-620":case"windows-874":return"windows-874";case"cp1250":case"windows-1250":case"x-cp1250":return"windows-1250";case"cp1251":case"windows-1251":case"x-cp1251":return"windows-1251";case"ansi_x3.4-1968":case"ascii":case"cp1252":case"cp819":case"csisolatin1":case"ibm819":case"iso-8859-1":case"iso-ir-100":case"iso8859-1":case"iso88591":case"iso_8859-1":case"iso_8859-1:1987":case"l1":case"latin1":case"us-ascii":case"windows-1252":case"x-cp1252":return"windows-1252";case"cp1253":case"windows-1253":case"x-cp1253":return"windows-1253";case"cp1254":case"csisolatin5":case"iso-8859-9":case"iso-ir-148":case"iso8859-9":case"iso88599":case"iso_8859-9":case"iso_8859-9:1989":case"l5":case"latin5":case"windows-1254":case"x-cp1254":return"windows-1254";case"cp1255":case"windows-1255":case"x-cp1255":return"windows-1255";case"cp1256":case"windows-1256":case"x-cp1256":return"windows-1256";case"cp1257":case"windows-1257":case"x-cp1257":return"windows-1257";case"cp1258":case"windows-1258":case"x-cp1258":return"windows-1258";case"x-mac-cyrillic":case"x-mac-ukrainian":return"x-mac-cyrillic";case"chinese":case"csgb2312":case"csiso58gb231280":case"gb2312":case"gb_2312":case"gb_2312-80":case"gbk":case"iso-ir-58":case"x-gbk":return"GBK";case"gb18030":return"gb18030";case"big5":case"big5-hkscs":case"cn-big5":case"csbig5":case"x-x-big5":return"Big5";case"cseucpkdfmtjapanese":case"euc-jp":case"x-euc-jp":return"EUC-JP";case"csiso2022jp":case"iso-2022-jp":return"ISO-2022-JP";case"csshiftjis":case"ms932":case"ms_kanji":case"shift-jis":case"shift_jis":case"sjis":case"windows-31j":case"x-sjis":return"Shift_JIS";case"cseuckr":case"csksc56011987":case"euc-kr":case"iso-ir-149":case"korean":case"ks_c_5601-1987":case"ks_c_5601-1989":case"ksc5601":case"ksc_5601":case"windows-949":return"EUC-KR";case"csiso2022kr":case"hz-gb-2312":case"iso-2022-cn":case"iso-2022-cn-ext":case"iso-2022-kr":case"replacement":return"replacement";case"unicodefffe":case"utf-16be":return"UTF-16BE";case"csunicode":case"iso-10646-ucs-2":case"ucs-2":case"unicode":case"unicodefeff":case"utf-16":case"utf-16le":return"UTF-16LE";case"x-user-defined":return"x-user-defined";default:return"failure"}}e.exports={getEncoding:getEncoding}},2150:(e,A,t)=>{"use strict";const{staticPropertyDescriptors:r,readOperation:s,fireAProgressEvent:o}=t(8439);const{kState:n,kError:i,kResult:a,kEvents:c,kAborted:g}=t(6794);const{webidl:E}=t(6684);const{kEnumerableProperty:l}=t(2806);class FileReader extends EventTarget{constructor(){super();this[n]="empty";this[a]=null;this[i]=null;this[c]={loadend:null,error:null,abort:null,load:null,progress:null,loadstart:null}}readAsArrayBuffer(e){E.brandCheck(this,FileReader);E.argumentLengthCheck(arguments,1,{header:"FileReader.readAsArrayBuffer"});e=E.converters.Blob(e,{strict:false});s(this,e,"ArrayBuffer")}readAsBinaryString(e){E.brandCheck(this,FileReader);E.argumentLengthCheck(arguments,1,{header:"FileReader.readAsBinaryString"});e=E.converters.Blob(e,{strict:false});s(this,e,"BinaryString")}readAsText(e,A=undefined){E.brandCheck(this,FileReader);E.argumentLengthCheck(arguments,1,{header:"FileReader.readAsText"});e=E.converters.Blob(e,{strict:false});if(A!==undefined){A=E.converters.DOMString(A)}s(this,e,"Text",A)}readAsDataURL(e){E.brandCheck(this,FileReader);E.argumentLengthCheck(arguments,1,{header:"FileReader.readAsDataURL"});e=E.converters.Blob(e,{strict:false});s(this,e,"DataURL")}abort(){if(this[n]==="empty"||this[n]==="done"){this[a]=null;return}if(this[n]==="loading"){this[n]="done";this[a]=null}this[g]=true;o("abort",this);if(this[n]!=="loading"){o("loadend",this)}}get readyState(){E.brandCheck(this,FileReader);switch(this[n]){case"empty":return this.EMPTY;case"loading":return this.LOADING;case"done":return this.DONE}}get result(){E.brandCheck(this,FileReader);return this[a]}get error(){E.brandCheck(this,FileReader);return this[i]}get onloadend(){E.brandCheck(this,FileReader);return this[c].loadend}set onloadend(e){E.brandCheck(this,FileReader);if(this[c].loadend){this.removeEventListener("loadend",this[c].loadend)}if(typeof e==="function"){this[c].loadend=e;this.addEventListener("loadend",e)}else{this[c].loadend=null}}get onerror(){E.brandCheck(this,FileReader);return this[c].error}set onerror(e){E.brandCheck(this,FileReader);if(this[c].error){this.removeEventListener("error",this[c].error)}if(typeof e==="function"){this[c].error=e;this.addEventListener("error",e)}else{this[c].error=null}}get onloadstart(){E.brandCheck(this,FileReader);return this[c].loadstart}set onloadstart(e){E.brandCheck(this,FileReader);if(this[c].loadstart){this.removeEventListener("loadstart",this[c].loadstart)}if(typeof e==="function"){this[c].loadstart=e;this.addEventListener("loadstart",e)}else{this[c].loadstart=null}}get onprogress(){E.brandCheck(this,FileReader);return this[c].progress}set onprogress(e){E.brandCheck(this,FileReader);if(this[c].progress){this.removeEventListener("progress",this[c].progress)}if(typeof e==="function"){this[c].progress=e;this.addEventListener("progress",e)}else{this[c].progress=null}}get onload(){E.brandCheck(this,FileReader);return this[c].load}set onload(e){E.brandCheck(this,FileReader);if(this[c].load){this.removeEventListener("load",this[c].load)}if(typeof e==="function"){this[c].load=e;this.addEventListener("load",e)}else{this[c].load=null}}get onabort(){E.brandCheck(this,FileReader);return this[c].abort}set onabort(e){E.brandCheck(this,FileReader);if(this[c].abort){this.removeEventListener("abort",this[c].abort)}if(typeof e==="function"){this[c].abort=e;this.addEventListener("abort",e)}else{this[c].abort=null}}}FileReader.EMPTY=FileReader.prototype.EMPTY=0;FileReader.LOADING=FileReader.prototype.LOADING=1;FileReader.DONE=FileReader.prototype.DONE=2;Object.defineProperties(FileReader.prototype,{EMPTY:r,LOADING:r,DONE:r,readAsArrayBuffer:l,readAsBinaryString:l,readAsText:l,readAsDataURL:l,abort:l,readyState:l,result:l,error:l,onloadstart:l,onprogress:l,onload:l,onabort:l,onerror:l,onloadend:l,[Symbol.toStringTag]:{value:"FileReader",writable:false,enumerable:false,configurable:true}});Object.defineProperties(FileReader,{EMPTY:r,LOADING:r,DONE:r});e.exports={FileReader:FileReader}},7038:(e,A,t)=>{"use strict";const{webidl:r}=t(6684);const s=Symbol("ProgressEvent state");class ProgressEvent extends Event{constructor(e,A={}){e=r.converters.DOMString(e);A=r.converters.ProgressEventInit(A??{});super(e,A);this[s]={lengthComputable:A.lengthComputable,loaded:A.loaded,total:A.total}}get lengthComputable(){r.brandCheck(this,ProgressEvent);return this[s].lengthComputable}get loaded(){r.brandCheck(this,ProgressEvent);return this[s].loaded}get total(){r.brandCheck(this,ProgressEvent);return this[s].total}}r.converters.ProgressEventInit=r.dictionaryConverter([{key:"lengthComputable",converter:r.converters.boolean,defaultValue:false},{key:"loaded",converter:r.converters["unsigned long long"],defaultValue:0},{key:"total",converter:r.converters["unsigned long long"],defaultValue:0},{key:"bubbles",converter:r.converters.boolean,defaultValue:false},{key:"cancelable",converter:r.converters.boolean,defaultValue:false},{key:"composed",converter:r.converters.boolean,defaultValue:false}]);e.exports={ProgressEvent:ProgressEvent}},6794:e=>{"use strict";e.exports={kState:Symbol("FileReader state"),kResult:Symbol("FileReader result"),kError:Symbol("FileReader error"),kLastProgressEventFired:Symbol("FileReader last progress event fired timestamp"),kEvents:Symbol("FileReader events"),kAborted:Symbol("FileReader aborted")}},8439:(e,A,t)=>{"use strict";const{kState:r,kError:s,kResult:o,kAborted:n,kLastProgressEventFired:i}=t(6794);const{ProgressEvent:a}=t(7038);const{getEncoding:c}=t(8450);const{DOMException:g}=t(6040);const{serializeAMimeType:E,parseMIMEType:l}=t(7160);const{types:u}=t(9023);const{StringDecoder:Q}=t(3193);const{btoa:C}=t(181);const h={enumerable:true,writable:false,configurable:false};function readOperation(e,A,t,a){if(e[r]==="loading"){throw new g("Invalid state","InvalidStateError")}e[r]="loading";e[o]=null;e[s]=null;const c=A.stream();const E=c.getReader();const l=[];let Q=E.read();let C=true;(async()=>{while(!e[n]){try{const{done:c,value:g}=await Q;if(C&&!e[n]){queueMicrotask((()=>{fireAProgressEvent("loadstart",e)}))}C=false;if(!c&&u.isUint8Array(g)){l.push(g);if((e[i]===undefined||Date.now()-e[i]>=50)&&!e[n]){e[i]=Date.now();queueMicrotask((()=>{fireAProgressEvent("progress",e)}))}Q=E.read()}else if(c){queueMicrotask((()=>{e[r]="done";try{const r=packageData(l,t,A.type,a);if(e[n]){return}e[o]=r;fireAProgressEvent("load",e)}catch(A){e[s]=A;fireAProgressEvent("error",e)}if(e[r]!=="loading"){fireAProgressEvent("loadend",e)}}));break}}catch(A){if(e[n]){return}queueMicrotask((()=>{e[r]="done";e[s]=A;fireAProgressEvent("error",e);if(e[r]!=="loading"){fireAProgressEvent("loadend",e)}}));break}}})()}function fireAProgressEvent(e,A){const t=new a(e,{bubbles:false,cancelable:false});A.dispatchEvent(t)}function packageData(e,A,t,r){switch(A){case"DataURL":{let A="data:";const r=l(t||"application/octet-stream");if(r!=="failure"){A+=E(r)}A+=";base64,";const s=new Q("latin1");for(const t of e){A+=C(s.write(t))}A+=C(s.end());return A}case"Text":{let A="failure";if(r){A=c(r)}if(A==="failure"&&t){const e=l(t);if(e!=="failure"){A=c(e.parameters.get("charset"))}}if(A==="failure"){A="UTF-8"}return decode(e,A)}case"ArrayBuffer":{const A=combineByteSequences(e);return A.buffer}case"BinaryString":{let A="";const t=new Q("latin1");for(const r of e){A+=t.write(r)}A+=t.end();return A}}}function decode(e,A){const t=combineByteSequences(e);const r=BOMSniffing(t);let s=0;if(r!==null){A=r;s=r==="UTF-8"?3:2}const o=t.slice(s);return new TextDecoder(A).decode(o)}function BOMSniffing(e){const[A,t,r]=e;if(A===239&&t===187&&r===191){return"UTF-8"}else if(A===254&&t===255){return"UTF-16BE"}else if(A===255&&t===254){return"UTF-16LE"}return null}function combineByteSequences(e){const A=e.reduce(((e,A)=>e+A.byteLength),0);let t=0;return e.reduce(((e,A)=>{e.set(A,t);t+=A.byteLength;return e}),new Uint8Array(A))}e.exports={staticPropertyDescriptors:h,readOperation:readOperation,fireAProgressEvent:fireAProgressEvent}},6875:(e,A,t)=>{"use strict";const r=Symbol.for("undici.globalDispatcher.1");const{InvalidArgumentError:s}=t(7221);const o=t(8787);if(getGlobalDispatcher()===undefined){setGlobalDispatcher(new o)}function setGlobalDispatcher(e){if(!e||typeof e.dispatch!=="function"){throw new s("Argument agent must implement Agent")}Object.defineProperty(globalThis,r,{value:e,writable:true,enumerable:false,configurable:false})}function getGlobalDispatcher(){return globalThis[r]}e.exports={setGlobalDispatcher:setGlobalDispatcher,getGlobalDispatcher:getGlobalDispatcher}},5658:e=>{"use strict";e.exports=class DecoratorHandler{constructor(e){this.handler=e}onConnect(...e){return this.handler.onConnect(...e)}onError(...e){return this.handler.onError(...e)}onUpgrade(...e){return this.handler.onUpgrade(...e)}onHeaders(...e){return this.handler.onHeaders(...e)}onData(...e){return this.handler.onData(...e)}onComplete(...e){return this.handler.onComplete(...e)}onBodySent(...e){return this.handler.onBodySent(...e)}}},8977:(e,A,t)=>{"use strict";const r=t(2806);const{kBodyUsed:s}=t(7781);const o=t(2613);const{InvalidArgumentError:n}=t(7221);const i=t(4434);const a=[300,301,302,303,307,308];const c=Symbol("body");class BodyAsyncIterable{constructor(e){this[c]=e;this[s]=false}async*[Symbol.asyncIterator](){o(!this[s],"disturbed");this[s]=true;yield*this[c]}}class RedirectHandler{constructor(e,A,t,a){if(A!=null&&(!Number.isInteger(A)||A<0)){throw new n("maxRedirections must be a positive number")}r.validateHandler(a,t.method,t.upgrade);this.dispatch=e;this.location=null;this.abort=null;this.opts={...t,maxRedirections:0};this.maxRedirections=A;this.handler=a;this.history=[];if(r.isStream(this.opts.body)){if(r.bodyLength(this.opts.body)===0){this.opts.body.on("data",(function(){o(false)}))}if(typeof this.opts.body.readableDidRead!=="boolean"){this.opts.body[s]=false;i.prototype.on.call(this.opts.body,"data",(function(){this[s]=true}))}}else if(this.opts.body&&typeof this.opts.body.pipeTo==="function"){this.opts.body=new BodyAsyncIterable(this.opts.body)}else if(this.opts.body&&typeof this.opts.body!=="string"&&!ArrayBuffer.isView(this.opts.body)&&r.isIterable(this.opts.body)){this.opts.body=new BodyAsyncIterable(this.opts.body)}}onConnect(e){this.abort=e;this.handler.onConnect(e,{history:this.history})}onUpgrade(e,A,t){this.handler.onUpgrade(e,A,t)}onError(e){this.handler.onError(e)}onHeaders(e,A,t,s){this.location=this.history.length>=this.maxRedirections||r.isDisturbed(this.opts.body)?null:parseLocation(e,A);if(this.opts.origin){this.history.push(new URL(this.opts.path,this.opts.origin))}if(!this.location){return this.handler.onHeaders(e,A,t,s)}const{origin:o,pathname:n,search:i}=r.parseURL(new URL(this.location,this.opts.origin&&new URL(this.opts.path,this.opts.origin)));const a=i?`${n}${i}`:n;this.opts.headers=cleanRequestHeaders(this.opts.headers,e===303,this.opts.origin!==o);this.opts.path=a;this.opts.origin=o;this.opts.maxRedirections=0;this.opts.query=null;if(e===303&&this.opts.method!=="HEAD"){this.opts.method="GET";this.opts.body=null}}onData(e){if(this.location){}else{return this.handler.onData(e)}}onComplete(e){if(this.location){this.location=null;this.abort=null;this.dispatch(this.opts,this)}else{this.handler.onComplete(e)}}onBodySent(e){if(this.handler.onBodySent){this.handler.onBodySent(e)}}}function parseLocation(e,A){if(a.indexOf(e)===-1){return null}for(let e=0;e{const r=t(2613);const{kRetryHandlerDefaultRetry:s}=t(7781);const{RequestRetryError:o}=t(7221);const{isDisturbed:n,parseHeaders:i,parseRangeHeader:a}=t(2806);function calculateRetryAfterHeader(e){const A=Date.now();const t=new Date(e).getTime()-A;return t}class RetryHandler{constructor(e,A){const{retryOptions:t,...r}=e;const{retry:o,maxRetries:n,maxTimeout:i,minTimeout:a,timeoutFactor:c,methods:g,errorCodes:E,retryAfter:l,statusCodes:u}=t??{};this.dispatch=A.dispatch;this.handler=A.handler;this.opts=r;this.abort=null;this.aborted=false;this.retryOpts={retry:o??RetryHandler[s],retryAfter:l??true,maxTimeout:i??30*1e3,timeout:a??500,timeoutFactor:c??2,maxRetries:n??5,methods:g??["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"],statusCodes:u??[500,502,503,504,429],errorCodes:E??["ECONNRESET","ECONNREFUSED","ENOTFOUND","ENETDOWN","ENETUNREACH","EHOSTDOWN","EHOSTUNREACH","EPIPE"]};this.retryCount=0;this.start=0;this.end=null;this.etag=null;this.resume=null;this.handler.onConnect((e=>{this.aborted=true;if(this.abort){this.abort(e)}else{this.reason=e}}))}onRequestSent(){if(this.handler.onRequestSent){this.handler.onRequestSent()}}onUpgrade(e,A,t){if(this.handler.onUpgrade){this.handler.onUpgrade(e,A,t)}}onConnect(e){if(this.aborted){e(this.reason)}else{this.abort=e}}onBodySent(e){if(this.handler.onBodySent)return this.handler.onBodySent(e)}static[s](e,{state:A,opts:t},r){const{statusCode:s,code:o,headers:n}=e;const{method:i,retryOptions:a}=t;const{maxRetries:c,timeout:g,maxTimeout:E,timeoutFactor:l,statusCodes:u,errorCodes:Q,methods:C}=a;let{counter:h,currentTimeout:B}=A;B=B!=null&&B>0?B:g;if(o&&o!=="UND_ERR_REQ_RETRY"&&o!=="UND_ERR_SOCKET"&&!Q.includes(o)){r(e);return}if(Array.isArray(C)&&!C.includes(i)){r(e);return}if(s!=null&&Array.isArray(u)&&!u.includes(s)){r(e);return}if(h>c){r(e);return}let I=n!=null&&n["retry-after"];if(I){I=Number(I);I=isNaN(I)?calculateRetryAfterHeader(I):I*1e3}const d=I>0?Math.min(I,E):Math.min(B*l**h,E);A.currentTimeout=d;setTimeout((()=>r(null)),d)}onHeaders(e,A,t,s){const n=i(A);this.retryCount+=1;if(e>=300){this.abort(new o("Request failed",e,{headers:n,count:this.retryCount}));return false}if(this.resume!=null){this.resume=null;if(e!==206){return true}const A=a(n["content-range"]);if(!A){this.abort(new o("Content-Range mismatch",e,{headers:n,count:this.retryCount}));return false}if(this.etag!=null&&this.etag!==n.etag){this.abort(new o("ETag mismatch",e,{headers:n,count:this.retryCount}));return false}const{start:s,size:i,end:c=i}=A;r(this.start===s,"content-range mismatch");r(this.end==null||this.end===c,"content-range mismatch");this.resume=t;return true}if(this.end==null){if(e===206){const o=a(n["content-range"]);if(o==null){return this.handler.onHeaders(e,A,t,s)}const{start:i,size:c,end:g=c}=o;r(i!=null&&Number.isFinite(i)&&this.start!==i,"content-range mismatch");r(Number.isFinite(i));r(g!=null&&Number.isFinite(g)&&this.end!==g,"invalid content-length");this.start=i;this.end=g}if(this.end==null){const e=n["content-length"];this.end=e!=null?Number(e):null}r(Number.isFinite(this.start));r(this.end==null||Number.isFinite(this.end),"invalid content-length");this.resume=t;this.etag=n.etag!=null?n.etag:null;return this.handler.onHeaders(e,A,t,s)}const c=new o("Request failed",e,{headers:n,count:this.retryCount});this.abort(c);return false}onData(e){this.start+=e.length;return this.handler.onData(e)}onComplete(e){this.retryCount=0;return this.handler.onComplete(e)}onError(e){if(this.aborted||n(this.opts.body)){return this.handler.onError(e)}this.retryOpts.retry(e,{state:{counter:this.retryCount++,currentTimeout:this.retryAfter},opts:{retryOptions:this.retryOpts,...this.opts}},onRetry.bind(this));function onRetry(e){if(e!=null||this.aborted||n(this.opts.body)){return this.handler.onError(e)}if(this.start!==0){this.opts={...this.opts,headers:{...this.opts.headers,range:`bytes=${this.start}-${this.end??""}`}}}try{this.dispatch(this.opts,this)}catch(e){this.handler.onError(e)}}}}e.exports=RetryHandler},9421:(e,A,t)=>{"use strict";const r=t(8977);function createRedirectInterceptor({maxRedirections:e}){return A=>function Intercept(t,s){const{maxRedirections:o=e}=t;if(!o){return A(t,s)}const n=new r(A,o,t,s);t={...t,maxRedirections:0};return A(t,n)}}e.exports=createRedirectInterceptor},5766:(e,A,t)=>{"use strict";Object.defineProperty(A,"__esModule",{value:true});A.SPECIAL_HEADERS=A.HEADER_STATE=A.MINOR=A.MAJOR=A.CONNECTION_TOKEN_CHARS=A.HEADER_CHARS=A.TOKEN=A.STRICT_TOKEN=A.HEX=A.URL_CHAR=A.STRICT_URL_CHAR=A.USERINFO_CHARS=A.MARK=A.ALPHANUM=A.NUM=A.HEX_MAP=A.NUM_MAP=A.ALPHA=A.FINISH=A.H_METHOD_MAP=A.METHOD_MAP=A.METHODS_RTSP=A.METHODS_ICE=A.METHODS_HTTP=A.METHODS=A.LENIENT_FLAGS=A.FLAGS=A.TYPE=A.ERROR=void 0;const r=t(8974);var s;(function(e){e[e["OK"]=0]="OK";e[e["INTERNAL"]=1]="INTERNAL";e[e["STRICT"]=2]="STRICT";e[e["LF_EXPECTED"]=3]="LF_EXPECTED";e[e["UNEXPECTED_CONTENT_LENGTH"]=4]="UNEXPECTED_CONTENT_LENGTH";e[e["CLOSED_CONNECTION"]=5]="CLOSED_CONNECTION";e[e["INVALID_METHOD"]=6]="INVALID_METHOD";e[e["INVALID_URL"]=7]="INVALID_URL";e[e["INVALID_CONSTANT"]=8]="INVALID_CONSTANT";e[e["INVALID_VERSION"]=9]="INVALID_VERSION";e[e["INVALID_HEADER_TOKEN"]=10]="INVALID_HEADER_TOKEN";e[e["INVALID_CONTENT_LENGTH"]=11]="INVALID_CONTENT_LENGTH";e[e["INVALID_CHUNK_SIZE"]=12]="INVALID_CHUNK_SIZE";e[e["INVALID_STATUS"]=13]="INVALID_STATUS";e[e["INVALID_EOF_STATE"]=14]="INVALID_EOF_STATE";e[e["INVALID_TRANSFER_ENCODING"]=15]="INVALID_TRANSFER_ENCODING";e[e["CB_MESSAGE_BEGIN"]=16]="CB_MESSAGE_BEGIN";e[e["CB_HEADERS_COMPLETE"]=17]="CB_HEADERS_COMPLETE";e[e["CB_MESSAGE_COMPLETE"]=18]="CB_MESSAGE_COMPLETE";e[e["CB_CHUNK_HEADER"]=19]="CB_CHUNK_HEADER";e[e["CB_CHUNK_COMPLETE"]=20]="CB_CHUNK_COMPLETE";e[e["PAUSED"]=21]="PAUSED";e[e["PAUSED_UPGRADE"]=22]="PAUSED_UPGRADE";e[e["PAUSED_H2_UPGRADE"]=23]="PAUSED_H2_UPGRADE";e[e["USER"]=24]="USER"})(s=A.ERROR||(A.ERROR={}));var o;(function(e){e[e["BOTH"]=0]="BOTH";e[e["REQUEST"]=1]="REQUEST";e[e["RESPONSE"]=2]="RESPONSE"})(o=A.TYPE||(A.TYPE={}));var n;(function(e){e[e["CONNECTION_KEEP_ALIVE"]=1]="CONNECTION_KEEP_ALIVE";e[e["CONNECTION_CLOSE"]=2]="CONNECTION_CLOSE";e[e["CONNECTION_UPGRADE"]=4]="CONNECTION_UPGRADE";e[e["CHUNKED"]=8]="CHUNKED";e[e["UPGRADE"]=16]="UPGRADE";e[e["CONTENT_LENGTH"]=32]="CONTENT_LENGTH";e[e["SKIPBODY"]=64]="SKIPBODY";e[e["TRAILING"]=128]="TRAILING";e[e["TRANSFER_ENCODING"]=512]="TRANSFER_ENCODING"})(n=A.FLAGS||(A.FLAGS={}));var i;(function(e){e[e["HEADERS"]=1]="HEADERS";e[e["CHUNKED_LENGTH"]=2]="CHUNKED_LENGTH";e[e["KEEP_ALIVE"]=4]="KEEP_ALIVE"})(i=A.LENIENT_FLAGS||(A.LENIENT_FLAGS={}));var a;(function(e){e[e["DELETE"]=0]="DELETE";e[e["GET"]=1]="GET";e[e["HEAD"]=2]="HEAD";e[e["POST"]=3]="POST";e[e["PUT"]=4]="PUT";e[e["CONNECT"]=5]="CONNECT";e[e["OPTIONS"]=6]="OPTIONS";e[e["TRACE"]=7]="TRACE";e[e["COPY"]=8]="COPY";e[e["LOCK"]=9]="LOCK";e[e["MKCOL"]=10]="MKCOL";e[e["MOVE"]=11]="MOVE";e[e["PROPFIND"]=12]="PROPFIND";e[e["PROPPATCH"]=13]="PROPPATCH";e[e["SEARCH"]=14]="SEARCH";e[e["UNLOCK"]=15]="UNLOCK";e[e["BIND"]=16]="BIND";e[e["REBIND"]=17]="REBIND";e[e["UNBIND"]=18]="UNBIND";e[e["ACL"]=19]="ACL";e[e["REPORT"]=20]="REPORT";e[e["MKACTIVITY"]=21]="MKACTIVITY";e[e["CHECKOUT"]=22]="CHECKOUT";e[e["MERGE"]=23]="MERGE";e[e["M-SEARCH"]=24]="M-SEARCH";e[e["NOTIFY"]=25]="NOTIFY";e[e["SUBSCRIBE"]=26]="SUBSCRIBE";e[e["UNSUBSCRIBE"]=27]="UNSUBSCRIBE";e[e["PATCH"]=28]="PATCH";e[e["PURGE"]=29]="PURGE";e[e["MKCALENDAR"]=30]="MKCALENDAR";e[e["LINK"]=31]="LINK";e[e["UNLINK"]=32]="UNLINK";e[e["SOURCE"]=33]="SOURCE";e[e["PRI"]=34]="PRI";e[e["DESCRIBE"]=35]="DESCRIBE";e[e["ANNOUNCE"]=36]="ANNOUNCE";e[e["SETUP"]=37]="SETUP";e[e["PLAY"]=38]="PLAY";e[e["PAUSE"]=39]="PAUSE";e[e["TEARDOWN"]=40]="TEARDOWN";e[e["GET_PARAMETER"]=41]="GET_PARAMETER";e[e["SET_PARAMETER"]=42]="SET_PARAMETER";e[e["REDIRECT"]=43]="REDIRECT";e[e["RECORD"]=44]="RECORD";e[e["FLUSH"]=45]="FLUSH"})(a=A.METHODS||(A.METHODS={}));A.METHODS_HTTP=[a.DELETE,a.GET,a.HEAD,a.POST,a.PUT,a.CONNECT,a.OPTIONS,a.TRACE,a.COPY,a.LOCK,a.MKCOL,a.MOVE,a.PROPFIND,a.PROPPATCH,a.SEARCH,a.UNLOCK,a.BIND,a.REBIND,a.UNBIND,a.ACL,a.REPORT,a.MKACTIVITY,a.CHECKOUT,a.MERGE,a["M-SEARCH"],a.NOTIFY,a.SUBSCRIBE,a.UNSUBSCRIBE,a.PATCH,a.PURGE,a.MKCALENDAR,a.LINK,a.UNLINK,a.PRI,a.SOURCE];A.METHODS_ICE=[a.SOURCE];A.METHODS_RTSP=[a.OPTIONS,a.DESCRIBE,a.ANNOUNCE,a.SETUP,a.PLAY,a.PAUSE,a.TEARDOWN,a.GET_PARAMETER,a.SET_PARAMETER,a.REDIRECT,a.RECORD,a.FLUSH,a.GET,a.POST];A.METHOD_MAP=r.enumToMap(a);A.H_METHOD_MAP={};Object.keys(A.METHOD_MAP).forEach((e=>{if(/^H/.test(e)){A.H_METHOD_MAP[e]=A.METHOD_MAP[e]}}));var c;(function(e){e[e["SAFE"]=0]="SAFE";e[e["SAFE_WITH_CB"]=1]="SAFE_WITH_CB";e[e["UNSAFE"]=2]="UNSAFE"})(c=A.FINISH||(A.FINISH={}));A.ALPHA=[];for(let e="A".charCodeAt(0);e<="Z".charCodeAt(0);e++){A.ALPHA.push(String.fromCharCode(e));A.ALPHA.push(String.fromCharCode(e+32))}A.NUM_MAP={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9};A.HEX_MAP={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15};A.NUM=["0","1","2","3","4","5","6","7","8","9"];A.ALPHANUM=A.ALPHA.concat(A.NUM);A.MARK=["-","_",".","!","~","*","'","(",")"];A.USERINFO_CHARS=A.ALPHANUM.concat(A.MARK).concat(["%",";",":","&","=","+","$",","]);A.STRICT_URL_CHAR=["!",'"',"$","%","&","'","(",")","*","+",",","-",".","/",":",";","<","=",">","@","[","\\","]","^","_","`","{","|","}","~"].concat(A.ALPHANUM);A.URL_CHAR=A.STRICT_URL_CHAR.concat(["\t","\f"]);for(let e=128;e<=255;e++){A.URL_CHAR.push(e)}A.HEX=A.NUM.concat(["a","b","c","d","e","f","A","B","C","D","E","F"]);A.STRICT_TOKEN=["!","#","$","%","&","'","*","+","-",".","^","_","`","|","~"].concat(A.ALPHANUM);A.TOKEN=A.STRICT_TOKEN.concat([" "]);A.HEADER_CHARS=["\t"];for(let e=32;e<=255;e++){if(e!==127){A.HEADER_CHARS.push(e)}}A.CONNECTION_TOKEN_CHARS=A.HEADER_CHARS.filter((e=>e!==44));A.MAJOR=A.NUM_MAP;A.MINOR=A.MAJOR;var g;(function(e){e[e["GENERAL"]=0]="GENERAL";e[e["CONNECTION"]=1]="CONNECTION";e[e["CONTENT_LENGTH"]=2]="CONTENT_LENGTH";e[e["TRANSFER_ENCODING"]=3]="TRANSFER_ENCODING";e[e["UPGRADE"]=4]="UPGRADE";e[e["CONNECTION_KEEP_ALIVE"]=5]="CONNECTION_KEEP_ALIVE";e[e["CONNECTION_CLOSE"]=6]="CONNECTION_CLOSE";e[e["CONNECTION_UPGRADE"]=7]="CONNECTION_UPGRADE";e[e["TRANSFER_ENCODING_CHUNKED"]=8]="TRANSFER_ENCODING_CHUNKED"})(g=A.HEADER_STATE||(A.HEADER_STATE={}));A.SPECIAL_HEADERS={connection:g.CONNECTION,"content-length":g.CONTENT_LENGTH,"proxy-connection":g.CONNECTION,"transfer-encoding":g.TRANSFER_ENCODING,upgrade:g.UPGRADE}},2108:e=>{e.exports="AGFzbQEAAAABMAhgAX8Bf2ADf39/AX9gBH9/f38Bf2AAAGADf39/AGABfwBgAn9/AGAGf39/f39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQACA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAA0ZFAwMEAAAFAAAAAAAABQEFAAUFBQAABgAAAAAGBgYGAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAAABAQcAAAUFAwABBAUBcAESEgUDAQACBggBfwFBgNQECwfRBSIGbWVtb3J5AgALX2luaXRpYWxpemUACRlfX2luZGlyZWN0X2Z1bmN0aW9uX3RhYmxlAQALbGxodHRwX2luaXQAChhsbGh0dHBfc2hvdWxkX2tlZXBfYWxpdmUAQQxsbGh0dHBfYWxsb2MADAZtYWxsb2MARgtsbGh0dHBfZnJlZQANBGZyZWUASA9sbGh0dHBfZ2V0X3R5cGUADhVsbGh0dHBfZ2V0X2h0dHBfbWFqb3IADxVsbGh0dHBfZ2V0X2h0dHBfbWlub3IAEBFsbGh0dHBfZ2V0X21ldGhvZAARFmxsaHR0cF9nZXRfc3RhdHVzX2NvZGUAEhJsbGh0dHBfZ2V0X3VwZ3JhZGUAEwxsbGh0dHBfcmVzZXQAFA5sbGh0dHBfZXhlY3V0ZQAVFGxsaHR0cF9zZXR0aW5nc19pbml0ABYNbGxodHRwX2ZpbmlzaAAXDGxsaHR0cF9wYXVzZQAYDWxsaHR0cF9yZXN1bWUAGRtsbGh0dHBfcmVzdW1lX2FmdGVyX3VwZ3JhZGUAGhBsbGh0dHBfZ2V0X2Vycm5vABsXbGxodHRwX2dldF9lcnJvcl9yZWFzb24AHBdsbGh0dHBfc2V0X2Vycm9yX3JlYXNvbgAdFGxsaHR0cF9nZXRfZXJyb3JfcG9zAB4RbGxodHRwX2Vycm5vX25hbWUAHxJsbGh0dHBfbWV0aG9kX25hbWUAIBJs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ADcDACAAQShqQgA3AwAgAEEgakIANwMAIABBEGpCADcDACAAQQhqQgA3AwAgAEHdATYCHEEAC3sBAX8CQCAAKAIMIgMNAAJAIAAoAgRFDQAgACABNgIECwJAIAAgASACEMSAgIAAIgMNACAAKAIMDwsgACADNgIcQQAhAyAAKAIEIgFFDQAgACABIAIgACgCCBGBgICAAAAiAUUNACAAIAI2AhQgACABNgIMIAEhAwsgAwvk8wEDDn8DfgR/I4CAgIAAQRBrIgMkgICAgAAgASEEIAEhBSABIQYgASEHIAEhCCABIQkgASEKIAEhCyABIQwgASENIAEhDiABIQ8CQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgACgCHCIQQX9qDt0B2gEB2QECAwQFBgcICQoLDA0O2AEPENcBERLWARMUFRYXGBkaG+AB3wEcHR7VAR8gISIjJCXUASYnKCkqKyzTAdIBLS7RAdABLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVG2wFHSElKzwHOAUvNAUzMAU1OT1BRUlNUVVZXWFlaW1xdXl9gYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXp7fH1+f4ABgQGCAYMBhAGFAYYBhwGIAYkB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QAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC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FCAAIBA2AgxBACEQDLIBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxNCyAAQdMANgIcIAAgATYCFCAAIBA2AgxBACEQDLEBCyAAKAIEIRAgAEEANgIEAkAgACAQIAEQp4CAgAAiEA0AIAEhAQxRCyAAQeUANgIcIAAgATYCFCAAIBA2AgxBACEQDLABCyAAQQA2AhwgACABNgIUIABBxoqAgAA2AhAgAEEHNgIMQQAhEAyvAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMSQsgAEHSADYCHCAAIAE2AhQgACAQNgIMQQAhEAyuAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMSQsgAEHTADYCHCAAIAE2AhQgACAQNgIMQQAhEAytAQsgACgCBCEQIABBADYCBAJAIAAgECABEKeAgIAAIhANACABIQEMTQsgAEHlADYCHCAAIAE2AhQgACAQNgIMQQAhEAysAQsgAEEANgIcIAAgATYCFCAAQdyIgIAANgIQIABBBzYCDEEAIRAMqwELIBBBP0cNASABQQFqIQELQQUhEAyQAQtBACEQIABBADYCHCAAIAE2AhQgAEH9koCAADYCECAAQQc2AgwMqAELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDEILIABB0gA2AhwgACABNgIUIAAgEDYCDEEAIRAMpwELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDEILIABB0wA2AhwgACABNgIUIAAgEDYCDEEAIRAMpgELIAAoAgQhECAAQQA2AgQCQCAAIBAgARCngICAACIQDQAgASEBDEYLIABB5QA2AhwgACABNgIUIAAgEDYCDEEAIRAMpQELIAAoAgQhASAAQQA2AgQCQCAAIAEgFBCngICAACIBDQAgFCEBDD8LIABB0gA2AhwgACAUNgIUIAAgATYCDEEAIRAMpAELIAAoAgQhASAAQQA2AgQCQCAAIAEgFBCngICAACIBDQAgFCEBDD8LIABB0wA2AhwgACAUNgIUIAAgATYCDEEAIRAMowELIAAoAgQhASAAQQA2AgQCQCAAIAEgFBCngICAACIBDQAgFCEBDEMLIABB5QA2AhwgACAU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YWdlX2JlZ2luYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlYCBjYWxsYmFjayBlcnJvcgBgb25fc3RhdHVzX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdmVyc2lvbl9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3VybF9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX3ZhbHVlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWVzc2FnZV9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX21ldGhvZF9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2hlYWRlcl9maWVsZF9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lYCBjYWxsYmFjayBlcnJvcgBVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNlcnZlcgBJbnZhbGlkIGhlYWRlciB2YWx1ZSBjaGFyAEludmFsaWQgaGVhZGVyIGZpZWxkIGNoYXIAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl92ZXJzaW9uAEludmFsaWQgbWlub3IgdmVyc2lvbgBJbnZhbGlkIG1ham9yIHZlcnNpb24ARXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgdmVyc2lvbgBFeHBlY3RlZCBDUkxGIGFmdGVyIHZlcnNpb24ASW52YWxpZCBIVFRQIHZlcnNpb24ASW52YWxpZCBoZWFkZXIgdG9rZW4AU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl91cmwASW52YWxpZCBjaGFyYWN0ZXJzIGluIHVybABVbmV4cGVjdGVkIHN0YXJ0IGNoYXIgaW4gdXJsAERvdWJsZSBAIGluIHVybABFbXB0eSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXJhY3RlciBpbiBDb250ZW50LUxlbmd0aABEdXBsaWNhdGUgQ29udGVudC1MZW5ndGgASW52YWxpZCBjaGFyIGluIHVybCBwYXRoAENvbnRlbnQtTGVuZ3RoIGNhbid0IGJlIHByZXNlbnQgd2l0aCBUcmFuc2Zlci1FbmNvZGluZwBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBzaXplAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25faGVhZGVyX3ZhbHVlAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgdmFsdWUATWlzc2luZyBleHBlY3RlZCBMRiBhZnRlciBoZWFkZXIgdmFsdWUASW52YWxpZCBgVHJhbnNmZXItRW5jb2RpbmdgIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHF1b3RlIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGVkIHZhbHVlAFBhdXNlZCBieSBvbl9oZWFkZXJzX2NvbXBsZXRlAEludmFsaWQgRU9GIHN0YXRlAG9uX3Jlc2V0IHBhdXNlAG9uX2NodW5rX2hlYWRlciBwYXVzZQBvbl9tZXNzYWdlX2JlZ2luIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZSBwYXVzZQBvbl9zdGF0dXNfY29tcGxldGUgcGF1c2UAb25fdmVyc2lvbl9jb21wbGV0ZSBwYXVzZQBvbl91cmxfY29tcGxldGUgcGF1c2UAb25fY2h1bmtfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX3ZhbHVlX2NvbXBsZXRlIHBhdXNlAG9uX21lc3NhZ2VfY29tcGxldGUgcGF1c2UAb25fbWV0aG9kX2NvbXBsZXRlIHBhdXNlAG9uX2hlYWRlcl9maWVsZF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19leHRlbnNpb25fbmFtZSBwYXVzZQBVbmV4cGVjdGVkIHNwYWNlIGFmdGVyIHN0YXJ0IGxpbmUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fbmFtZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIG5hbWUAUGF1c2Ugb24gQ09OTkVDVC9VcGdyYWRlAFBhdXNlIG9uIFBSSS9VcGdyYWRlAEV4cGVjdGVkIEhUVFAvMiBDb25uZWN0aW9uIFByZWZhY2UAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9tZXRob2QARXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgbWV0aG9kAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25faGVhZGVyX2ZpZWxkAFBhdXNlZABJbnZhbGlkIHdvcmQgZW5jb3VudGVyZWQASW52YWxpZCBtZXRob2QgZW5jb3VudGVyZWQAVW5leHBlY3RlZCBjaGFyIGluIHVybCBzY2hlbWEAUmVxdWVzdCBoYXMgaW52YWxpZCBgVHJhbnNmZXItRW5jb2RpbmdgAFNXSVRDSF9QUk9YWQBVU0VfUFJPWFkATUtBQ1RJVklUWQBVTlBST0NFU1NBQkxFX0VOVElUWQBDT1BZAE1PVkVEX1BFUk1BTkVOVExZAFRPT19FQVJMWQBOT1RJRlkARkFJTEVEX0RFUEVOREVOQ1kAQkFEX0dBVEVXQVkAUExBWQBQVVQAQ0hFQ0tPVVQAR0FURVdBWV9USU1FT1VUAFJFUVVFU1RfVElNRU9VVABORVRXT1JLX0NPTk5FQ1RfVElNRU9VVABDT05ORUNUSU9OX1RJTUVPVVQATE9HSU5fVElNRU9VVABORVRXT1JLX1JFQURfVElNRU9VVABQT1NUAE1JU0RJUkVDVEVEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9SRVFVRVNUAENMSUVOVF9DTE9TRURfTE9BRF9CQUxBTkNFRF9SRVFVRVNUAEJBRF9SRVFVRVNUAEhUVFBfUkVRVUVTVF9TRU5UX1RPX0hUVFBTX1BPUlQAUkVQT1JUAElNX0FfVEVBUE9UAFJFU0VUX0NPTlRFTlQATk9fQ09OVEVOVABQQVJUSUFMX0NPTlRFTlQASFBFX0lOVkFMSURfQ09OU1RBTlQASFBFX0NCX1JFU0VUAEdFVABIUEVfU1RSSUNUAENPTkZMSUNUAFRFTVBPUkFSWV9SRURJUkVDVABQRVJNQU5FTlRfUkVESVJFQ1QAQ09OTkVDVABNVUxUSV9TVEFUVVMASFBFX0lOVkFMSURfU1RBVFVTAFRPT19NQU5ZX1JFUVVFU1RTAEVBUkxZX0hJTlRTAFVOQVZBSUxBQkxFX0ZPUl9MRUdBTF9SRUFTT05TAE9QVElPTlMAU1dJVENISU5HX1BST1RPQ09MUwBWQVJJQU5UX0FMU09fTkVHT1RJQVRFUwBNVUxUSVBMRV9DSE9JQ0VTAElOVEVSTkFMX1NFUlZFUl9FUlJPUgBXRUJfU0VSVkVSX1VOS05PV05fRVJST1IAUkFJTEdVTl9FUlJPUgBJREVOVElUWV9QUk9WSURFUl9BVVRIRU5USUNBVElPTl9FUlJPUgBTU0xfQ0VSVElGSUNBVEVfRVJST1IASU5WQUxJRF9YX0ZPUldBUkRFRF9GT1IAU0VUX1BBUkFNRVRFUgBHRVRfUEFSQU1FVEVSAEhQRV9VU0VSAFNFRV9PVEhFUgBIUEVfQ0JfQ0hVTktfSEVBREVSAE1LQ0FMRU5EQVIAU0VUVVAAV0VCX1NFUlZFUl9JU19ET1dOAFRFQVJET1dOAEhQRV9DTE9TRURfQ09OTkVDVElPTgBIRVVSSVNUSUNfRVhQSVJBVElPTgBESVNDT05ORUNURURfT1BFUkFUSU9OAE5PTl9BVVRIT1JJVEFUSVZFX0lORk9STUFUSU9OAEhQRV9JTlZBTElEX1ZFUlNJT04ASFBFX0NCX01FU1NBR0VfQkVHSU4AU0lURV9JU19GUk9aRU4ASFBFX0lOVkFMSURfSEVBREVSX1RPS0VOAElOVkFMSURfVE9LRU4ARk9SQklEREVOAEVOSEFOQ0VfWU9VUl9DQUxNAEhQRV9JTlZBTElEX1VSTABCTE9DS0VEX0JZX1BBUkVOVEFMX0NPTlRST0wATUtDT0wAQUNMAEhQRV9JTlRFUk5BTABSRVFVRVNUX0hFQURFUl9GSUVMRFNfVE9PX0xBUkdFX1VOT0ZGSUNJQUwASFBFX09LAFVOTElOSwBVTkxPQ0sAUFJJAFJFVFJZX1dJVEgASFBFX0lOVkFMSURfQ09OVEVOVF9MRU5HVEgASFBFX1VORVhQRUNURURfQ09OVEVOVF9MRU5HVEgARkxVU0gAUFJPUFBBVENIAE0tU0VBUkNIAFVSSV9UT09fTE9ORwBQUk9DRVNTSU5HAE1JU0NFTExBTkVPVVNfUEVSU0lTVEVOVF9XQVJOSU5HAE1JU0NFTExBTkVPVVNfV0FSTklORwBIUEVfSU5WQUxJRF9UUkFOU0ZFUl9FTkNPRElORwBFeHBlY3RlZCBDUkxGAEhQRV9JTlZBTElEX0NIVU5LX1NJWkUATU9WRQBDT05USU5VRQBIUEVfQ0JfU1RBVFVTX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJTX0NPTVBMRVRFAEhQRV9DQl9WRVJTSU9OX0NPTVBMRVRFAEhQRV9DQl9VUkxfQ09NUExFVEUASFBFX0NCX0NIVU5LX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfVkFMVUVfQ09NUExFVEUASFBFX0NCX0NIVU5LX0VYVEVOU0lPTl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX05BTUVfQ09NUExFVEUASFBFX0NCX01FU1NBR0VfQ09NUExFVEUASFBFX0NCX01FVEhPRF9DT01QTEVURQBIUEVfQ0JfSEVBREVSX0ZJRUxEX0NPTVBMRVRFAERFTEVURQBIUEVfSU5WQUxJRF9FT0ZfU1RBVEUASU5WQUxJRF9TU0xfQ0VSVElGSUNBVEUAUEFVU0UATk9fUkVTUE9OU0UAVU5TVVBQT1JURURfTUVESUFfVFlQRQBHT05FAE5PVF9BQ0NFUFRBQkxFAFNFUlZJQ0VfVU5BVkFJTEFCTEUAUkFOR0VfTk9UX1NBVElTRklBQkxFAE9SSUdJTl9JU19VTlJFQUNIQUJMRQBSRVNQT05TRV9JU19TVEFMRQBQVVJHRQBNRVJHRQBSRVFVRVNUX0hFQURFUl9GSUVMRFNfVE9PX0xBUkdFAFJFUVVFU1RfSEVBREVSX1RPT19MQVJHRQBQQVlMT0FEX1RPT19MQVJHRQBJTlNVRkZJQ0lFTlRfU1RPUkFHRQBIUEVfUEFVU0VEX1VQR1JBREUASFBFX1BBVVNFRF9IMl9VUEdSQURFAFNPVVJDRQBBTk5PVU5DRQBUUkFDRQBIUEVfVU5FWFBFQ1RFRF9TUEFDRQBERVNDUklCRQBVTlNVQlNDUklCRQBSRUNPUkQASFBFX0lOVkFMSURfTUVUSE9EAE5PVF9GT1VORABQUk9QRklORABVTkJJTkQAUkVCSU5EAFVOQVVUSE9SSVpFRABNRVRIT0RfTk9UX0FMTE9XRUQASFRUUF9WRVJTSU9OX05PVF9TVVBQT1JURUQAQUxSRUFEWV9SRVBPUlRFRABBQ0NFUFRFRABOT1RfSU1QTEVNRU5URUQATE9PUF9ERVRFQ1RFRABIUEVfQ1JfRVhQRUNURUQASFBFX0xGX0VYUEVDVEVEAENSRUFURUQASU1fVVNFRABIUEVfUEFVU0VEAFRJTUVPVVRfT0NDVVJFRABQQVlNRU5UX1JFUVVJUkVEAFBSRUNPTkRJVElPTl9SRVFVSVJFRABQUk9YWV9BVVRIRU5USUNBVElPTl9SRVFVSVJFRABORVRXT1JLX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAExFTkdUSF9SRVFVSVJFRABTU0xfQ0VSVElGSUNBVEVfUkVRVUlSRUQAVVBHUkFERV9SRVFVSVJFRABQQUdFX0VYUElSRUQAUFJFQ09ORElUSU9OX0ZBSUxFRABFWFBFQ1RBVElPTl9GQUlMRUQAUkVWQUxJREFUSU9OX0ZBSUxFRABTU0xfSEFORFNIQUtFX0ZBSUxFRABMT0NLRUQAVFJBTlNGT1JNQVRJT05fQVBQTElFRABOT1RfTU9ESUZJRUQATk9UX0VYVEVOREVEAEJBTkRXSURUSF9MSU1JVF9FWENFRURFRABTSVRFX0lTX09WRVJMT0FERUQASEVBRABFeHBlY3RlZCBIVFRQLwAAXhMAACYTAAAwEAAA8BcAAJ0TAAAVEgAAORcAAPASAAAKEAAAdRIAAK0SAACCEwAATxQAAH8QAACgFQAAIxQAAIkSAACLFAAATRUAANQRAADPFAAAEBgAAMkWAADcFgAAwREAAOAXAAC7FAAAdBQAAHwVAADlFAAACBcAAB8QAABlFQAAoxQAACgVAAACFQAAmRUAACwQAACLGQAATw8AANQOAABqEAAAzhAAAAIXAACJDgAAbhMAABwTAABmFAAAVhcAAMETAADNEwAAbBMAAGgXAABmFwAAXxcAACITAADODwAAaQ4AANgOAABjFgAAyxMAAKoOAAAoFwAAJhcAAMUTAABdFgAA6BEAAGcTAABlEwAA8hYAAHMTAAAdFwAA+RYAAPMRAADPDgAAzhUAAAwSAACzEQAApREAAGEQAAAyFwAAuxMAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIDAgICAgIAAAICAAICAAICAgICAgICAgIABAAAAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAICAgICAAACAgACAgACAgICAgICAgICAAMABAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbG9zZWVlcC1hbGl2ZQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQEBAQEBAQEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBY2h1bmtlZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAQEBAQEAAAEBAAEBAAEBAQEBAQEBAQEAAAAAAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlY3Rpb25lbnQtbGVuZ3Rob25yb3h5LWNvbm5lY3Rpb24AAAAAAAAAAAAAAAAAAAByYW5zZmVyLWVuY29kaW5ncGdyYWRlDQoNCg0KU00NCg0KVFRQL0NFL1RTUC8AAAAAAAAAAAAAAAABAgABAwAAAAAAAAAAAAAAAAAAAAAAAAQBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAQIAAQMAAAAAAAAAAAAAAAAAAAAAAAAEAQEFAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAAAAQAAAgAAAAAAAAAAAAAAAAAAAAAAAAMEAAAEBAQEBAQEBAQEBAUEBAQEBAQEBAQEBAQABAAGBwQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAIAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOT1VOQ0VFQ0tPVVRORUNURVRFQ1JJQkVMVVNIRVRFQURTRUFSQ0hSR0VDVElWSVRZTEVOREFSVkVPVElGWVBUSU9OU0NIU0VBWVNUQVRDSEdFT1JESVJFQ1RPUlRSQ0hQQVJBTUVURVJVUkNFQlNDUklCRUFSRE9XTkFDRUlORE5LQ0tVQlNDUklCRUhUVFAvQURUUC8="},2084:e=>{e.exports="AGFzbQEAAAABMAhgAX8Bf2ADf39/AX9gBH9/f38Bf2AAAGADf39/AGABfwBgAn9/AGAGf39/f39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQACA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAA0ZFAwMEAAAFAAAAAAAABQEFAAUFBQAABgAAAAAGBgYGAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAAABAQcAAAUFAwABBAUBcAESEgUDAQACBggBfwFBgNQECwfRBSIGbWVtb3J5AgALX2luaXRpYWxpemUACRlfX2luZGlyZWN0X2Z1bmN0aW9uX3RhYmxlAQALbGxodHRwX2luaXQAChhsbGh0dHBfc2hvdWxkX2tlZXBfYWxpdmUAQQxsbGh0dHBfYWxsb2MADAZtYWxsb2MARgtsbGh0dHBfZnJlZQANBGZyZWUASA9sbGh0dHBfZ2V0X3R5cGUADhVsbGh0dHBfZ2V0X2h0dHBfbWFqb3IADxVsbGh0dHBfZ2V0X2h0dHBfbWlub3IAEBFsbGh0dHBfZ2V0X21ldGhvZAARFmxsaHR0cF9nZXRfc3RhdHVzX2NvZGUAEhJsbGh0dHBfZ2V0X3VwZ3JhZGUAEwxsbGh0dHBfcmVzZXQAFA5sbGh0dHBfZXhlY3V0ZQAVFGxsaHR0cF9zZXR0aW5nc19pbml0ABYNbGxodHRwX2ZpbmlzaAAXDGxsaHR0cF9wYXVzZQAYDWxsaHR0cF9yZXN1bWUAGRtsbGh0dHBfcmVzdW1lX2FmdGVyX3VwZ3JhZGUAGhBsbGh0dHBfZ2V0X2Vycm5vABsXbGxodHRwX2dldF9lcnJvcl9yZWFzb24AHBdsbGh0dHBfc2V0X2Vycm9yX3JlYXNvbgAdFGxsaHR0cF9nZXRfZXJyb3JfcG9zAB4RbGxodHRwX2Vycm5vX25hbWUAHxJsbGh0dHBfbWV0aG9kX25hbWUAIBJs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IAH9CwMAIABBMGogAf0LAwAgAEEgaiAB/QsDACAAQd0BNgIcQQALewEBfwJAIAAoAgwiAw0AAkAgACgCBEUNACAAIAE2AgQLAkAgACABIAIQxICAgAAiAw0AIAAoAgwPCyAAIAM2AhxBACEDIAAoAgQiAUUNACAAIAEgAiAAKAIIEYGAgIAAACIBRQ0AIAAgAjYCFCAAIAE2AgwgASEDCyADC+TzAQMOfwN+BH8jgICAgABBEGsiAySAgICAACABIQQgASEFIAEhBiABIQcgASEIIAEhCSABIQogASELIAEhDCABIQ0gASEOIAEhDwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAKAIcIhBBf2oO3QHaAQHZAQIDBAUGBwgJCgsMDQ7YAQ8Q1wEREtYBExQVFhcYGRob4AHfARwdHtUBHyAhIiMkJdQBJicoKSorLNMB0gEtLtEB0AEvMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUbbAUdISUrPAc4BS80BTMwBTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ent8fX5/gAGBAYIBgwGEAYUBhgGHAYgBiQGKAYsBjAGNAY4BjwGQAZEB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QAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIBAOxwEAAQID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YWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fdmFsdWVgIGNhbGxiYWNrIGVycm9yAGBvbl9zdGF0dXNfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl92ZXJzaW9uX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdXJsX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXNzYWdlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWV0aG9kX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX25hbWVgIGNhbGxiYWNrIGVycm9yAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2VydmVyAEludmFsaWQgaGVhZGVyIHZhbHVlIGNoYXIASW52YWxpZCBoZWFkZXIgZmllbGQgY2hhcgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3ZlcnNpb24ASW52YWxpZCBtaW5vciB2ZXJzaW9uAEludmFsaWQgbWFqb3IgdmVyc2lvbgBFeHBlY3RlZCBzcGFjZSBhZnRlciB2ZXJzaW9uAEV4cGVjdGVkIENSTEYgYWZ0ZXIgdmVyc2lvbgBJbnZhbGlkIEhUVFAgdmVyc2lvbgBJbnZhbGlkIGhlYWRlciB0b2tlbgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3VybABJbnZhbGlkIGNoYXJhY3RlcnMgaW4gdXJsAFVuZXhwZWN0ZWQgc3RhcnQgY2hhciBpbiB1cmwARG91YmxlIEAgaW4gdXJsAEVtcHR5IENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhcmFjdGVyIGluIENvbnRlbnQtTGVuZ3RoAER1cGxpY2F0ZSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXIgaW4gdXJsIHBhdGgAQ29udGVudC1MZW5ndGggY2FuJ3QgYmUgcHJlc2VudCB3aXRoIFRyYW5zZmVyLUVuY29kaW5nAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIHNpemUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfdmFsdWUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyB2YWx1ZQBNaXNzaW5nIGV4cGVjdGVkIExGIGFmdGVyIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AgaGVhZGVyIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGUgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZWQgdmFsdWUAUGF1c2VkIGJ5IG9uX2hlYWRlcnNfY29tcGxldGUASW52YWxpZCBFT0Ygc3RhdGUAb25fcmVzZXQgcGF1c2UAb25fY2h1bmtfaGVhZGVyIHBhdXNlAG9uX21lc3NhZ2VfYmVnaW4gcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlIHBhdXNlAG9uX3N0YXR1c19jb21wbGV0ZSBwYXVzZQBvbl92ZXJzaW9uX2NvbXBsZXRlIHBhdXNlAG9uX3VybF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGUgcGF1c2UAb25fbWVzc2FnZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXRob2RfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lIHBhdXNlAFVuZXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgc3RhcnQgbGluZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgbmFtZQBQYXVzZSBvbiBDT05ORUNUL1VwZ3JhZGUAUGF1c2Ugb24gUFJJL1VwZ3JhZGUARXhwZWN0ZWQgSFRUUC8yIENvbm5lY3Rpb24gUHJlZmFjZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX21ldGhvZABFeHBlY3RlZCBzcGFjZSBhZnRlciBtZXRob2QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfZmllbGQAUGF1c2VkAEludmFsaWQgd29yZCBlbmNvdW50ZXJlZABJbnZhbGlkIG1ldGhvZCBlbmNvdW50ZXJlZABVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNjaGVtYQBSZXF1ZXN0IGhhcyBpbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AAU1dJVENIX1BST1hZAFVTRV9QUk9YWQBNS0FDVElWSVRZAFVOUFJPQ0VTU0FCTEVfRU5USVRZAENPUFkATU9WRURfUEVSTUFORU5UTFkAVE9PX0VBUkxZAE5PVElGWQBGQUlMRURfREVQRU5ERU5DWQBCQURfR0FURVdBWQBQTEFZAFBVVABDSEVDS09VVABHQVRFV0FZX1RJTUVPVVQAUkVRVUVTVF9USU1FT1VUAE5FVFdPUktfQ09OTkVDVF9USU1FT1VUAENPTk5FQ1RJT05fVElNRU9VVABMT0dJTl9USU1FT1VUAE5FVFdPUktfUkVBRF9USU1FT1VUAFBPU1QATUlTRElSRUNURURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9MT0FEX0JBTEFOQ0VEX1JFUVVFU1QAQkFEX1JFUVVFU1QASFRUUF9SRVFVRVNUX1NFTlRfVE9fSFRUUFNfUE9SVABSRVBPUlQASU1fQV9URUFQT1QAUkVTRVRfQ09OVEVOVABOT19DT05URU5UAFBBUlRJQUxfQ09OVEVOVABIUEVfSU5WQUxJRF9DT05TVEFOVABIUEVfQ0JfUkVTRVQAR0VUAEhQRV9TVFJJQ1QAQ09ORkxJQ1QAVEVNUE9SQVJZX1JFRElSRUNUAFBFUk1BTkVOVF9SRURJUkVDVABDT05ORUNUAE1VTFRJX1NUQVRVUwBIUEVfSU5WQUxJRF9TVEFUVVMAVE9PX01BTllfUkVRVUVTVFMARUFSTFlfSElOVFMAVU5BVkFJTEFCTEVfRk9SX0xFR0FMX1JFQVNPTlMAT1BUSU9OUwBTV0lUQ0hJTkdfUFJPVE9DT0xTAFZBUklBTlRfQUxTT19ORUdPVElBVEVTAE1VTFRJUExFX0NIT0lDRVMASU5URVJOQUxfU0VSVkVSX0VSUk9SAFdFQl9TRVJWRVJfVU5LTk9XTl9FUlJPUgBSQUlMR1VOX0VSUk9SAElERU5USVRZX1BST1ZJREVSX0FVVEhFTlRJQ0FUSU9OX0VSUk9SAFNTTF9DRVJUSUZJQ0FURV9FUlJPUgBJTlZBTElEX1hfRk9SV0FSREVEX0ZPUgBTRVRfUEFSQU1FVEVSAEdFVF9QQVJBTUVURVIASFBFX1VTRVIAU0VFX09USEVSAEhQRV9DQl9DSFVOS19IRUFERVIATUtDQUxFTkRBUgBTRVRVUABXRUJfU0VSVkVSX0lTX0RPV04AVEVBUkRPV04ASFBFX0NMT1NFRF9DT05ORUNUSU9OAEhFVVJJU1RJQ19FWFBJUkFUSU9OAERJU0NPTk5FQ1RFRF9PUEVSQVRJT04ATk9OX0FVVEhPUklUQVRJVkVfSU5GT1JNQVRJT04ASFBFX0lOVkFMSURfVkVSU0lPTgBIUEVfQ0JfTUVTU0FHRV9CRUdJTgBTSVRFX0lTX0ZST1pFTgBIUEVfSU5WQUxJRF9IRUFERVJfVE9LRU4ASU5WQUxJRF9UT0tFTgBGT1JCSURERU4ARU5IQU5DRV9ZT1VSX0NBTE0ASFBFX0lOVkFMSURfVVJMAEJMT0NLRURfQllfUEFSRU5UQUxfQ09OVFJPTABNS0NPTABBQ0wASFBFX0lOVEVSTkFMAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0VfVU5PRkZJQ0lBTABIUEVfT0sAVU5MSU5LAFVOTE9DSwBQUkkAUkVUUllfV0lUSABIUEVfSU5WQUxJRF9DT05URU5UX0xFTkdUSABIUEVfVU5FWFBFQ1RFRF9DT05URU5UX0xFTkdUSABGTFVTSABQUk9QUEFUQ0gATS1TRUFSQ0gAVVJJX1RPT19MT05HAFBST0NFU1NJTkcATUlTQ0VMTEFORU9VU19QRVJTSVNURU5UX1dBUk5JTkcATUlTQ0VMTEFORU9VU19XQVJOSU5HAEhQRV9JTlZBTElEX1RSQU5TRkVSX0VOQ09ESU5HAEV4cGVjdGVkIENSTEYASFBFX0lOVkFMSURfQ0hVTktfU0laRQBNT1ZFAENPTlRJTlVFAEhQRV9DQl9TVEFUVVNfQ09NUExFVEUASFBFX0NCX0hFQURFUlNfQ09NUExFVEUASFBFX0NCX1ZFUlNJT05fQ09NUExFVEUASFBFX0NCX1VSTF9DT01QTEVURQBIUEVfQ0JfQ0hVTktfQ09NUExFVEUASFBFX0NCX0hFQURFUl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fTkFNRV9DT01QTEVURQBIUEVfQ0JfTUVTU0FHRV9DT01QTEVURQBIUEVfQ0JfTUVUSE9EX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfRklFTERfQ09NUExFVEUAREVMRVRFAEhQRV9JTlZBTElEX0VPRl9TVEFURQBJTlZBTElEX1NTTF9DRVJUSUZJQ0FURQBQQVVTRQBOT19SRVNQT05TRQBVTlNVUFBPUlRFRF9NRURJQV9UWVBFAEdPTkUATk9UX0FDQ0VQVEFCTEUAU0VSVklDRV9VTkFWQUlMQUJMRQBSQU5HRV9OT1RfU0FUSVNGSUFCTEUAT1JJR0lOX0lTX1VOUkVBQ0hBQkxFAFJFU1BPTlNFX0lTX1NUQUxFAFBVUkdFAE1FUkdFAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0UAUkVRVUVTVF9IRUFERVJfVE9PX0xBUkdFAFBBWUxPQURfVE9PX0xBUkdFAElOU1VGRklDSUVOVF9TVE9SQUdFAEhQRV9QQVVTRURfVVBHUkFERQBIUEVfUEFVU0VEX0gyX1VQR1JBREUAU09VUkNFAEFOTk9VTkNFAFRSQUNFAEhQRV9VTkVYUEVDVEVEX1NQQUNFAERFU0NSSUJFAFVOU1VCU0NSSUJFAFJFQ09SRABIUEVfSU5WQUxJRF9NRVRIT0QATk9UX0ZPVU5EAFBST1BGSU5EAFVOQklORABSRUJJTkQAVU5BVVRIT1JJWkVEAE1FVEhPRF9OT1RfQUxMT1dFRABIVFRQX1ZFUlNJT05fTk9UX1NVUFBPUlRFRABBTFJFQURZX1JFUE9SVEVEAEFDQ0VQVEVEAE5PVF9JTVBMRU1FTlRFRABMT09QX0RFVEVDVEVEAEhQRV9DUl9FWFBFQ1RFRABIUEVfTEZfRVhQRUNURUQAQ1JFQVRFRABJTV9VU0VEAEhQRV9QQVVTRUQAVElNRU9VVF9PQ0NVUkVEAFBBWU1FTlRfUkVRVUlSRUQAUFJFQ09ORElUSU9OX1JFUVVJUkVEAFBST1hZX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAE5FVFdPUktfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATEVOR1RIX1JFUVVJUkVEAFNTTF9DRVJUSUZJQ0FURV9SRVFVSVJFRABVUEdSQURFX1JFUVVJUkVEAFBBR0VfRVhQSVJFRABQUkVDT05ESVRJT05fRkFJTEVEAEVYUEVDVEFUSU9OX0ZBSUxFRABSRVZBTElEQVRJT05fRkFJTEVEAFNTTF9IQU5EU0hBS0VfRkFJTEVEAExPQ0tFRABUUkFOU0ZPUk1BVElPTl9BUFBMSUVEAE5PVF9NT0RJRklFRABOT1RfRVhURU5ERUQAQkFORFdJRFRIX0xJTUlUX0VYQ0VFREVEAFNJVEVfSVNfT1ZFUkxPQURFRABIRUFEAEV4cGVjdGVkIEhUVFAvAABeEwAAJhMAADAQAADwFwAAnRMAABUSAAA5FwAA8BIAAAoQAAB1EgAArRIAAIITAABPFAAAfxAAAKAVAAAjFAAAiRIAAIsUAABNFQAA1BEAAM8UAAAQGAAAyRYAANwWAADBEQAA4BcAALsUAAB0FAAAfBUAAOUUAAAIFwAAHxAAAGUVAACjFAAAKBUAAAIVAACZFQAALBAAAIsZAABPDwAA1A4AAGoQAADOEAAAAhcAAIkOAABuEwAAHBMAAGYUAABWFwAAwRMAAM0TAABsEwAAaBcAAGYXAABfFwAAIhMAAM4PAABpDgAA2A4AAGMWAADLEwAAqg4AACgXAAAmFwAAxRMAAF0WAADoEQAAZxMAAGUTAADyFgAAcxMAAB0XAAD5FgAA8xEAAM8OAADOFQAADBIAALMRAAClEQAAYRAAADIXAAC7EwAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAgMCAgICAgAAAgIAAgIAAgICAgICAgICAgAEAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAIAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAIAAgICAgIAAAICAAICAAICAgICAgICAgIAAwAEAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABsb3NlZWVwLWFsaXZlAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAQEBAQEBAQEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFjaHVua2VkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQABAQEBAQAAAQEAAQEAAQEBAQEBAQEBAQAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGVjdGlvbmVudC1sZW5ndGhvbnJveHktY29ubmVjdGlvbgAAAAAAAAAAAAAAAAAAAHJhbnNmZXItZW5jb2RpbmdwZ3JhZGUNCg0KDQpTTQ0KDQpUVFAvQ0UvVFNQLwAAAAAAAAAAAAAAAAECAAEDAAAAAAAAAAAAAAAAAAAAAAAABAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAAAAAAAAAAABAgABAwAAAAAAAAAAAAAAAAAAAAAAAAQBAQUBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAAAAAAAAAQAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAAAAAAAAAABAAACAAAAAAAAAAAAAAAAAAAAAAAAAwQAAAQEBAQEBAQEBAQEBQQEBAQEBAQEBAQEBAAEAAYHBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAgAAAAACAAAAAAAAAAAAAAAAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE5PVU5DRUVDS09VVE5FQ1RFVEVDUklCRUxVU0hFVEVBRFNFQVJDSFJHRUNUSVZJVFlMRU5EQVJWRU9USUZZUFRJT05TQ0hTRUFZU1RBVENIR0VPUkRJUkVDVE9SVFJDSFBBUkFNRVRFUlVSQ0VCU0NSSUJFQVJET1dOQUNFSU5ETktDS1VCU0NSSUJFSFRUUC9BRFRQLw=="},8974:(e,A)=>{"use strict";Object.defineProperty(A,"__esModule",{value:true});A.enumToMap=void 0;function enumToMap(e){const A={};Object.keys(e).forEach((t=>{const r=e[t];if(typeof r==="number"){A[t]=r}}));return A}A.enumToMap=enumToMap},3703:(e,A,t)=>{"use strict";const{kClients:r}=t(7781);const s=t(8787);const{kAgent:o,kMockAgentSet:n,kMockAgentGet:i,kDispatches:a,kIsMockActive:c,kNetConnect:g,kGetNetConnect:E,kOptions:l,kFactory:u}=t(3647);const Q=t(7739);const C=t(1986);const{matchValue:h,buildMockOptions:B}=t(1779);const{InvalidArgumentError:I,UndiciError:d}=t(7221);const p=t(8841);const m=t(5051);const y=t(1600);class FakeWeakRef{constructor(e){this.value=e}deref(){return this.value}}class MockAgent extends p{constructor(e){super(e);this[g]=true;this[c]=true;if(e&&e.agent&&typeof e.agent.dispatch!=="function"){throw new I("Argument opts.agent must implement Agent")}const A=e&&e.agent?e.agent:new s(e);this[o]=A;this[r]=A[r];this[l]=B(e)}get(e){let A=this[i](e);if(!A){A=this[u](e);this[n](e,A)}return A}dispatch(e,A){this.get(e.origin);return this[o].dispatch(e,A)}async close(){await this[o].close();this[r].clear()}deactivate(){this[c]=false}activate(){this[c]=true}enableNetConnect(e){if(typeof e==="string"||typeof e==="function"||e instanceof RegExp){if(Array.isArray(this[g])){this[g].push(e)}else{this[g]=[e]}}else if(typeof e==="undefined"){this[g]=true}else{throw new I("Unsupported matcher. Must be one of String|Function|RegExp.")}}disableNetConnect(){this[g]=false}get isMockActive(){return this[c]}[n](e,A){this[r].set(e,new FakeWeakRef(A))}[u](e){const A=Object.assign({agent:this},this[l]);return this[l]&&this[l].connections===1?new Q(e,A):new C(e,A)}[i](e){const A=this[r].get(e);if(A){return A.deref()}if(typeof e!=="string"){const A=this[u]("http://localhost:9999");this[n](e,A);return A}for(const[A,t]of Array.from(this[r])){const r=t.deref();if(r&&typeof A!=="string"&&h(A,e)){const A=this[u](e);this[n](e,A);A[a]=r[a];return A}}}[E](){return this[g]}pendingInterceptors(){const e=this[r];return Array.from(e.entries()).flatMap((([e,A])=>A.deref()[a].map((A=>({...A,origin:e}))))).filter((({pending:e})=>e))}assertNoPendingInterceptors({pendingInterceptorsFormatter:e=new y}={}){const A=this.pendingInterceptors();if(A.length===0){return}const t=new m("interceptor","interceptors").pluralize(A.length);throw new d(`\n${t.count} ${t.noun} ${t.is} pending:\n\n${e.format(A)}\n`.trim())}}e.exports=MockAgent},7739:(e,A,t)=>{"use strict";const{promisify:r}=t(9023);const s=t(1247);const{buildMockDispatch:o}=t(1779);const{kDispatches:n,kMockAgent:i,kClose:a,kOriginalClose:c,kOrigin:g,kOriginalDispatch:E,kConnected:l}=t(3647);const{MockInterceptor:u}=t(2013);const Q=t(7781);const{InvalidArgumentError:C}=t(7221);class MockClient extends s{constructor(e,A){super(e,A);if(!A||!A.agent||typeof A.agent.dispatch!=="function"){throw new C("Argument opts.agent must implement Agent")}this[i]=A.agent;this[g]=e;this[n]=[];this[l]=1;this[E]=this.dispatch;this[c]=this.close.bind(this);this.dispatch=o.call(this);this.close=this[a]}get[Q.kConnected](){return this[l]}intercept(e){return new u(e,this[n])}async[a](){await r(this[c])();this[l]=0;this[i][Q.kClients].delete(this[g])}}e.exports=MockClient},2567:(e,A,t)=>{"use strict";const{UndiciError:r}=t(7221);class MockNotMatchedError extends r{constructor(e){super(e);Error.captureStackTrace(this,MockNotMatchedError);this.name="MockNotMatchedError";this.message=e||"The request does not match any registered mock dispatches";this.code="UND_MOCK_ERR_MOCK_NOT_MATCHED"}}e.exports={MockNotMatchedError:MockNotMatchedError}},2013:(e,A,t)=>{"use strict";const{getResponseData:r,buildKey:s,addMockDispatch:o}=t(1779);const{kDispatches:n,kDispatchKey:i,kDefaultHeaders:a,kDefaultTrailers:c,kContentLength:g,kMockDispatch:E}=t(3647);const{InvalidArgumentError:l}=t(7221);const{buildURL:u}=t(2806);class MockScope{constructor(e){this[E]=e}delay(e){if(typeof e!=="number"||!Number.isInteger(e)||e<=0){throw new l("waitInMs must be a valid integer > 0")}this[E].delay=e;return this}persist(){this[E].persist=true;return this}times(e){if(typeof e!=="number"||!Number.isInteger(e)||e<=0){throw new l("repeatTimes must be a valid integer > 0")}this[E].times=e;return this}}class MockInterceptor{constructor(e,A){if(typeof e!=="object"){throw new l("opts must be an object")}if(typeof e.path==="undefined"){throw new l("opts.path must be defined")}if(typeof e.method==="undefined"){e.method="GET"}if(typeof e.path==="string"){if(e.query){e.path=u(e.path,e.query)}else{const A=new URL(e.path,"data://");e.path=A.pathname+A.search}}if(typeof e.method==="string"){e.method=e.method.toUpperCase()}this[i]=s(e);this[n]=A;this[a]={};this[c]={};this[g]=false}createMockScopeDispatchData(e,A,t={}){const s=r(A);const o=this[g]?{"content-length":s.length}:{};const n={...this[a],...o,...t.headers};const i={...this[c],...t.trailers};return{statusCode:e,data:A,headers:n,trailers:i}}validateReplyParameters(e,A,t){if(typeof e==="undefined"){throw new l("statusCode must be defined")}if(typeof A==="undefined"){throw new l("data must be defined")}if(typeof t!=="object"){throw new l("responseOptions must be an object")}}reply(e){if(typeof e==="function"){const wrappedDefaultsCallback=A=>{const t=e(A);if(typeof t!=="object"){throw new l("reply options callback must return an object")}const{statusCode:r,data:s="",responseOptions:o={}}=t;this.validateReplyParameters(r,s,o);return{...this.createMockScopeDispatchData(r,s,o)}};const A=o(this[n],this[i],wrappedDefaultsCallback);return new MockScope(A)}const[A,t="",r={}]=[...arguments];this.validateReplyParameters(A,t,r);const s=this.createMockScopeDispatchData(A,t,r);const a=o(this[n],this[i],s);return new MockScope(a)}replyWithError(e){if(typeof e==="undefined"){throw new l("error must be defined")}const A=o(this[n],this[i],{error:e});return new MockScope(A)}defaultReplyHeaders(e){if(typeof e==="undefined"){throw new l("headers must be defined")}this[a]=e;return this}defaultReplyTrailers(e){if(typeof e==="undefined"){throw new l("trailers must be defined")}this[c]=e;return this}replyContentLength(){this[g]=true;return this}}e.exports.MockInterceptor=MockInterceptor;e.exports.MockScope=MockScope},1986:(e,A,t)=>{"use strict";const{promisify:r}=t(9023);const s=t(4094);const{buildMockDispatch:o}=t(1779);const{kDispatches:n,kMockAgent:i,kClose:a,kOriginalClose:c,kOrigin:g,kOriginalDispatch:E,kConnected:l}=t(3647);const{MockInterceptor:u}=t(2013);const Q=t(7781);const{InvalidArgumentError:C}=t(7221);class MockPool extends s{constructor(e,A){super(e,A);if(!A||!A.agent||typeof A.agent.dispatch!=="function"){throw new C("Argument opts.agent must implement Agent")}this[i]=A.agent;this[g]=e;this[n]=[];this[l]=1;this[E]=this.dispatch;this[c]=this.close.bind(this);this.dispatch=o.call(this);this.close=this[a]}get[Q.kConnected](){return this[l]}intercept(e){return new u(e,this[n])}async[a](){await r(this[c])();this[l]=0;this[i][Q.kClients].delete(this[g])}}e.exports=MockPool},3647:e=>{"use strict";e.exports={kAgent:Symbol("agent"),kOptions:Symbol("options"),kFactory:Symbol("factory"),kDispatches:Symbol("dispatches"),kDispatchKey:Symbol("dispatch key"),kDefaultHeaders:Symbol("default headers"),kDefaultTrailers:Symbol("default trailers"),kContentLength:Symbol("content length"),kMockAgent:Symbol("mock agent"),kMockAgentSet:Symbol("mock agent set"),kMockAgentGet:Symbol("mock agent get"),kMockDispatch:Symbol("mock dispatch"),kClose:Symbol("close"),kOriginalClose:Symbol("original agent close"),kOrigin:Symbol("origin"),kIsMockActive:Symbol("is mock active"),kNetConnect:Symbol("net connect"),kGetNetConnect:Symbol("get net connect"),kConnected:Symbol("connected")}},1779:(e,A,t)=>{"use strict";const{MockNotMatchedError:r}=t(2567);const{kDispatches:s,kMockAgent:o,kOriginalDispatch:n,kOrigin:i,kGetNetConnect:a}=t(3647);const{buildURL:c,nop:g}=t(2806);const{STATUS_CODES:E}=t(8611);const{types:{isPromise:l}}=t(9023);function matchValue(e,A){if(typeof e==="string"){return e===A}if(e instanceof RegExp){return e.test(A)}if(typeof e==="function"){return e(A)===true}return false}function lowerCaseEntries(e){return Object.fromEntries(Object.entries(e).map((([e,A])=>[e.toLocaleLowerCase(),A])))}function getHeaderByName(e,A){if(Array.isArray(e)){for(let t=0;t!e)).filter((({path:e})=>matchValue(safeUrl(e),s)));if(o.length===0){throw new r(`Mock dispatch not matched for path '${s}'`)}o=o.filter((({method:e})=>matchValue(e,A.method)));if(o.length===0){throw new r(`Mock dispatch not matched for method '${A.method}'`)}o=o.filter((({body:e})=>typeof e!=="undefined"?matchValue(e,A.body):true));if(o.length===0){throw new r(`Mock dispatch not matched for body '${A.body}'`)}o=o.filter((e=>matchHeaders(e,A.headers)));if(o.length===0){throw new r(`Mock dispatch not matched for headers '${typeof A.headers==="object"?JSON.stringify(A.headers):A.headers}'`)}return o[0]}function addMockDispatch(e,A,t){const r={timesInvoked:0,times:1,persist:false,consumed:false};const s=typeof t==="function"?{callback:t}:{...t};const o={...r,...A,pending:true,data:{error:null,...s}};e.push(o);return o}function deleteMockDispatch(e,A){const t=e.findIndex((e=>{if(!e.consumed){return false}return matchKey(e,A)}));if(t!==-1){e.splice(t,1)}}function buildKey(e){const{path:A,method:t,body:r,headers:s,query:o}=e;return{path:A,method:t,body:r,headers:s,query:o}}function generateKeyValues(e){return Object.entries(e).reduce(((e,[A,t])=>[...e,Buffer.from(`${A}`),Array.isArray(t)?t.map((e=>Buffer.from(`${e}`))):Buffer.from(`${t}`)]),[])}function getStatusText(e){return E[e]||"unknown"}async function getResponse(e){const A=[];for await(const t of e){A.push(t)}return Buffer.concat(A).toString("utf8")}function mockDispatch(e,A){const t=buildKey(e);const r=getMockDispatch(this[s],t);r.timesInvoked++;if(r.data.callback){r.data={...r.data,...r.data.callback(e)}}const{data:{statusCode:o,data:n,headers:i,trailers:a,error:c},delay:E,persist:u}=r;const{timesInvoked:Q,times:C}=r;r.consumed=!u&&Q>=C;r.pending=Q0){setTimeout((()=>{handleReply(this[s])}),E)}else{handleReply(this[s])}function handleReply(r,s=n){const c=Array.isArray(e.headers)?buildHeadersFromArray(e.headers):e.headers;const E=typeof s==="function"?s({...e,headers:c}):s;if(l(E)){E.then((e=>handleReply(r,e)));return}const u=getResponseData(E);const Q=generateKeyValues(i);const C=generateKeyValues(a);A.abort=g;A.onHeaders(o,Q,resume,getStatusText(o));A.onData(Buffer.from(u));A.onComplete(C);deleteMockDispatch(r,t)}function resume(){}return true}function buildMockDispatch(){const e=this[o];const A=this[i];const t=this[n];return function dispatch(s,o){if(e.isMockActive){try{mockDispatch.call(this,s,o)}catch(n){if(n instanceof r){const i=e[a]();if(i===false){throw new r(`${n.message}: subsequent request to origin ${A} was not allowed (net.connect disabled)`)}if(checkNetConnect(i,A)){t.call(this,s,o)}else{throw new r(`${n.message}: subsequent request to origin ${A} was not allowed (net.connect is not enabled for this origin)`)}}else{throw n}}}else{t.call(this,s,o)}}}function checkNetConnect(e,A){const t=new URL(A);if(e===true){return true}else if(Array.isArray(e)&&e.some((e=>matchValue(e,t.host)))){return true}return false}function buildMockOptions(e){if(e){const{agent:A,...t}=e;return t}}e.exports={getResponseData:getResponseData,getMockDispatch:getMockDispatch,addMockDispatch:addMockDispatch,deleteMockDispatch:deleteMockDispatch,buildKey:buildKey,generateKeyValues:generateKeyValues,matchValue:matchValue,getResponse:getResponse,getStatusText:getStatusText,mockDispatch:mockDispatch,buildMockDispatch:buildMockDispatch,checkNetConnect:checkNetConnect,buildMockOptions:buildMockOptions,getHeaderByName:getHeaderByName}},1600:(e,A,t)=>{"use strict";const{Transform:r}=t(2203);const{Console:s}=t(4236);e.exports=class PendingInterceptorsFormatter{constructor({disableColors:e}={}){this.transform=new r({transform(e,A,t){t(null,e)}});this.logger=new s({stdout:this.transform,inspectOptions:{colors:!e&&!process.env.CI}})}format(e){const A=e.map((({method:e,path:A,data:{statusCode:t},persist:r,times:s,timesInvoked:o,origin:n})=>({Method:e,Origin:n,Path:A,"Status code":t,Persistent:r?"✅":"❌",Invocations:o,Remaining:r?Infinity:s-o})));this.logger.table(A);return this.transform.read().toString()}}},5051:e=>{"use strict";const A={pronoun:"it",is:"is",was:"was",this:"this"};const t={pronoun:"they",is:"are",was:"were",this:"these"};e.exports=class Pluralizer{constructor(e,A){this.singular=e;this.plural=A}pluralize(e){const r=e===1;const s=r?A:t;const o=r?this.singular:this.plural;return{...s,count:e,noun:o}}}},5631:e=>{"use strict";const A=2048;const t=A-1;class FixedCircularBuffer{constructor(){this.bottom=0;this.top=0;this.list=new Array(A);this.next=null}isEmpty(){return this.top===this.bottom}isFull(){return(this.top+1&t)===this.bottom}push(e){this.list[this.top]=e;this.top=this.top+1&t}shift(){const e=this.list[this.bottom];if(e===undefined)return null;this.list[this.bottom]=undefined;this.bottom=this.bottom+1&t;return e}}e.exports=class FixedQueue{constructor(){this.head=this.tail=new FixedCircularBuffer}isEmpty(){return this.head.isEmpty()}push(e){if(this.head.isFull()){this.head=this.head.next=new FixedCircularBuffer}this.head.push(e)}shift(){const e=this.tail;const A=e.shift();if(e.isEmpty()&&e.next!==null){this.tail=e.next}return A}}},5934:(e,A,t)=>{"use strict";const r=t(6915);const s=t(5631);const{kConnected:o,kSize:n,kRunning:i,kPending:a,kQueued:c,kBusy:g,kFree:E,kUrl:l,kClose:u,kDestroy:Q,kDispatch:C}=t(7781);const h=t(6752);const B=Symbol("clients");const I=Symbol("needDrain");const d=Symbol("queue");const p=Symbol("closed resolve");const m=Symbol("onDrain");const y=Symbol("onConnect");const w=Symbol("onDisconnect");const R=Symbol("onConnectionError");const b=Symbol("get dispatcher");const D=Symbol("add client");const k=Symbol("remove client");const F=Symbol("stats");class PoolBase extends r{constructor(){super();this[d]=new s;this[B]=[];this[c]=0;const e=this;this[m]=function onDrain(A,t){const r=e[d];let s=false;while(!s){const A=r.shift();if(!A){break}e[c]--;s=!this.dispatch(A.opts,A.handler)}this[I]=s;if(!this[I]&&e[I]){e[I]=false;e.emit("drain",A,[e,...t])}if(e[p]&&r.isEmpty()){Promise.all(e[B].map((e=>e.close()))).then(e[p])}};this[y]=(A,t)=>{e.emit("connect",A,[e,...t])};this[w]=(A,t,r)=>{e.emit("disconnect",A,[e,...t],r)};this[R]=(A,t,r)=>{e.emit("connectionError",A,[e,...t],r)};this[F]=new h(this)}get[g](){return this[I]}get[o](){return this[B].filter((e=>e[o])).length}get[E](){return this[B].filter((e=>e[o]&&!e[I])).length}get[a](){let e=this[c];for(const{[a]:A}of this[B]){e+=A}return e}get[i](){let e=0;for(const{[i]:A}of this[B]){e+=A}return e}get[n](){let e=this[c];for(const{[n]:A}of this[B]){e+=A}return e}get stats(){return this[F]}async[u](){if(this[d].isEmpty()){return Promise.all(this[B].map((e=>e.close())))}else{return new Promise((e=>{this[p]=e}))}}async[Q](e){while(true){const A=this[d].shift();if(!A){break}A.handler.onError(e)}return Promise.all(this[B].map((A=>A.destroy(e))))}[C](e,A){const t=this[b]();if(!t){this[I]=true;this[d].push({opts:e,handler:A});this[c]++}else if(!t.dispatch(e,A)){t[I]=true;this[I]=!this[b]()}return!this[I]}[D](e){e.on("drain",this[m]).on("connect",this[y]).on("disconnect",this[w]).on("connectionError",this[R]);this[B].push(e);if(this[I]){process.nextTick((()=>{if(this[I]){this[m](e[l],[this,e])}}))}return this}[k](e){e.close((()=>{const A=this[B].indexOf(e);if(A!==-1){this[B].splice(A,1)}}));this[I]=this[B].some((e=>!e[I]&&e.closed!==true&&e.destroyed!==true))}}e.exports={PoolBase:PoolBase,kClients:B,kNeedDrain:I,kAddClient:D,kRemoveClient:k,kGetDispatcher:b}},6752:(e,A,t)=>{const{kFree:r,kConnected:s,kPending:o,kQueued:n,kRunning:i,kSize:a}=t(7781);const c=Symbol("pool");class PoolStats{constructor(e){this[c]=e}get connected(){return this[c][s]}get free(){return this[c][r]}get pending(){return this[c][o]}get queued(){return this[c][n]}get running(){return this[c][i]}get size(){return this[c][a]}}e.exports=PoolStats},4094:(e,A,t)=>{"use strict";const{PoolBase:r,kClients:s,kNeedDrain:o,kAddClient:n,kGetDispatcher:i}=t(5934);const a=t(1247);const{InvalidArgumentError:c}=t(7221);const g=t(2806);const{kUrl:E,kInterceptors:l}=t(7781);const u=t(4470);const Q=Symbol("options");const C=Symbol("connections");const h=Symbol("factory");function defaultFactory(e,A){return new a(e,A)}class Pool extends r{constructor(e,{connections:A,factory:t=defaultFactory,connect:r,connectTimeout:s,tls:o,maxCachedSessions:n,socketPath:i,autoSelectFamily:a,autoSelectFamilyAttemptTimeout:B,allowH2:I,...d}={}){super();if(A!=null&&(!Number.isFinite(A)||A<0)){throw new c("invalid connections")}if(typeof t!=="function"){throw new c("factory must be a function.")}if(r!=null&&typeof r!=="function"&&typeof r!=="object"){throw new c("connect must be a function or an object")}if(typeof r!=="function"){r=u({...o,maxCachedSessions:n,allowH2:I,socketPath:i,timeout:s,...g.nodeHasAutoSelectFamily&&a?{autoSelectFamily:a,autoSelectFamilyAttemptTimeout:B}:undefined,...r})}this[l]=d.interceptors&&d.interceptors.Pool&&Array.isArray(d.interceptors.Pool)?d.interceptors.Pool:[];this[C]=A||null;this[E]=g.parseOrigin(e);this[Q]={...g.deepClone(d),connect:r,allowH2:I};this[Q].interceptors=d.interceptors?{...d.interceptors}:undefined;this[h]=t}[i](){let e=this[s].find((e=>!e[o]));if(e){return e}if(!this[C]||this[s].length{"use strict";const{kProxy:r,kClose:s,kDestroy:o,kInterceptors:n}=t(7781);const{URL:i}=t(7016);const a=t(8787);const c=t(4094);const g=t(6915);const{InvalidArgumentError:E,RequestAbortedError:l}=t(7221);const u=t(4470);const Q=Symbol("proxy agent");const C=Symbol("proxy client");const h=Symbol("proxy headers");const B=Symbol("request tls settings");const I=Symbol("proxy tls settings");const d=Symbol("connect endpoint function");function defaultProtocolPort(e){return e==="https:"?443:80}function buildProxyOptions(e){if(typeof e==="string"){e={uri:e}}if(!e||!e.uri){throw new E("Proxy opts.uri is mandatory")}return{uri:e.uri,protocol:e.protocol||"https"}}function defaultFactory(e,A){return new c(e,A)}class ProxyAgent extends g{constructor(e){super(e);this[r]=buildProxyOptions(e);this[Q]=new a(e);this[n]=e.interceptors&&e.interceptors.ProxyAgent&&Array.isArray(e.interceptors.ProxyAgent)?e.interceptors.ProxyAgent:[];if(typeof e==="string"){e={uri:e}}if(!e||!e.uri){throw new E("Proxy opts.uri is mandatory")}const{clientFactory:A=defaultFactory}=e;if(typeof A!=="function"){throw new E("Proxy opts.clientFactory must be a function.")}this[B]=e.requestTls;this[I]=e.proxyTls;this[h]=e.headers||{};const t=new i(e.uri);const{origin:s,port:o,host:c,username:g,password:p}=t;if(e.auth&&e.token){throw new E("opts.auth cannot be used in combination with opts.token")}else if(e.auth){this[h]["proxy-authorization"]=`Basic ${e.auth}`}else if(e.token){this[h]["proxy-authorization"]=e.token}else if(g&&p){this[h]["proxy-authorization"]=`Basic ${Buffer.from(`${decodeURIComponent(g)}:${decodeURIComponent(p)}`).toString("base64")}`}const m=u({...e.proxyTls});this[d]=u({...e.requestTls});this[C]=A(t,{connect:m});this[Q]=new a({...e,connect:async(e,A)=>{let t=e.host;if(!e.port){t+=`:${defaultProtocolPort(e.protocol)}`}try{const{socket:r,statusCode:n}=await this[C].connect({origin:s,port:o,path:t,signal:e.signal,headers:{...this[h],host:c}});if(n!==200){r.on("error",(()=>{})).destroy();A(new l(`Proxy response (${n}) !== 200 when HTTP Tunneling`))}if(e.protocol!=="https:"){A(null,r);return}let i;if(this[B]){i=this[B].servername}else{i=e.servername}this[d]({...e,servername:i,httpSocket:r},A)}catch(e){A(e)}}})}dispatch(e,A){const{host:t}=new i(e.origin);const r=buildHeaders(e.headers);throwIfProxyAuthIsSent(r);return this[Q].dispatch({...e,headers:{...r,host:t}},A)}async[s](){await this[Q].close();await this[C].close()}async[o](){await this[Q].destroy();await this[C].destroy()}}function buildHeaders(e){if(Array.isArray(e)){const A={};for(let t=0;te.toLowerCase()==="proxy-authorization"));if(A){throw new E("Proxy-Authorization should be sent in ProxyAgent constructor")}}e.exports=ProxyAgent},6190:e=>{"use strict";let A=Date.now();let t;const r=[];function onTimeout(){A=Date.now();let e=r.length;let t=0;while(t0&&A>=s.state){s.state=-1;s.callback(s.opaque)}if(s.state===-1){s.state=-2;if(t!==e-1){r[t]=r.pop()}else{r.pop()}e-=1}else{t+=1}}if(r.length>0){refreshTimeout()}}function refreshTimeout(){if(t&&t.refresh){t.refresh()}else{clearTimeout(t);t=setTimeout(onTimeout,1e3);if(t.unref){t.unref()}}}class Timeout{constructor(e,A,t){this.callback=e;this.delay=A;this.opaque=t;this.state=-2;this.refresh()}refresh(){if(this.state===-2){r.push(this);if(!t||r.length===1){refreshTimeout()}}this.state=0}clear(){this.state=-1}}e.exports={setTimeout(e,A,t){return A<1e3?setTimeout(e,A,t):new Timeout(e,A,t)},clearTimeout(e){if(e instanceof Timeout){e.clear()}else{clearTimeout(e)}}}},4200:(e,A,t)=>{"use strict";const r=t(1637);const{uid:s,states:o}=t(8763);const{kReadyState:n,kSentClose:i,kByteParser:a,kReceivedClose:c}=t(9879);const{fireEvent:g,failWebsocketConnection:E}=t(1700);const{CloseEvent:l}=t(1521);const{makeRequest:u}=t(1940);const{fetching:Q}=t(5697);const{Headers:C}=t(1815);const{getGlobalDispatcher:h}=t(6875);const{kHeadersList:B}=t(7781);const I={};I.open=r.channel("undici:websocket:open");I.close=r.channel("undici:websocket:close");I.socketError=r.channel("undici:websocket:socket_error");let d;try{d=t(6982)}catch{}function establishWebSocketConnection(e,A,t,r,o){const n=e;n.protocol=e.protocol==="ws:"?"http:":"https:";const i=u({urlList:[n],serviceWorkers:"none",referrer:"no-referrer",mode:"websocket",credentials:"include",cache:"no-store",redirect:"error"});if(o.headers){const e=new C(o.headers)[B];i.headersList=e}const a=d.randomBytes(16).toString("base64");i.headersList.append("sec-websocket-key",a);i.headersList.append("sec-websocket-version","13");for(const e of A){i.headersList.append("sec-websocket-protocol",e)}const c="";const g=Q({request:i,useParallelQueue:true,dispatcher:o.dispatcher??h(),processResponse(e){if(e.type==="error"||e.status!==101){E(t,"Received network error or non-101 status code.");return}if(A.length!==0&&!e.headersList.get("Sec-WebSocket-Protocol")){E(t,"Server did not respond with sent protocols.");return}if(e.headersList.get("Upgrade")?.toLowerCase()!=="websocket"){E(t,'Server did not set Upgrade header to "websocket".');return}if(e.headersList.get("Connection")?.toLowerCase()!=="upgrade"){E(t,'Server did not set Connection header to "upgrade".');return}const o=e.headersList.get("Sec-WebSocket-Accept");const n=d.createHash("sha1").update(a+s).digest("base64");if(o!==n){E(t,"Incorrect hash received in Sec-WebSocket-Accept header.");return}const g=e.headersList.get("Sec-WebSocket-Extensions");if(g!==null&&g!==c){E(t,"Received different permessage-deflate than the one set.");return}const l=e.headersList.get("Sec-WebSocket-Protocol");if(l!==null&&l!==i.headersList.get("Sec-WebSocket-Protocol")){E(t,"Protocol was not set in the opening handshake.");return}e.socket.on("data",onSocketData);e.socket.on("close",onSocketClose);e.socket.on("error",onSocketError);if(I.open.hasSubscribers){I.open.publish({address:e.socket.address(),protocol:l,extensions:g})}r(e)}});return g}function onSocketData(e){if(!this.ws[a].write(e)){this.pause()}}function onSocketClose(){const{ws:e}=this;const A=e[i]&&e[c];let t=1005;let r="";const s=e[a].closingInfo;if(s){t=s.code??1005;r=s.reason}else if(!e[i]){t=1006}e[n]=o.CLOSED;g("close",e,l,{wasClean:A,code:t,reason:r});if(I.close.hasSubscribers){I.close.publish({websocket:e,code:t,reason:r})}}function onSocketError(e){const{ws:A}=this;A[n]=o.CLOSING;if(I.socketError.hasSubscribers){I.socketError.publish(e)}this.destroy()}e.exports={establishWebSocketConnection:establishWebSocketConnection}},8763:e=>{"use strict";const A="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";const t={enumerable:true,writable:false,configurable:false};const r={CONNECTING:0,OPEN:1,CLOSING:2,CLOSED:3};const s={CONTINUATION:0,TEXT:1,BINARY:2,CLOSE:8,PING:9,PONG:10};const o=2**16-1;const n={INFO:0,PAYLOADLENGTH_16:2,PAYLOADLENGTH_64:3,READ_DATA:4};const i=Buffer.allocUnsafe(0);e.exports={uid:A,staticPropertyDescriptors:t,states:r,opcodes:s,maxUnsigned16Bit:o,parserStates:n,emptyBuffer:i}},1521:(e,A,t)=>{"use strict";const{webidl:r}=t(6684);const{kEnumerableProperty:s}=t(2806);const{MessagePort:o}=t(8167);class MessageEvent extends Event{#o;constructor(e,A={}){r.argumentLengthCheck(arguments,1,{header:"MessageEvent constructor"});e=r.converters.DOMString(e);A=r.converters.MessageEventInit(A);super(e,A);this.#o=A}get data(){r.brandCheck(this,MessageEvent);return this.#o.data}get origin(){r.brandCheck(this,MessageEvent);return this.#o.origin}get lastEventId(){r.brandCheck(this,MessageEvent);return this.#o.lastEventId}get source(){r.brandCheck(this,MessageEvent);return this.#o.source}get ports(){r.brandCheck(this,MessageEvent);if(!Object.isFrozen(this.#o.ports)){Object.freeze(this.#o.ports)}return this.#o.ports}initMessageEvent(e,A=false,t=false,s=null,o="",n="",i=null,a=[]){r.brandCheck(this,MessageEvent);r.argumentLengthCheck(arguments,1,{header:"MessageEvent.initMessageEvent"});return new MessageEvent(e,{bubbles:A,cancelable:t,data:s,origin:o,lastEventId:n,source:i,ports:a})}}class CloseEvent extends Event{#o;constructor(e,A={}){r.argumentLengthCheck(arguments,1,{header:"CloseEvent constructor"});e=r.converters.DOMString(e);A=r.converters.CloseEventInit(A);super(e,A);this.#o=A}get wasClean(){r.brandCheck(this,CloseEvent);return this.#o.wasClean}get code(){r.brandCheck(this,CloseEvent);return this.#o.code}get reason(){r.brandCheck(this,CloseEvent);return this.#o.reason}}class ErrorEvent extends Event{#o;constructor(e,A){r.argumentLengthCheck(arguments,1,{header:"ErrorEvent constructor"});super(e,A);e=r.converters.DOMString(e);A=r.converters.ErrorEventInit(A??{});this.#o=A}get message(){r.brandCheck(this,ErrorEvent);return this.#o.message}get filename(){r.brandCheck(this,ErrorEvent);return this.#o.filename}get lineno(){r.brandCheck(this,ErrorEvent);return this.#o.lineno}get colno(){r.brandCheck(this,ErrorEvent);return this.#o.colno}get error(){r.brandCheck(this,ErrorEvent);return this.#o.error}}Object.defineProperties(MessageEvent.prototype,{[Symbol.toStringTag]:{value:"MessageEvent",configurable:true},data:s,origin:s,lastEventId:s,source:s,ports:s,initMessageEvent:s});Object.defineProperties(CloseEvent.prototype,{[Symbol.toStringTag]:{value:"CloseEvent",configurable:true},reason:s,code:s,wasClean:s});Object.defineProperties(ErrorEvent.prototype,{[Symbol.toStringTag]:{value:"ErrorEvent",configurable:true},message:s,filename:s,lineno:s,colno:s,error:s});r.converters.MessagePort=r.interfaceConverter(o);r.converters["sequence"]=r.sequenceConverter(r.converters.MessagePort);const n=[{key:"bubbles",converter:r.converters.boolean,defaultValue:false},{key:"cancelable",converter:r.converters.boolean,defaultValue:false},{key:"composed",converter:r.converters.boolean,defaultValue:false}];r.converters.MessageEventInit=r.dictionaryConverter([...n,{key:"data",converter:r.converters.any,defaultValue:null},{key:"origin",converter:r.converters.USVString,defaultValue:""},{key:"lastEventId",converter:r.converters.DOMString,defaultValue:""},{key:"source",converter:r.nullableConverter(r.converters.MessagePort),defaultValue:null},{key:"ports",converter:r.converters["sequence"],get defaultValue(){return[]}}]);r.converters.CloseEventInit=r.dictionaryConverter([...n,{key:"wasClean",converter:r.converters.boolean,defaultValue:false},{key:"code",converter:r.converters["unsigned short"],defaultValue:0},{key:"reason",converter:r.converters.USVString,defaultValue:""}]);r.converters.ErrorEventInit=r.dictionaryConverter([...n,{key:"message",converter:r.converters.DOMString,defaultValue:""},{key:"filename",converter:r.converters.USVString,defaultValue:""},{key:"lineno",converter:r.converters["unsigned long"],defaultValue:0},{key:"colno",converter:r.converters["unsigned long"],defaultValue:0},{key:"error",converter:r.converters.any}]);e.exports={MessageEvent:MessageEvent,CloseEvent:CloseEvent,ErrorEvent:ErrorEvent}},9183:(e,A,t)=>{"use strict";const{maxUnsigned16Bit:r}=t(8763);let s;try{s=t(6982)}catch{}class WebsocketFrameSend{constructor(e){this.frameData=e;this.maskKey=s.randomBytes(4)}createFrame(e){const A=this.frameData?.byteLength??0;let t=A;let s=6;if(A>r){s+=8;t=127}else if(A>125){s+=2;t=126}const o=Buffer.allocUnsafe(A+s);o[0]=o[1]=0;o[0]|=128;o[0]=(o[0]&240)+e; +/*! ws. MIT License. Einar Otto Stangvik */o[s-4]=this.maskKey[0];o[s-3]=this.maskKey[1];o[s-2]=this.maskKey[2];o[s-1]=this.maskKey[3];o[1]=t;if(t===126){o.writeUInt16BE(A,2)}else if(t===127){o[2]=o[3]=0;o.writeUIntBE(A,4,6)}o[1]|=128;for(let e=0;e{"use strict";const{Writable:r}=t(2203);const s=t(1637);const{parserStates:o,opcodes:n,states:i,emptyBuffer:a}=t(8763);const{kReadyState:c,kSentClose:g,kResponse:E,kReceivedClose:l}=t(9879);const{isValidStatusCode:u,failWebsocketConnection:Q,websocketMessageReceived:C}=t(1700);const{WebsocketFrameSend:h}=t(9183);const B={};B.ping=s.channel("undici:websocket:ping");B.pong=s.channel("undici:websocket:pong");class ByteParser extends r{#n=[];#i=0;#a=o.INFO;#c={};#g=[];constructor(e){super();this.ws=e}_write(e,A,t){this.#n.push(e);this.#i+=e.length;this.run(t)}run(e){while(true){if(this.#a===o.INFO){if(this.#i<2){return e()}const A=this.consume(2);this.#c.fin=(A[0]&128)!==0;this.#c.opcode=A[0]&15;this.#c.originalOpcode??=this.#c.opcode;this.#c.fragmented=!this.#c.fin&&this.#c.opcode!==n.CONTINUATION;if(this.#c.fragmented&&this.#c.opcode!==n.BINARY&&this.#c.opcode!==n.TEXT){Q(this.ws,"Invalid frame type was fragmented.");return}const t=A[1]&127;if(t<=125){this.#c.payloadLength=t;this.#a=o.READ_DATA}else if(t===126){this.#a=o.PAYLOADLENGTH_16}else if(t===127){this.#a=o.PAYLOADLENGTH_64}if(this.#c.fragmented&&t>125){Q(this.ws,"Fragmented frame exceeded 125 bytes.");return}else if((this.#c.opcode===n.PING||this.#c.opcode===n.PONG||this.#c.opcode===n.CLOSE)&&t>125){Q(this.ws,"Payload length for control frame exceeded 125 bytes.");return}else if(this.#c.opcode===n.CLOSE){if(t===1){Q(this.ws,"Received close frame with a 1-byte body.");return}const e=this.consume(t);this.#c.closeInfo=this.parseCloseBody(false,e);if(!this.ws[g]){const e=Buffer.allocUnsafe(2);e.writeUInt16BE(this.#c.closeInfo.code,0);const A=new h(e);this.ws[E].socket.write(A.createFrame(n.CLOSE),(e=>{if(!e){this.ws[g]=true}}))}this.ws[c]=i.CLOSING;this.ws[l]=true;this.end();return}else if(this.#c.opcode===n.PING){const A=this.consume(t);if(!this.ws[l]){const e=new h(A);this.ws[E].socket.write(e.createFrame(n.PONG));if(B.ping.hasSubscribers){B.ping.publish({payload:A})}}this.#a=o.INFO;if(this.#i>0){continue}else{e();return}}else if(this.#c.opcode===n.PONG){const A=this.consume(t);if(B.pong.hasSubscribers){B.pong.publish({payload:A})}if(this.#i>0){continue}else{e();return}}}else if(this.#a===o.PAYLOADLENGTH_16){if(this.#i<2){return e()}const A=this.consume(2);this.#c.payloadLength=A.readUInt16BE(0);this.#a=o.READ_DATA}else if(this.#a===o.PAYLOADLENGTH_64){if(this.#i<8){return e()}const A=this.consume(8);const t=A.readUInt32BE(0);if(t>2**31-1){Q(this.ws,"Received payload length > 2^31 bytes.");return}const r=A.readUInt32BE(4);this.#c.payloadLength=(t<<8)+r;this.#a=o.READ_DATA}else if(this.#a===o.READ_DATA){if(this.#i=this.#c.payloadLength){const e=this.consume(this.#c.payloadLength);this.#g.push(e);if(!this.#c.fragmented||this.#c.fin&&this.#c.opcode===n.CONTINUATION){const e=Buffer.concat(this.#g);C(this.ws,this.#c.originalOpcode,e);this.#c={};this.#g.length=0}this.#a=o.INFO}}if(this.#i>0){continue}else{e();break}}}consume(e){if(e>this.#i){return null}else if(e===0){return a}if(this.#n[0].length===e){this.#i-=this.#n[0].length;return this.#n.shift()}const A=Buffer.allocUnsafe(e);let t=0;while(t!==e){const r=this.#n[0];const{length:s}=r;if(s+t===e){A.set(this.#n.shift(),t);break}else if(s+t>e){A.set(r.subarray(0,e-t),t);this.#n[0]=r.subarray(e-t);break}else{A.set(this.#n.shift(),t);t+=r.length}}this.#i-=e;return A}parseCloseBody(e,A){let t;if(A.length>=2){t=A.readUInt16BE(0)}if(e){if(!u(t)){return null}return{code:t}}let r=A.subarray(2);if(r[0]===239&&r[1]===187&&r[2]===191){r=r.subarray(3)}if(t!==undefined&&!u(t)){return null}try{r=new TextDecoder("utf-8",{fatal:true}).decode(r)}catch{return null}return{code:t,reason:r}}get closingInfo(){return this.#c.closeInfo}}e.exports={ByteParser:ByteParser}},9879:e=>{"use strict";e.exports={kWebSocketURL:Symbol("url"),kReadyState:Symbol("ready state"),kController:Symbol("controller"),kResponse:Symbol("response"),kBinaryType:Symbol("binary type"),kSentClose:Symbol("sent close"),kReceivedClose:Symbol("received close"),kByteParser:Symbol("byte parser")}},1700:(e,A,t)=>{"use strict";const{kReadyState:r,kController:s,kResponse:o,kBinaryType:n,kWebSocketURL:i}=t(9879);const{states:a,opcodes:c}=t(8763);const{MessageEvent:g,ErrorEvent:E}=t(1521);function isEstablished(e){return e[r]===a.OPEN}function isClosing(e){return e[r]===a.CLOSING}function isClosed(e){return e[r]===a.CLOSED}function fireEvent(e,A,t=Event,r){const s=new t(e,r);A.dispatchEvent(s)}function websocketMessageReceived(e,A,t){if(e[r]!==a.OPEN){return}let s;if(A===c.TEXT){try{s=new TextDecoder("utf-8",{fatal:true}).decode(t)}catch{failWebsocketConnection(e,"Received invalid UTF-8 in text frame.");return}}else if(A===c.BINARY){if(e[n]==="blob"){s=new Blob([t])}else{s=new Uint8Array(t).buffer}}fireEvent("message",e,g,{origin:e[i].origin,data:s})}function isValidSubprotocol(e){if(e.length===0){return false}for(const A of e){const e=A.charCodeAt(0);if(e<33||e>126||A==="("||A===")"||A==="<"||A===">"||A==="@"||A===","||A===";"||A===":"||A==="\\"||A==='"'||A==="/"||A==="["||A==="]"||A==="?"||A==="="||A==="{"||A==="}"||e===32||e===9){return false}}return true}function isValidStatusCode(e){if(e>=1e3&&e<1015){return e!==1004&&e!==1005&&e!==1006}return e>=3e3&&e<=4999}function failWebsocketConnection(e,A){const{[s]:t,[o]:r}=e;t.abort();if(r?.socket&&!r.socket.destroyed){r.socket.destroy()}if(A){fireEvent("error",e,E,{error:new Error(A)})}}e.exports={isEstablished:isEstablished,isClosing:isClosing,isClosed:isClosed,fireEvent:fireEvent,isValidSubprotocol:isValidSubprotocol,isValidStatusCode:isValidStatusCode,failWebsocketConnection:failWebsocketConnection,websocketMessageReceived:websocketMessageReceived}},7045:(e,A,t)=>{"use strict";const{webidl:r}=t(6684);const{DOMException:s}=t(6040);const{URLSerializer:o}=t(7160);const{getGlobalOrigin:n}=t(574);const{staticPropertyDescriptors:i,states:a,opcodes:c,emptyBuffer:g}=t(8763);const{kWebSocketURL:E,kReadyState:l,kController:u,kBinaryType:Q,kResponse:C,kSentClose:h,kByteParser:B}=t(9879);const{isEstablished:I,isClosing:d,isValidSubprotocol:p,failWebsocketConnection:m,fireEvent:y}=t(1700);const{establishWebSocketConnection:w}=t(4200);const{WebsocketFrameSend:R}=t(9183);const{ByteParser:b}=t(4697);const{kEnumerableProperty:D,isBlobLike:k}=t(2806);const{getGlobalDispatcher:F}=t(6875);const{types:S}=t(9023);let T=false;class WebSocket extends EventTarget{#E={open:null,error:null,close:null,message:null};#l=0;#u="";#Q="";constructor(e,A=[]){super();r.argumentLengthCheck(arguments,1,{header:"WebSocket constructor"});if(!T){T=true;process.emitWarning("WebSockets are experimental, expect them to change at any time.",{code:"UNDICI-WS"})}const t=r.converters["DOMString or sequence or WebSocketInit"](A);e=r.converters.USVString(e);A=t.protocols;const o=n();let i;try{i=new URL(e,o)}catch(e){throw new s(e,"SyntaxError")}if(i.protocol==="http:"){i.protocol="ws:"}else if(i.protocol==="https:"){i.protocol="wss:"}if(i.protocol!=="ws:"&&i.protocol!=="wss:"){throw new s(`Expected a ws: or wss: protocol, got ${i.protocol}`,"SyntaxError")}if(i.hash||i.href.endsWith("#")){throw new s("Got fragment","SyntaxError")}if(typeof A==="string"){A=[A]}if(A.length!==new Set(A.map((e=>e.toLowerCase()))).size){throw new s("Invalid Sec-WebSocket-Protocol value","SyntaxError")}if(A.length>0&&!A.every((e=>p(e)))){throw new s("Invalid Sec-WebSocket-Protocol value","SyntaxError")}this[E]=new URL(i.href);this[u]=w(i,A,this,(e=>this.#C(e)),t);this[l]=WebSocket.CONNECTING;this[Q]="blob"}close(e=undefined,A=undefined){r.brandCheck(this,WebSocket);if(e!==undefined){e=r.converters["unsigned short"](e,{clamp:true})}if(A!==undefined){A=r.converters.USVString(A)}if(e!==undefined){if(e!==1e3&&(e<3e3||e>4999)){throw new s("invalid code","InvalidAccessError")}}let t=0;if(A!==undefined){t=Buffer.byteLength(A);if(t>123){throw new s(`Reason must be less than 123 bytes; received ${t}`,"SyntaxError")}}if(this[l]===WebSocket.CLOSING||this[l]===WebSocket.CLOSED){}else if(!I(this)){m(this,"Connection was closed before it was established.");this[l]=WebSocket.CLOSING}else if(!d(this)){const r=new R;if(e!==undefined&&A===undefined){r.frameData=Buffer.allocUnsafe(2);r.frameData.writeUInt16BE(e,0)}else if(e!==undefined&&A!==undefined){r.frameData=Buffer.allocUnsafe(2+t);r.frameData.writeUInt16BE(e,0);r.frameData.write(A,2,"utf-8")}else{r.frameData=g}const s=this[C].socket;s.write(r.createFrame(c.CLOSE),(e=>{if(!e){this[h]=true}}));this[l]=a.CLOSING}else{this[l]=WebSocket.CLOSING}}send(e){r.brandCheck(this,WebSocket);r.argumentLengthCheck(arguments,1,{header:"WebSocket.send"});e=r.converters.WebSocketSendData(e);if(this[l]===WebSocket.CONNECTING){throw new s("Sent before connected.","InvalidStateError")}if(!I(this)||d(this)){return}const A=this[C].socket;if(typeof e==="string"){const t=Buffer.from(e);const r=new R(t);const s=r.createFrame(c.TEXT);this.#l+=t.byteLength;A.write(s,(()=>{this.#l-=t.byteLength}))}else if(S.isArrayBuffer(e)){const t=Buffer.from(e);const r=new R(t);const s=r.createFrame(c.BINARY);this.#l+=t.byteLength;A.write(s,(()=>{this.#l-=t.byteLength}))}else if(ArrayBuffer.isView(e)){const t=Buffer.from(e,e.byteOffset,e.byteLength);const r=new R(t);const s=r.createFrame(c.BINARY);this.#l+=t.byteLength;A.write(s,(()=>{this.#l-=t.byteLength}))}else if(k(e)){const t=new R;e.arrayBuffer().then((e=>{const r=Buffer.from(e);t.frameData=r;const s=t.createFrame(c.BINARY);this.#l+=r.byteLength;A.write(s,(()=>{this.#l-=r.byteLength}))}))}}get readyState(){r.brandCheck(this,WebSocket);return this[l]}get bufferedAmount(){r.brandCheck(this,WebSocket);return this.#l}get url(){r.brandCheck(this,WebSocket);return o(this[E])}get extensions(){r.brandCheck(this,WebSocket);return this.#Q}get protocol(){r.brandCheck(this,WebSocket);return this.#u}get onopen(){r.brandCheck(this,WebSocket);return this.#E.open}set onopen(e){r.brandCheck(this,WebSocket);if(this.#E.open){this.removeEventListener("open",this.#E.open)}if(typeof e==="function"){this.#E.open=e;this.addEventListener("open",e)}else{this.#E.open=null}}get onerror(){r.brandCheck(this,WebSocket);return this.#E.error}set onerror(e){r.brandCheck(this,WebSocket);if(this.#E.error){this.removeEventListener("error",this.#E.error)}if(typeof e==="function"){this.#E.error=e;this.addEventListener("error",e)}else{this.#E.error=null}}get onclose(){r.brandCheck(this,WebSocket);return this.#E.close}set onclose(e){r.brandCheck(this,WebSocket);if(this.#E.close){this.removeEventListener("close",this.#E.close)}if(typeof e==="function"){this.#E.close=e;this.addEventListener("close",e)}else{this.#E.close=null}}get onmessage(){r.brandCheck(this,WebSocket);return this.#E.message}set onmessage(e){r.brandCheck(this,WebSocket);if(this.#E.message){this.removeEventListener("message",this.#E.message)}if(typeof e==="function"){this.#E.message=e;this.addEventListener("message",e)}else{this.#E.message=null}}get binaryType(){r.brandCheck(this,WebSocket);return this[Q]}set binaryType(e){r.brandCheck(this,WebSocket);if(e!=="blob"&&e!=="arraybuffer"){this[Q]="blob"}else{this[Q]=e}}#C(e){this[C]=e;const A=new b(this);A.on("drain",(function onParserDrain(){this.ws[C].socket.resume()}));e.socket.ws=this;this[B]=A;this[l]=a.OPEN;const t=e.headersList.get("sec-websocket-extensions");if(t!==null){this.#Q=t}const r=e.headersList.get("sec-websocket-protocol");if(r!==null){this.#u=r}y("open",this)}}WebSocket.CONNECTING=WebSocket.prototype.CONNECTING=a.CONNECTING;WebSocket.OPEN=WebSocket.prototype.OPEN=a.OPEN;WebSocket.CLOSING=WebSocket.prototype.CLOSING=a.CLOSING;WebSocket.CLOSED=WebSocket.prototype.CLOSED=a.CLOSED;Object.defineProperties(WebSocket.prototype,{CONNECTING:i,OPEN:i,CLOSING:i,CLOSED:i,url:D,readyState:D,bufferedAmount:D,onopen:D,onerror:D,onclose:D,close:D,onmessage:D,binaryType:D,send:D,extensions:D,protocol:D,[Symbol.toStringTag]:{value:"WebSocket",writable:false,enumerable:false,configurable:true}});Object.defineProperties(WebSocket,{CONNECTING:i,OPEN:i,CLOSING:i,CLOSED:i});r.converters["sequence"]=r.sequenceConverter(r.converters.DOMString);r.converters["DOMString or sequence"]=function(e){if(r.util.Type(e)==="Object"&&Symbol.iterator in e){return r.converters["sequence"](e)}return r.converters.DOMString(e)};r.converters.WebSocketInit=r.dictionaryConverter([{key:"protocols",converter:r.converters["DOMString or sequence"],get defaultValue(){return[]}},{key:"dispatcher",converter:e=>e,get defaultValue(){return F()}},{key:"headers",converter:r.nullableConverter(r.converters.HeadersInit)}]);r.converters["DOMString or sequence or WebSocketInit"]=function(e){if(r.util.Type(e)==="Object"&&!(Symbol.iterator in e)){return r.converters.WebSocketInit(e)}return{protocols:r.converters["DOMString or sequence"](e)}};r.converters.WebSocketSendData=function(e){if(r.util.Type(e)==="Object"){if(k(e)){return r.converters.Blob(e,{strict:false})}if(ArrayBuffer.isView(e)||S.isAnyArrayBuffer(e)){return r.converters.BufferSource(e)}}return r.converters.USVString(e)};e.exports={WebSocket:WebSocket}},9653:(e,A)=>{"use strict";Object.defineProperty(A,"__esModule",{value:true});function getUserAgent(){if(typeof navigator==="object"&&"userAgent"in navigator){return navigator.userAgent}if(typeof process==="object"&&"version"in process){return`Node.js/${process.version.substr(1)} (${process.platform}; ${process.arch})`}return""}A.getUserAgent=getUserAgent},1459:e=>{e.exports=wrappy;function wrappy(e,A){if(e&&A)return wrappy(e)(A);if(typeof e!=="function")throw new TypeError("need wrapper function");Object.keys(e).forEach((function(A){wrapper[A]=e[A]}));return wrapper;function wrapper(){var A=new Array(arguments.length);for(var t=0;t{"use strict";Object.defineProperty(A,"__esModule",{value:true});A.runGate=runGate;const t=5*60*1e3;const r=5*60*60*1e3;const s="thank you, next";function errorMessage(e){return e instanceof Error?e.message:String(e)}function errorStatus(e){if(!e||typeof e!=="object")return undefined;const A=e;return A.status??A.response?.status}function isTransientApiError(e){const A=errorStatus(e);return A===429||A!==undefined&&A>=500&&A<600}function escapeCell(e){return String(e??"").replaceAll("|","\\|").replaceAll("\n"," ")}function checkState(e){if(!e||e.status!=="completed")return"waiting";return e.conclusion==="success"?"success":"unsuccessful"}function gateDecision(e){if(e.some((e=>e.state==="success"))){return"open"}if(e.length===3&&e.every((e=>e.state==="unsuccessful"))){return"fail"}return"wait"}function sameRepository(e,A){return e.head?.repo?.full_name===A}function sameRevision(e,A){return A.state==="open"&&e.head.sha===A.head.sha&&e.base.ref===A.base.ref&&e.base.sha===A.base.sha}async function runGate({github:e,context:A,core:o}){o.setOutput("skip","false");const n=Date.now();let i="";const{owner:a,repo:c}=A.repo;const g=A.payload.repository?.full_name??"";async function listOpenPulls(A){const t=await e.paginate(e.rest.pulls.list,{owner:a,repo:c,state:"open",per_page:100,...A});return t}async function getPull(A){const{data:t}=await e.rest.pulls.get({owner:a,repo:c,pull_number:A});return t}async function findPredecessor(e){const A=(await listOpenPulls({head:`${a}:${e.base.ref}`})).filter((A=>A.number!==e.number&&A.head.ref===e.base.ref&&sameRepository(A,g)));return{ambiguous:A.length>1,pull:A[0]}}async function findSuccessors(e){return(await listOpenPulls({base:e.head.ref})).filter((A=>A.number!==e.number&&A.base.ref===e.head.ref&&sameRepository(A,g)))}async function discoverTopology(){const e=A.payload.pull_request?.number;if(!e)throw new Error("Missing pull request number");const t=await getPull(e);if(!sameRepository(t,g)){return{current:t,role:"fork",reason:"fork PRs always run immediately"}}if(t.labels.some((e=>e.name===process.env.BYPASS_LABEL))){return{current:t,role:"bypass",reason:"bypass label is present"}}if((await findSuccessors(t)).length===0){return{current:t,role:"top",reason:"no open PR is based on this head branch"}}const r=[];const s=new Set([t.number]);let o=t;while(r.length<3){const e=await findPredecessor(o);if(e.ambiguous){return{current:t,role:"ambiguous",reason:`multiple open PRs have head branch ${o.base.ref}`}}if(!e.pull)break;if(s.has(e.pull.number)){return{current:t,role:"ambiguous",reason:"cycle detected in PR base branches"}}s.add(e.pull.number);r.push(e.pull);o=e.pull}if(r.length<3){return{current:t,role:"first-three",reason:`only ${r.length} open predecessor PR(s) are reachable`}}return{current:t,role:"middle",predecessors:r}}async function latestRequiredCheck(A){const{data:t}=await e.rest.checks.listForRef({owner:a,repo:c,ref:A.head.sha,check_name:s,filter:"latest",per_page:100});const r=t.check_runs.filter((e=>e.name===s&&e.app?.slug==="github-actions"&&e.head_sha===A.head.sha&&e.pull_requests?.some((e=>e.number===A.number&&e.head?.sha===A.head.sha&&e.base?.ref===A.base.ref&&e.base?.sha===A.base.sha)))).sort(((e,A)=>A.id-e.id))[0];return{pull:A,check:r??null,state:checkState(r??null)}}async function stillDecisive(e,A,t){const r=await getPull(e.current.number);if(!sameRevision(e.current,r))return false;const s=t==="open"?[A.find((e=>e.state==="success"))]:A;for(const e of s){const A=await getPull(e.pull.number);if(!sameRevision(e.pull,A))return false;const t=await latestRequiredCheck(A);if(t.state!==e.state||t.check?.id!==e.check?.id){return false}}return true}function fingerprint(e){return JSON.stringify({role:e.role,reason:e.reason,candidates:e.candidates?.map((e=>({number:e.pull.number,head:e.pull.head.sha,base:e.pull.base.sha,status:e.check?.status,conclusion:e.check?.conclusion})))})}async function writeSummary(e,t,r){try{const s=e.current;const i=Math.floor((Date.now()-n)/6e4);const a=["# PR Stack CI Gate","",`- PR: #${s?.number??A.payload.pull_request?.number??"n/a"}`,`- Branches: \`${escapeCell(s?.base?.ref)}\` ← \`${escapeCell(s?.head?.ref)}\``,`- Role: **${escapeCell(e.role)}**`,`- Result: **${escapeCell(t)}**`,`- Reason: ${escapeCell(r)}`,`- Elapsed: ${i} minute(s)`];if(e.candidates?.length){a.push("","| PR | Base ← Head | Head SHA | Base SHA | Check | State |","|---:|---|---|---|---|---|");for(const A of e.candidates){const e=A.check?`[${A.check.status}/${A.check.conclusion??""}](${A.check.html_url})`:"not reported";a.push(`| #${A.pull.number} | \`${escapeCell(A.pull.base.ref)}\` ← \`${escapeCell(A.pull.head.ref)}\` | \`${escapeCell(A.pull.head.sha?.slice(0,12))}\` | \`${escapeCell(A.pull.base.sha?.slice(0,12))}\` | ${e} | ${A.state} |`)}}await o.summary.addRaw(`${a.join("\n")}\n`).write()}catch(e){o.warning(`Could not write PR Stack CI Gate summary: ${errorMessage(e)}`)}}try{if(A.eventName!=="pull_request"){const e=`${A.eventName} runs immediately`;await writeSummary({role:"non-pr",reason:e},"open",e);return}while(true){let e;const a=[];try{e=await discoverTopology();if(e.role==="middle"){for(const A of e.predecessors){a.push(await latestRequiredCheck(A))}const A=gateDecision(a);if(A!=="wait"&&!await stillDecisive(e,a,A)){o.info("Stack/check state changed during verification; retrying");await new Promise((e=>setTimeout(e,t)));continue}}}catch(e){if(!isTransientApiError(e))throw e;if(Date.now()-n>=r){await writeSummary({current:A.payload.pull_request,role:"error",reason:errorMessage(e)},"open","Five-hour transient API error deadline reached; starting full CI");return}o.warning(`Transient GitHub API error (${errorStatus(e)}); retrying in five minutes: ${errorMessage(e)}`);await new Promise((e=>setTimeout(e,t)));continue}if(e.role!=="middle"){await writeSummary(e,"open",e.reason??"");return}const c={...e,candidates:a};const g=gateDecision(a);const E=fingerprint(c);if(E!==i){o.info(`PR #${e.current.number}: ${a.map((e=>`#${e.pull.number}=${e.state}`)).join(", ")}`);i=E}if(g==="open"){const e=a.find((e=>e.state==="success"));await writeSummary(c,"open",`PR #${e.pull.number} passed ${s}`);return}if(g==="fail"){const e=`All three predecessor PRs completed ${s} without success. Rerun this workflow after a predecessor passes, or apply the ${process.env.BYPASS_LABEL} label.`;await writeSummary(c,"failed",e);o.setFailed(e);return}if(Date.now()-n>=r){await writeSummary(c,"open","Five-hour waiting deadline reached; failing open and starting full CI");return}const l=new Date(Date.now()+t);o.info(`No predecessor has passed yet; polling again at ${l.toISOString()}`);await new Promise((e=>setTimeout(e,t)))}}catch(e){o.warning(`PR stack classification failed; failing open and starting full CI: ${String(e)}`);await writeSummary({current:A.payload.pull_request,role:"error",reason:errorMessage(e)},"open",`Classification/API error; failing open: ${errorMessage(e)}`)}}},3887:function(e,A,t){"use strict";var r=this&&this.__createBinding||(Object.create?function(e,A,t,r){if(r===undefined)r=t;var s=Object.getOwnPropertyDescriptor(A,t);if(!s||("get"in s?!A.__esModule:s.writable||s.configurable)){s={enumerable:true,get:function(){return A[t]}}}Object.defineProperty(e,r,s)}:function(e,A,t,r){if(r===undefined)r=t;e[r]=A[t]});var s=this&&this.__setModuleDefault||(Object.create?function(e,A){Object.defineProperty(e,"default",{enumerable:true,value:A})}:function(e,A){e["default"]=A});var o=this&&this.__importStar||function(){var ownKeys=function(e){ownKeys=Object.getOwnPropertyNames||function(e){var A=[];for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))A[A.length]=t;return A};return ownKeys(e)};return function(e){if(e&&e.__esModule)return e;var A={};if(e!=null)for(var t=ownKeys(e),o=0;o{n.warning(`PR stack gate could not start; running full CI: ${String(e)}`);n.setOutput("skip","false")}))},2613:e=>{"use strict";e.exports=require("assert")},290:e=>{"use strict";e.exports=require("async_hooks")},181:e=>{"use strict";e.exports=require("buffer")},5317:e=>{"use strict";e.exports=require("child_process")},4236:e=>{"use strict";e.exports=require("console")},6982:e=>{"use strict";e.exports=require("crypto")},1637:e=>{"use strict";e.exports=require("diagnostics_channel")},4434:e=>{"use strict";e.exports=require("events")},9896:e=>{"use strict";e.exports=require("fs")},8611:e=>{"use strict";e.exports=require("http")},5675:e=>{"use strict";e.exports=require("http2")},5692:e=>{"use strict";e.exports=require("https")},9278:e=>{"use strict";e.exports=require("net")},8474:e=>{"use strict";e.exports=require("node:events")},7075:e=>{"use strict";e.exports=require("node:stream")},7975:e=>{"use strict";e.exports=require("node:util")},857:e=>{"use strict";e.exports=require("os")},6928:e=>{"use strict";e.exports=require("path")},2987:e=>{"use strict";e.exports=require("perf_hooks")},3480:e=>{"use strict";e.exports=require("querystring")},2203:e=>{"use strict";e.exports=require("stream")},3774:e=>{"use strict";e.exports=require("stream/web")},3193:e=>{"use strict";e.exports=require("string_decoder")},3557:e=>{"use strict";e.exports=require("timers")},4756:e=>{"use strict";e.exports=require("tls")},7016:e=>{"use strict";e.exports=require("url")},9023:e=>{"use strict";e.exports=require("util")},8253:e=>{"use strict";e.exports=require("util/types")},8167:e=>{"use strict";e.exports=require("worker_threads")},3106:e=>{"use strict";e.exports=require("zlib")},1582:(e,A,t)=>{"use strict";const r=t(7075).Writable;const s=t(7975).inherits;const o=t(7784);const n=t(1316);const i=t(143);const a=45;const c=Buffer.from("-");const g=Buffer.from("\r\n");const EMPTY_FN=function(){};function Dicer(e){if(!(this instanceof Dicer)){return new Dicer(e)}r.call(this,e);if(!e||!e.headerFirst&&typeof e.boundary!=="string"){throw new TypeError("Boundary required")}if(typeof e.boundary==="string"){this.setBoundary(e.boundary)}else{this._bparser=undefined}this._headerFirst=e.headerFirst;this._dashes=0;this._parts=0;this._finished=false;this._realFinish=false;this._isPreamble=true;this._justMatched=false;this._firstWrite=true;this._inHeader=true;this._part=undefined;this._cb=undefined;this._ignoreData=false;this._partOpts={highWaterMark:e.partHwm};this._pause=false;const A=this;this._hparser=new i(e);this._hparser.on("header",(function(e){A._inHeader=false;A._part.emit("header",e)}))}s(Dicer,r);Dicer.prototype.emit=function(e){if(e==="finish"&&!this._realFinish){if(!this._finished){const e=this;process.nextTick((function(){e.emit("error",new Error("Unexpected end of multipart data"));if(e._part&&!e._ignoreData){const A=e._isPreamble?"Preamble":"Part";e._part.emit("error",new Error(A+" terminated early due to unexpected end of multipart data"));e._part.push(null);process.nextTick((function(){e._realFinish=true;e.emit("finish");e._realFinish=false}));return}e._realFinish=true;e.emit("finish");e._realFinish=false}))}}else{r.prototype.emit.apply(this,arguments)}};Dicer.prototype._write=function(e,A,t){if(!this._hparser&&!this._bparser){return t()}if(this._headerFirst&&this._isPreamble){if(!this._part){this._part=new n(this._partOpts);if(this.listenerCount("preamble")!==0){this.emit("preamble",this._part)}else{this._ignore()}}const A=this._hparser.push(e);if(!this._inHeader&&A!==undefined&&A{"use strict";const r=t(8474).EventEmitter;const s=t(7975).inherits;const o=t(7433);const n=t(7784);const i=Buffer.from("\r\n\r\n");const a=/\r\n/g;const c=/^([^:]+):[ \t]?([\x00-\xFF]+)?$/;function HeaderParser(e){r.call(this);e=e||{};const A=this;this.nread=0;this.maxed=false;this.npairs=0;this.maxHeaderPairs=o(e,"maxHeaderPairs",2e3);this.maxHeaderSize=o(e,"maxHeaderSize",80*1024);this.buffer="";this.header={};this.finished=false;this.ss=new n(i);this.ss.on("info",(function(e,t,r,s){if(t&&!A.maxed){if(A.nread+s-r>=A.maxHeaderSize){s=A.maxHeaderSize-A.nread+r;A.nread=A.maxHeaderSize;A.maxed=true}else{A.nread+=s-r}A.buffer+=t.toString("binary",r,s)}if(e){A._finish()}}))}s(HeaderParser,r);HeaderParser.prototype.push=function(e){const A=this.ss.push(e);if(this.finished){return A}};HeaderParser.prototype.reset=function(){this.finished=false;this.buffer="";this.header={};this.ss.reset()};HeaderParser.prototype._finish=function(){if(this.buffer){this._parseHeader()}this.ss.matches=this.ss.maxMatches;const e=this.header;this.header={};this.buffer="";this.finished=true;this.nread=this.npairs=0;this.maxed=false;this.emit("header",e)};HeaderParser.prototype._parseHeader=function(){if(this.npairs===this.maxHeaderPairs){return}const e=this.buffer.split(a);const A=e.length;let t,r;for(var s=0;s{"use strict";const r=t(7975).inherits;const s=t(7075).Readable;function PartStream(e){s.call(this,e)}r(PartStream,s);PartStream.prototype._read=function(e){};e.exports=PartStream},7784:(e,A,t)=>{"use strict";const r=t(8474).EventEmitter;const s=t(7975).inherits;function SBMH(e){if(typeof e==="string"){e=Buffer.from(e)}if(!Buffer.isBuffer(e)){throw new TypeError("The needle has to be a String or a Buffer.")}const A=e.length;if(A===0){throw new Error("The needle cannot be an empty String/Buffer.")}if(A>256){throw new Error("The needle cannot have a length bigger than 256.")}this.maxMatches=Infinity;this.matches=0;this._occ=new Array(256).fill(A);this._lookbehind_size=0;this._needle=e;this._bufpos=0;this._lookbehind=Buffer.alloc(A);for(var t=0;t=0){this.emit("info",false,this._lookbehind,0,this._lookbehind_size);this._lookbehind_size=0}else{const t=this._lookbehind_size+o;if(t>0){this.emit("info",false,this._lookbehind,0,t)}this._lookbehind.copy(this._lookbehind,0,t,this._lookbehind_size-t);this._lookbehind_size-=t;e.copy(this._lookbehind,this._lookbehind_size);this._lookbehind_size+=A;this._bufpos=A;return A}}o+=(o>=0)*this._bufpos;if(e.indexOf(t,o)!==-1){o=e.indexOf(t,o);++this.matches;if(o>0){this.emit("info",true,e,this._bufpos,o)}else{this.emit("info",true)}return this._bufpos=o+r}else{o=A-r}while(o0){this.emit("info",false,e,this._bufpos,o{"use strict";const r=t(7075).Writable;const{inherits:s}=t(7975);const o=t(1582);const n=t(8872);const i=t(8055);const a=t(6065);function Busboy(e){if(!(this instanceof Busboy)){return new Busboy(e)}if(typeof e!=="object"){throw new TypeError("Busboy expected an options-Object.")}if(typeof e.headers!=="object"){throw new TypeError("Busboy expected an options-Object with headers-attribute.")}if(typeof e.headers["content-type"]!=="string"){throw new TypeError("Missing Content-Type-header.")}const{headers:A,...t}=e;this.opts={autoDestroy:false,...t};r.call(this,this.opts);this._done=false;this._parser=this.getParserByHeaders(A);this._finished=false}s(Busboy,r);Busboy.prototype.emit=function(e){if(e==="finish"){if(!this._done){this._parser?.end();return}else if(this._finished){return}this._finished=true}r.prototype.emit.apply(this,arguments)};Busboy.prototype.getParserByHeaders=function(e){const A=a(e["content-type"]);const t={defCharset:this.opts.defCharset,fileHwm:this.opts.fileHwm,headers:e,highWaterMark:this.opts.highWaterMark,isPartAFile:this.opts.isPartAFile,limits:this.opts.limits,parsedConType:A,preservePath:this.opts.preservePath};if(n.detect.test(A[0])){return new n(this,t)}if(i.detect.test(A[0])){return new i(this,t)}throw new Error("Unsupported Content-Type.")};Busboy.prototype._write=function(e,A,t){this._parser.write(e,t)};e.exports=Busboy;e.exports["default"]=Busboy;e.exports.Busboy=Busboy;e.exports.Dicer=o},8872:(e,A,t)=>{"use strict";const{Readable:r}=t(7075);const{inherits:s}=t(7975);const o=t(1582);const n=t(6065);const i=t(6187);const a=t(8404);const c=t(7433);const g=/^boundary$/i;const E=/^form-data$/i;const l=/^charset$/i;const u=/^filename$/i;const Q=/^name$/i;Multipart.detect=/^multipart\/form-data/i;function Multipart(e,A){let t;let r;const s=this;let C;const h=A.limits;const B=A.isPartAFile||((e,A,t)=>A==="application/octet-stream"||t!==undefined);const I=A.parsedConType||[];const d=A.defCharset||"utf8";const p=A.preservePath;const m={highWaterMark:A.fileHwm};for(t=0,r=I.length;tD){s.parser.removeListener("part",onPart);s.parser.on("part",skipPart);e.hitPartsLimit=true;e.emit("partsLimit");return skipPart(A)}if(L){const e=L;e.emit("end");e.removeAllListeners("end")}A.on("header",(function(o){let c;let g;let C;let h;let I;let D;let k=0;if(o["content-type"]){C=n(o["content-type"][0]);if(C[0]){c=C[0].toLowerCase();for(t=0,r=C.length;tw){const r=w-k+e.length;if(r>0){t.push(e.slice(0,r))}t.truncated=true;t.bytesRead=w;A.removeAllListeners("data");t.emit("limit");return}else if(!t.push(e)){s._pause=true}t.bytesRead=k};G=function(){U=undefined;t.push(null)}}else{if(T===b){if(!e.hitFieldsLimit){e.hitFieldsLimit=true;e.emit("fieldsLimit")}return skipPart(A)}++T;++N;let t="";let r=false;L=A;F=function(e){if((k+=e.length)>y){const s=y-(k-e.length);t+=e.toString("binary",0,s);r=true;A.removeAllListeners("data")}else{t+=e.toString("binary")}};G=function(){L=undefined;if(t.length){t=i(t,"binary",h)}e.emit("field",g,t,false,r,I,c);--N;checkFinished()}}A._readableState.sync=false;A.on("data",F);A.on("end",G)})).on("error",(function(e){if(U){U.emit("error",e)}}))})).on("error",(function(A){e.emit("error",A)})).on("finish",(function(){G=true;checkFinished()}))}Multipart.prototype.write=function(e,A){const t=this.parser.write(e);if(t&&!this._pause){A()}else{this._needDrain=!t;this._cb=A}};Multipart.prototype.end=function(){const e=this;if(e.parser.writable){e.parser.end()}else if(!e._boy._done){process.nextTick((function(){e._boy._done=true;e._boy.emit("finish")}))}};function skipPart(e){e.resume()}function FileStream(e){r.call(this,e);this.bytesRead=0;this.truncated=false}s(FileStream,r);FileStream.prototype._read=function(e){};e.exports=Multipart},8055:(e,A,t)=>{"use strict";const r=t(7064);const s=t(6187);const o=t(7433);const n=/^charset$/i;UrlEncoded.detect=/^application\/x-www-form-urlencoded/i;function UrlEncoded(e,A){const t=A.limits;const s=A.parsedConType;this.boy=e;this.fieldSizeLimit=o(t,"fieldSize",1*1024*1024);this.fieldNameSizeLimit=o(t,"fieldNameSize",100);this.fieldsLimit=o(t,"fields",Infinity);let i;for(var a=0,c=s.length;an){this._key+=this.decoder.write(e.toString("binary",n,t))}this._state="val";this._hitLimit=false;this._checkingBytes=true;this._val="";this._bytesVal=0;this._valTrunc=false;this.decoder.reset();n=t+1}else if(r!==undefined){++this._fields;let t;const o=this._keyTrunc;if(r>n){t=this._key+=this.decoder.write(e.toString("binary",n,r))}else{t=this._key}this._hitLimit=false;this._checkingBytes=true;this._key="";this._bytesKey=0;this._keyTrunc=false;this.decoder.reset();if(t.length){this.boy.emit("field",s(t,"binary",this.charset),"",o,false)}n=r+1;if(this._fields===this.fieldsLimit){return A()}}else if(this._hitLimit){if(o>n){this._key+=this.decoder.write(e.toString("binary",n,o))}n=o;if((this._bytesKey=this._key.length)===this.fieldNameSizeLimit){this._checkingBytes=false;this._keyTrunc=true}}else{if(nn){this._val+=this.decoder.write(e.toString("binary",n,r))}this.boy.emit("field",s(this._key,"binary",this.charset),s(this._val,"binary",this.charset),this._keyTrunc,this._valTrunc);this._state="key";this._hitLimit=false;this._checkingBytes=true;this._key="";this._bytesKey=0;this._keyTrunc=false;this.decoder.reset();n=r+1;if(this._fields===this.fieldsLimit){return A()}}else if(this._hitLimit){if(o>n){this._val+=this.decoder.write(e.toString("binary",n,o))}n=o;if(this._val===""&&this.fieldSizeLimit===0||(this._bytesVal=this._val.length)===this.fieldSizeLimit){this._checkingBytes=false;this._valTrunc=true}}else{if(n0){this.boy.emit("field",s(this._key,"binary",this.charset),"",this._keyTrunc,false)}else if(this._state==="val"){this.boy.emit("field",s(this._key,"binary",this.charset),s(this._val,"binary",this.charset),this._keyTrunc,this._valTrunc)}this.boy._done=true;this.boy.emit("finish")};e.exports=UrlEncoded},7064:e=>{"use strict";const A=/\+/g;const t=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];function Decoder(){this.buffer=undefined}Decoder.prototype.write=function(e){e=e.replace(A," ");let r="";let s=0;let o=0;const n=e.length;for(;so){r+=e.substring(o,s);o=s}this.buffer="";++o}}if(o{"use strict";e.exports=function basename(e){if(typeof e!=="string"){return""}for(var A=e.length-1;A>=0;--A){switch(e.charCodeAt(A)){case 47:case 92:e=e.slice(A+1);return e===".."||e==="."?"":e}}return e===".."||e==="."?"":e}},6187:function(e){"use strict";const A=new TextDecoder("utf-8");const t=new Map([["utf-8",A],["utf8",A]]);function getDecoder(e){let A;while(true){switch(e){case"utf-8":case"utf8":return r.utf8;case"latin1":case"ascii":case"us-ascii":case"iso-8859-1":case"iso8859-1":case"iso88591":case"iso_8859-1":case"windows-1252":case"iso_8859-1:1987":case"cp1252":case"x-cp1252":return r.latin1;case"utf16le":case"utf-16le":case"ucs2":case"ucs-2":return r.utf16le;case"base64":return r.base64;default:if(A===undefined){A=true;e=e.toLowerCase();continue}return r.other.bind(e)}}}const r={utf8:(e,A)=>{if(e.length===0){return""}if(typeof e==="string"){e=Buffer.from(e,A)}return e.utf8Slice(0,e.length)},latin1:(e,A)=>{if(e.length===0){return""}if(typeof e==="string"){return e}return e.latin1Slice(0,e.length)},utf16le:(e,A)=>{if(e.length===0){return""}if(typeof e==="string"){e=Buffer.from(e,A)}return e.ucs2Slice(0,e.length)},base64:(e,A)=>{if(e.length===0){return""}if(typeof e==="string"){e=Buffer.from(e,A)}return e.base64Slice(0,e.length)},other:(e,A)=>{if(e.length===0){return""}if(typeof e==="string"){e=Buffer.from(e,A)}if(t.has(this.toString())){try{return t.get(this).decode(e)}catch{}}return typeof e==="string"?e:e.toString()}};function decodeText(e,A,t){if(e){return getDecoder(t)(e,A)}return e}e.exports=decodeText},7433:e=>{"use strict";e.exports=function getLimit(e,A,t){if(!e||e[A]===undefined||e[A]===null){return t}if(typeof e[A]!=="number"||isNaN(e[A])){throw new TypeError("Limit "+A+" is not a valid number")}return e[A]}},6065:(e,A,t)=>{"use strict";const r=t(6187);const s=/%[a-fA-F0-9][a-fA-F0-9]/g;const o={"%00":"\0","%01":"","%02":"","%03":"","%04":"","%05":"","%06":"","%07":"","%08":"\b","%09":"\t","%0a":"\n","%0A":"\n","%0b":"\v","%0B":"\v","%0c":"\f","%0C":"\f","%0d":"\r","%0D":"\r","%0e":"","%0E":"","%0f":"","%0F":"","%10":"","%11":"","%12":"","%13":"","%14":"","%15":"","%16":"","%17":"","%18":"","%19":"","%1a":"","%1A":"","%1b":"","%1B":"","%1c":"","%1C":"","%1d":"","%1D":"","%1e":"","%1E":"","%1f":"","%1F":"","%20":" ","%21":"!","%22":'"',"%23":"#","%24":"$","%25":"%","%26":"&","%27":"'","%28":"(","%29":")","%2a":"*","%2A":"*","%2b":"+","%2B":"+","%2c":",","%2C":",","%2d":"-","%2D":"-","%2e":".","%2E":".","%2f":"/","%2F":"/","%30":"0","%31":"1","%32":"2","%33":"3","%34":"4","%35":"5","%36":"6","%37":"7","%38":"8","%39":"9","%3a":":","%3A":":","%3b":";","%3B":";","%3c":"<","%3C":"<","%3d":"=","%3D":"=","%3e":">","%3E":">","%3f":"?","%3F":"?","%40":"@","%41":"A","%42":"B","%43":"C","%44":"D","%45":"E","%46":"F","%47":"G","%48":"H","%49":"I","%4a":"J","%4A":"J","%4b":"K","%4B":"K","%4c":"L","%4C":"L","%4d":"M","%4D":"M","%4e":"N","%4E":"N","%4f":"O","%4F":"O","%50":"P","%51":"Q","%52":"R","%53":"S","%54":"T","%55":"U","%56":"V","%57":"W","%58":"X","%59":"Y","%5a":"Z","%5A":"Z","%5b":"[","%5B":"[","%5c":"\\","%5C":"\\","%5d":"]","%5D":"]","%5e":"^","%5E":"^","%5f":"_","%5F":"_","%60":"`","%61":"a","%62":"b","%63":"c","%64":"d","%65":"e","%66":"f","%67":"g","%68":"h","%69":"i","%6a":"j","%6A":"j","%6b":"k","%6B":"k","%6c":"l","%6C":"l","%6d":"m","%6D":"m","%6e":"n","%6E":"n","%6f":"o","%6F":"o","%70":"p","%71":"q","%72":"r","%73":"s","%74":"t","%75":"u","%76":"v","%77":"w","%78":"x","%79":"y","%7a":"z","%7A":"z","%7b":"{","%7B":"{","%7c":"|","%7C":"|","%7d":"}","%7D":"}","%7e":"~","%7E":"~","%7f":"","%7F":"","%80":"€","%81":"","%82":"‚","%83":"ƒ","%84":"„","%85":"…","%86":"†","%87":"‡","%88":"ˆ","%89":"‰","%8a":"Š","%8A":"Š","%8b":"‹","%8B":"‹","%8c":"Œ","%8C":"Œ","%8d":"","%8D":"","%8e":"Ž","%8E":"Ž","%8f":"","%8F":"","%90":"","%91":"‘","%92":"’","%93":"“","%94":"”","%95":"•","%96":"–","%97":"—","%98":"˜","%99":"™","%9a":"š","%9A":"š","%9b":"›","%9B":"›","%9c":"œ","%9C":"œ","%9d":"","%9D":"","%9e":"ž","%9E":"ž","%9f":"Ÿ","%9F":"Ÿ","%a0":" ","%A0":" ","%a1":"¡","%A1":"¡","%a2":"¢","%A2":"¢","%a3":"£","%A3":"£","%a4":"¤","%A4":"¤","%a5":"¥","%A5":"¥","%a6":"¦","%A6":"¦","%a7":"§","%A7":"§","%a8":"¨","%A8":"¨","%a9":"©","%A9":"©","%aa":"ª","%Aa":"ª","%aA":"ª","%AA":"ª","%ab":"«","%Ab":"«","%aB":"«","%AB":"«","%ac":"¬","%Ac":"¬","%aC":"¬","%AC":"¬","%ad":"­","%Ad":"­","%aD":"­","%AD":"­","%ae":"®","%Ae":"®","%aE":"®","%AE":"®","%af":"¯","%Af":"¯","%aF":"¯","%AF":"¯","%b0":"°","%B0":"°","%b1":"±","%B1":"±","%b2":"²","%B2":"²","%b3":"³","%B3":"³","%b4":"´","%B4":"´","%b5":"µ","%B5":"µ","%b6":"¶","%B6":"¶","%b7":"·","%B7":"·","%b8":"¸","%B8":"¸","%b9":"¹","%B9":"¹","%ba":"º","%Ba":"º","%bA":"º","%BA":"º","%bb":"»","%Bb":"»","%bB":"»","%BB":"»","%bc":"¼","%Bc":"¼","%bC":"¼","%BC":"¼","%bd":"½","%Bd":"½","%bD":"½","%BD":"½","%be":"¾","%Be":"¾","%bE":"¾","%BE":"¾","%bf":"¿","%Bf":"¿","%bF":"¿","%BF":"¿","%c0":"À","%C0":"À","%c1":"Á","%C1":"Á","%c2":"Â","%C2":"Â","%c3":"Ã","%C3":"Ã","%c4":"Ä","%C4":"Ä","%c5":"Å","%C5":"Å","%c6":"Æ","%C6":"Æ","%c7":"Ç","%C7":"Ç","%c8":"È","%C8":"È","%c9":"É","%C9":"É","%ca":"Ê","%Ca":"Ê","%cA":"Ê","%CA":"Ê","%cb":"Ë","%Cb":"Ë","%cB":"Ë","%CB":"Ë","%cc":"Ì","%Cc":"Ì","%cC":"Ì","%CC":"Ì","%cd":"Í","%Cd":"Í","%cD":"Í","%CD":"Í","%ce":"Î","%Ce":"Î","%cE":"Î","%CE":"Î","%cf":"Ï","%Cf":"Ï","%cF":"Ï","%CF":"Ï","%d0":"Ð","%D0":"Ð","%d1":"Ñ","%D1":"Ñ","%d2":"Ò","%D2":"Ò","%d3":"Ó","%D3":"Ó","%d4":"Ô","%D4":"Ô","%d5":"Õ","%D5":"Õ","%d6":"Ö","%D6":"Ö","%d7":"×","%D7":"×","%d8":"Ø","%D8":"Ø","%d9":"Ù","%D9":"Ù","%da":"Ú","%Da":"Ú","%dA":"Ú","%DA":"Ú","%db":"Û","%Db":"Û","%dB":"Û","%DB":"Û","%dc":"Ü","%Dc":"Ü","%dC":"Ü","%DC":"Ü","%dd":"Ý","%Dd":"Ý","%dD":"Ý","%DD":"Ý","%de":"Þ","%De":"Þ","%dE":"Þ","%DE":"Þ","%df":"ß","%Df":"ß","%dF":"ß","%DF":"ß","%e0":"à","%E0":"à","%e1":"á","%E1":"á","%e2":"â","%E2":"â","%e3":"ã","%E3":"ã","%e4":"ä","%E4":"ä","%e5":"å","%E5":"å","%e6":"æ","%E6":"æ","%e7":"ç","%E7":"ç","%e8":"è","%E8":"è","%e9":"é","%E9":"é","%ea":"ê","%Ea":"ê","%eA":"ê","%EA":"ê","%eb":"ë","%Eb":"ë","%eB":"ë","%EB":"ë","%ec":"ì","%Ec":"ì","%eC":"ì","%EC":"ì","%ed":"í","%Ed":"í","%eD":"í","%ED":"í","%ee":"î","%Ee":"î","%eE":"î","%EE":"î","%ef":"ï","%Ef":"ï","%eF":"ï","%EF":"ï","%f0":"ð","%F0":"ð","%f1":"ñ","%F1":"ñ","%f2":"ò","%F2":"ò","%f3":"ó","%F3":"ó","%f4":"ô","%F4":"ô","%f5":"õ","%F5":"õ","%f6":"ö","%F6":"ö","%f7":"÷","%F7":"÷","%f8":"ø","%F8":"ø","%f9":"ù","%F9":"ù","%fa":"ú","%Fa":"ú","%fA":"ú","%FA":"ú","%fb":"û","%Fb":"û","%fB":"û","%FB":"û","%fc":"ü","%Fc":"ü","%fC":"ü","%FC":"ü","%fd":"ý","%Fd":"ý","%fD":"ý","%FD":"ý","%fe":"þ","%Fe":"þ","%fE":"þ","%FE":"þ","%ff":"ÿ","%Ff":"ÿ","%fF":"ÿ","%FF":"ÿ"};function encodedReplacer(e){return o[e]}const n=0;const i=1;const a=2;const c=3;function parseParams(e){const A=[];let t=n;let o="";let g=false;let E=false;let l=0;let u="";const Q=e.length;for(var C=0;C ({ name })), + } +} + +function linearStack(length) { + return Array.from({ length }, (_, index) => + pull(index + 1, index ? `branch-${index}` : 'canary', `branch-${index + 1}`) + ) +} + +function check(id, conclusion, status = 'completed', overrides = {}) { + return { + id, + name: 'thank you, next', + status, + conclusion, + html_url: `https://example.test/check/${id}`, + app: { slug: 'github-actions' }, + ...overrides, + } +} + +function coreMock() { + const outputs = new Map() + const failures = [] + const warnings = [] + const logs = [] + const summaries = [] + const summary = { + value: '', + addRaw(value) { + this.value += value + return this + }, + async write() { + summaries.push(this.value) + this.value = '' + }, + } + return { + outputs, + failures, + warnings, + logs, + summaries, + summary, + setOutput(name, value) { + outputs.set(name, String(value)) + }, + setFailed(message) { + failures.push(message) + }, + warning(message) { + warnings.push(message) + }, + info(message) { + logs.push(message) + }, + } +} + +function githubMock(pulls, checks, options) { + const calls = { get: 0, list: 0, checks: 0, refs: [] } + const route = () => {} + const github = { + rest: { + pulls: { + list: route, + async get({ pull_number }) { + calls.get++ + const error = options.getError?.(pull_number, calls.get) + if (error) throw error + const result = pulls.find((item) => item.number === pull_number) + if (!result) throw new Error(`Missing mock PR #${pull_number}`) + return { data: structuredClone(result) } + }, + }, + checks: { + async listForRef({ ref }) { + calls.checks++ + calls.refs.push(ref) + const value = + typeof checks === 'function' + ? checks(ref, calls.checks) + : checks[ref] + const candidates = ( + Array.isArray(value) ? value : value ? [value] : [] + ).map((item) => structuredClone(item)) + for (const candidate of candidates) { + if (!Object.hasOwn(candidate, 'head_sha')) candidate.head_sha = ref + if (!Object.hasOwn(candidate, 'pull_requests')) { + candidate.pull_requests = pulls + .filter((item) => item.head.sha === ref) + .map((item) => ({ + number: item.number, + head: { sha: item.head.sha }, + base: { ref: item.base.ref, sha: item.base.sha }, + })) + } + } + return { data: { check_runs: candidates } } + }, + }, + }, + async paginate(input, params) { + expect(input).toBe(route) + calls.list++ + let result = pulls.filter((item) => item.state === 'open') + if (params.base) + result = result.filter((item) => item.base.ref === params.base) + if (params.head) { + const [owner, ...parts] = params.head.split(':') + const branch = parts.join(':') + result = result.filter( + (item) => + item.head.ref === branch && + item.head.repo.full_name.startsWith(`${owner}/`) + ) + } + return structuredClone(result) + }, + } + return { github, calls } +} + +async function run({ + pulls = [], + current = 1, + checks = {}, + eventName = 'pull_request', + getError, +} = {}) { + jest.useFakeTimers({ now: new Date(0) }) + const core = coreMock() + const { github, calls } = githubMock(pulls, checks, { getError }) + const context = { + repo: { owner: 'vercel', repo: 'next.js' }, + eventName, + payload: { + repository: { full_name: 'vercel/next.js' }, + ...(eventName === 'pull_request' + ? { pull_request: { number: current } } + : {}), + }, + } + const previous = process.env.BYPASS_LABEL + process.env.BYPASS_LABEL = 'CI Bypass PR Stack Optimization' + let settled = false + try { + const result = gate({ core, github, context }).finally(() => { + settled = true + }) + for (let i = 0; i < 80 && !settled; i++) { + await jest.advanceTimersByTimeAsync(5 * 60 * 1000) + } + if (!settled) throw new Error('Gate did not finish within 80 fake polls') + await result + } finally { + jest.useRealTimers() + if (previous === undefined) delete process.env.BYPASS_LABEL + else process.env.BYPASS_LABEL = previous + } + return { core, calls } +} + +test('non-PR runs open immediately', async () => { + const { core } = await run({ eventName: 'push' }) + expect(core.outputs.get('skip')).toBe('false') + expect(core.failures).toEqual([]) + expect(core.warnings).toEqual([]) + expect(core.summaries.join('\n')).toMatch( + /PR: #n\/a.*|push runs immediately/s + ) +}) + +test('fork and bypass PRs open immediately', async () => { + const fork = await run({ + pulls: [pull(1, 'canary', 'fork-work', { repository: 'someone/fork' })], + }) + expect(fork.core.summaries.join('\n')).toContain( + 'fork PRs always run immediately' + ) + const bypass = await run({ + pulls: [ + pull(1, 'canary', 'work', { + labels: ['CI Bypass PR Stack Optimization'], + }), + ], + }) + expect(bypass.core.summaries.join('\n')).toContain('bypass label is present') +}) + +test('first three and the top/leaf PR open immediately', async () => { + const stack = linearStack(5) + for (const current of [1, 2, 3, 5]) { + const { core } = await run({ pulls: stack, current }) + expect(core.failures).toEqual([]) + } +}) + +test('older success releases even when nearer predecessor is pending', async () => { + const stack = linearStack(5) + const { core, calls } = await run({ + pulls: stack, + current: 4, + checks: { + 'head-3': check(3, null, 'in_progress'), + 'head-2': check(2, 'success'), + 'head-1': check(1, 'failure'), + }, + }) + expect(core.failures).toEqual([]) + expect(calls.refs).toEqual(['head-3', 'head-2', 'head-1', 'head-2']) + expect(core.summaries.join('\n')).toContain('PR #2 passed thank you, next') +}) + +test('a real current-head and current-base check association releases', async () => { + // Captured from #99095's passing required check; only branch-chain wiring + // uses synthetic PRs. This guards the live REST response shape. + const stack = linearStack(5) + stack[0].head.ref = realCheck.pull_requests[0].base.ref + stack[1].number = realCheck.pull_requests[0].number + stack[1].base.ref = realCheck.pull_requests[0].base.ref + stack[1].base.sha = realCheck.pull_requests[0].base.sha + stack[1].head.sha = realCheck.head_sha + const result = await run({ + pulls: stack, + current: 4, + checks: { [realCheck.head_sha]: realCheck }, + }) + expect(result.core.failures).toEqual([]) + expect(result.core.summaries.join('\n')).toContain('PR #99095 passed') +}) + +test('three terminal failures fail the gate without running expensive jobs', async () => { + const { core } = await run({ + pulls: linearStack(5), + current: 4, + checks: { + 'head-3': check(3, 'failure'), + 'head-2': check(2, 'cancelled'), + 'head-1': check(1, 'skipped'), + }, + }) + expect(core.failures).toHaveLength(1) + expect(core.failures[0]).toMatch(/All three predecessor PRs/) + expect(core.summaries.join('\n')).toMatch(/Result: \*\*failed\*\*/) +}) + +test('an old-base success cannot release the gate', async () => { + const stack = linearStack(5) + const staleSuccess = check(200, 'success', 'completed', { + pull_requests: [ + { + number: 3, + head: { sha: 'head-3' }, + base: { ref: 'branch-2', sha: 'old-base' }, + }, + ], + }) + const { core } = await run({ + pulls: stack, + current: 4, + checks: { + 'head-3': [staleSuccess, check(100, 'failure')], + 'head-2': check(2, 'cancelled'), + 'head-1': check(1, 'failure'), + }, + }) + expect(core.failures).toHaveLength(1) +}) + +test.each([ + { head_sha: 'old-head-sha' }, + { + pull_requests: [ + { + number: 99, + head: { sha: 'head-3' }, + base: { ref: 'branch-2', sha: 'base-branch-2' }, + }, + ], + }, + { pull_requests: [] }, + { app: { slug: 'untrusted-app' } }, +])('unrelated or stale check does not release (%j)', async (overrides) => { + const { core } = await run({ + pulls: linearStack(5), + current: 4, + checks: { 'head-3': check(3, 'success', 'completed', overrides) }, + }) + expect(core.failures).toEqual([]) + expect(core.summaries.join('\n')).toContain( + 'Five-hour waiting deadline reached' + ) +}) + +test('a later successful predecessor releases within a five-minute poll', async () => { + const calls = new Map() + const checks = (ref) => { + const count = calls.get(ref) ?? 0 + calls.set(ref, count + 1) + if (ref === 'head-3' && count > 0) return check(30, 'success') + if (ref === 'head-2') return check(20, 'failure') + return null + } + const result = await run({ pulls: linearStack(5), current: 4, checks }) + expect(result.core.failures).toEqual([]) + expect(result.core.summaries.join('\n')).toContain( + 'PR #3 passed thank you, next' + ) +}) + +test('a failed predecessor may succeed on a later rerun', async () => { + let reads = 0 + const result = await run({ + pulls: linearStack(5), + current: 4, + checks: (ref) => { + if (ref === 'head-1') + return ++reads > 1 ? check(100, 'success') : check(1, 'failure') + if (ref === 'head-2') return check(2, 'failure') + return null + }, + }) + expect(result.core.summaries.join('\n')).toContain( + 'PR #1 passed thank you, next' + ) +}) + +test('steady pending polls save three REST requests without losing topology refresh', async () => { + const snapshots = [] + const stack = linearStack(5) + const result = await run({ + pulls: stack, + current: 4, + checks: (ref, nth) => { + if (nth % 3 === 0) snapshots.push(nth) + // After two full waiting polls, release on the third poll. + return nth > 6 && ref === 'head-2' ? check(2, 'success') : null + }, + }) + expect(snapshots).toEqual([3, 6, 9]) + // Three initial/fresh topology polls: 1 PR GET + 4 lists + 3 checks each. + // A decisive poll intentionally adds extra validation calls. + expect(result.calls.get).toBe(5) + expect(result.calls.list).toBe(12) + expect(result.calls.checks).toBe(10) + expect(result.core.failures).toEqual([]) +}) + +test('closing a successor makes a waiting middle PR a leaf', async () => { + const stack = linearStack(5) + const result = await run({ + pulls: stack, + current: 4, + checks: (ref, nth) => { + if (nth === 3) stack[4].state = 'closed' + return null + }, + }) + expect(result.core.summaries.join('\n')).toContain( + 'no open PR is based on this head branch' + ) +}) + +test('closing or changing a predecessor rebuilds the chain on the next poll', async () => { + const stack = linearStack(5) + const result = await run({ + pulls: stack, + current: 4, + checks: (ref, nth) => { + if (nth === 3) stack[1].state = 'closed' + return null + }, + }) + expect(result.core.summaries.join('\n')).toContain( + 'only 1 open predecessor PR(s) are reachable' + ) +}) + +test('a retargeted current PR opens on the next poll', async () => { + const stack = linearStack(5) + const result = await run({ + pulls: stack, + current: 4, + checks: (ref, nth) => { + if (nth === 3) stack[3].base.ref = 'canary' + return null + }, + }) + expect(result.core.summaries.join('\n')).toContain( + 'only 0 open predecessor PR(s) are reachable' + ) +}) + +test('an outdated head/base cannot pass decisive revalidation', async () => { + const stack = linearStack(5) + const result = await run({ + pulls: stack, + current: 4, + checks: (ref, nth) => { + if (nth === 2) stack[1].base.sha = 'changed-base' + return ref === 'head-2' + ? check(2, 'success', 'completed', { + pull_requests: [ + { + number: 2, + head: { sha: 'head-2' }, + base: { ref: 'branch-1', sha: 'base-branch-1' }, + }, + ], + }) + : null + }, + }) + expect(result.core.summaries.join('\n')).toContain( + 'Five-hour waiting deadline reached' + ) + expect(result.core.summaries.join('\n')).not.toContain('PR #2 passed') +}) + +test('a predecessor head update invalidates an apparent success', async () => { + const stack = linearStack(5) + const result = await run({ + pulls: stack, + current: 4, + checks: (ref, nth) => { + if (nth === 3) stack[1].head.sha = 'rebased-head' + return ref === 'head-2' ? check(2, 'success') : null + }, + }) + expect(result.core.logs.join('\n')).toContain('changed during verification') + expect(result.calls.refs).toContain('rebased-head') + expect(result.core.summaries.join('\n')).toContain( + 'Five-hour waiting deadline reached' + ) +}) + +test('a failure rerun during decisive revalidation prevents false failure', async () => { + const result = await run({ + pulls: linearStack(5), + current: 4, + checks: (ref, nth) => + ref === 'head-3' && nth >= 4 + ? check(30, 'success') + : check(nth, 'failure'), + }) + expect(result.core.failures).toEqual([]) + expect(result.core.logs.join('\n')).toContain('changed during verification') + expect(result.core.summaries.join('\n')).toContain('PR #3 passed') +}) + +test('five-hour unresolved wait fails open and transient API errors retry', async () => { + const pending = await run({ pulls: linearStack(5), current: 4 }) + expect(pending.core.summaries.join('\n')).toContain( + 'Five-hour waiting deadline reached' + ) + let count = 0 + const temporary = Object.assign(new Error('temporarily unavailable'), { + status: 500, + }) + const recovered = await run({ + pulls: linearStack(5), + current: 4, + checks: { 'head-2': check(2, 'success') }, + getError: () => (count++ === 0 ? temporary : null), + }) + expect(recovered.core.warnings.join('\n')).toContain( + 'Transient GitHub API error' + ) + expect(recovered.core.summaries.join('\n')).toContain('PR #2 passed') +}) + +test('persistent 429 releases only at the five-hour deadline', async () => { + const error = Object.assign(new Error('rate limited'), { status: 429 }) + const result = await run({ + pulls: linearStack(5), + current: 4, + getError: () => error, + }) + expect(result.core.failures).toEqual([]) + expect(result.core.summaries.join('\n')).toContain( + 'Five-hour transient API error deadline reached' + ) +}) + +test('403 and ambiguous chain fail open', async () => { + const unauthorized = await run({ + pulls: linearStack(5), + current: 4, + getError: () => Object.assign(new Error('not authorized'), { status: 403 }), + }) + expect(unauthorized.core.warnings.join('\n')).toContain('failing open') + const stack = linearStack(5) + stack.push(pull(30, 'other', 'branch-3')) + const ambiguous = await run({ pulls: stack, current: 4 }) + expect(ambiguous.core.summaries.join('\n')).toContain('multiple open PRs') +}) + +test('workflow keeps expensive work gated, forks isolated and action pinned to the CI SHA', () => { + const workflow = fs.readFileSync( + path.join(root, '.github/workflows/pr_stack_optimizer.yml'), + 'utf8' + ) + const build = fs.readFileSync( + path.join(root, '.github/workflows/build_and_test.yml'), + 'utf8' + ) + const action = fs.readFileSync(path.join(__dirname, 'action.yml'), 'utf8') + expect(workflow).toContain('runs-on: ubuntu-latest') + expect(workflow).toContain('timeout-minutes: 360') + expect(workflow).toContain('contents: read') + expect(workflow).not.toContain('secrets:') + expect(workflow).toContain('head.repo.full_name != github.repository') + expect( + workflow.match(/head.repo.full_name == github.repository/g) + ).toHaveLength(2) + expect(workflow).toContain('ref: ${{ github.sha }}') + expect(workflow).toContain('persist-credentials: false') + expect(workflow).toContain('.github/actions/pr-stack-ci-gate/action.yml') + expect(workflow).toContain('.github/actions/pr-stack-ci-gate/dist/index.js') + expect(workflow).toContain('uses: ./.github/actions/pr-stack-ci-gate') + expect(action).toContain("using: 'node24'") + expect(action).toContain("main: 'dist/index.js'") + expect(build).toMatch( + /optimize-ci:\n permissions:\n checks: read\n contents: read\n pull-requests: read/ + ) + for (const job of ['changes', 'build-next', 'validate-docs-links']) { + const block = build.match( + new RegExp( + `^ ${job}:\\n([\\s\\S]*?)(?=^ [a-zA-Z0-9_-]+:|(?![\\s\\S]))`, + 'm' + ) + ) + expect(block).not.toBeNull() + expect(block[0]).toContain("needs: ['optimize-ci']") + } + const lint = build.match( + /^ lint:\n([\s\S]*?)(?=^ validate-docs-links:)/m + )?.[1] + expect(lint).toBeDefined() + const commands = [ + 'pnpm lint-no-typescript', + 'pnpm check-examples', + 'pnpm validate-externals-doc', + 'pnpm generate-browser-variant-aliases', + 'pnpm --dir .github/actions/pr-stack-ci-gate install', + 'pnpm --dir .github/actions/pr-stack-ci-gate types', + 'pnpm --dir .github/actions/pr-stack-ci-gate build', + 'pnpm --dir .github/actions/pr-stack-ci-gate test', + 'git diff --exit-code', + ] + let previous = -1 + for (const command of commands) { + const position = lint.indexOf(` ${command}`) + expect(position).toBeGreaterThan(previous) + previous = position + } + expect(build).not.toContain('node --test .github/actions/pr-stack-ci-gate') + expect(build).toContain("needs: ['optimize-ci', 'changes', 'build-next'") +}) diff --git a/.github/actions/pr-stack-ci-gate/jest-typescript-transform.cjs b/.github/actions/pr-stack-ci-gate/jest-typescript-transform.cjs new file mode 100644 index 000000000000..81504ed6272c --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/jest-typescript-transform.cjs @@ -0,0 +1,18 @@ +const ts = require('typescript') + +// Jest runs the TypeScript action source without emitting temporary JS into +// the repository checkout (where repo-wide lint would discover it). +module.exports = { + process(source, filename) { + return { + code: ts.transpileModule(source, { + fileName: filename, + compilerOptions: { + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2024, + esModuleInterop: true, + }, + }).outputText, + } + }, +} diff --git a/.github/actions/pr-stack-ci-gate/jest.config.cjs b/.github/actions/pr-stack-ci-gate/jest.config.cjs new file mode 100644 index 000000000000..03c90dea0199 --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/jest.config.cjs @@ -0,0 +1,7 @@ +module.exports = { + rootDir: __dirname, + testEnvironment: 'node', + testMatch: ['/**/*.test.js'], + transform: { '^.+\\.ts$': '/jest-typescript-transform.cjs' }, + clearMocks: true, +} diff --git a/.github/actions/pr-stack-ci-gate/package.json b/.github/actions/pr-stack-ci-gate/package.json new file mode 100644 index 000000000000..3f04737198ba --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/package.json @@ -0,0 +1,17 @@ +{ + "private": true, + "description": "Read-only CI gate for branch-based pull request stacks", + "scripts": { + "build": "ncc build src/index.ts -m -o dist --license licenses.txt", + "types": "tsc --noEmit", + "test": "../../../node_modules/.bin/jest --config jest.config.cjs --runInBand" + }, + "dependencies": { + "@actions/core": "1.11.1", + "@actions/github": "6.0.0" + }, + "devDependencies": { + "@vercel/ncc": "0.38.4", + "typescript": "6.0.2" + } +} diff --git a/.github/actions/pr-stack-ci-gate/src/gate.ts b/.github/actions/pr-stack-ci-gate/src/gate.ts new file mode 100644 index 000000000000..74e6d5c414e1 --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/src/gate.ts @@ -0,0 +1,472 @@ +import type * as coreModule from '@actions/core' +import type { context as githubContext, getOctokit } from '@actions/github' + +type Github = ReturnType +type Context = typeof githubContext +type Core = Pick< + typeof coreModule, + 'setOutput' | 'setFailed' | 'warning' | 'info' | 'summary' +> +type Pull = { + number: number + state: string + base: { ref: string; sha: string } + head: { ref: string; sha: string; repo: { full_name: string } | null } + labels: { name: string }[] +} +type Check = { + id: number + name: string + status: string + conclusion: string | null + html_url: string | null + head_sha: string + app: { slug: string } | null + pull_requests?: { + number: number + head?: { sha?: string } + base?: { ref?: string; sha?: string } + }[] +} +type Role = + | 'fork' + | 'bypass' + | 'top' + | 'ambiguous' + | 'first-three' + | 'middle' + | 'non-pr' + | 'error' +type CandidateState = 'success' | 'waiting' | 'unsuccessful' +type Candidate = { pull: Pull; check: Check | null; state: CandidateState } +type Topology = { + current: Pull + role: Role + reason?: string + predecessors?: Pull[] +} +type Snapshot = { + current?: Pull | null + role: Role + reason?: string + candidates?: Candidate[] +} + +const POLL_INTERVAL_MS = 5 * 60 * 1000 +const WAIT_DEADLINE_MS = 5 * 60 * 60 * 1000 +const REQUIRED_CHECK = 'thank you, next' + +function errorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error) +} + +function errorStatus(error: unknown): number | undefined { + if (!error || typeof error !== 'object') return undefined + const data = error as { status?: number; response?: { status?: number } } + return data.status ?? data.response?.status +} + +function isTransientApiError(error: unknown): boolean { + const status = errorStatus(error) + return ( + status === 429 || (status !== undefined && status >= 500 && status < 600) + ) +} + +function escapeCell(value: unknown): string { + return String(value ?? '') + .replaceAll('|', '\\|') + .replaceAll('\n', ' ') +} + +function checkState(check: Check | null): CandidateState { + if (!check || check.status !== 'completed') return 'waiting' + return check.conclusion === 'success' ? 'success' : 'unsuccessful' +} + +// Any nearby green CI can release this PR despite a flaky predecessor, but +// all three must have finished unsuccessfully before the gate can fail. +function gateDecision(candidates: Candidate[]): 'open' | 'fail' | 'wait' { + if (candidates.some((candidate) => candidate.state === 'success')) { + return 'open' + } + if ( + candidates.length === 3 && + candidates.every((candidate) => candidate.state === 'unsuccessful') + ) { + return 'fail' + } + return 'wait' +} + +function sameRepository(pull: Pull, repository: string): boolean { + return pull.head?.repo?.full_name === repository +} + +function sameRevision(before: Pull, after: Pull): boolean { + return ( + after.state === 'open' && + before.head.sha === after.head.sha && + before.base.ref === after.base.ref && + before.base.sha === after.base.sha + ) +} + +export async function runGate({ + github, + context, + core, +}: { + github: Github + context: Context + core: Core +}): Promise { + // A failed gate stops expensive dependents via its job conclusion; all + // successful gate outcomes retain the reusable workflow's skip=false API. + core.setOutput('skip', 'false') + + const startedAt = Date.now() + let lastFingerprint = '' + const { owner, repo } = context.repo + const repository = context.payload.repository?.full_name ?? '' + + async function listOpenPulls( + parameters: Record + ): Promise { + const pulls = await github.paginate(github.rest.pulls.list, { + owner, + repo, + state: 'open', + per_page: 100, + ...parameters, + }) + return pulls as unknown as Pull[] + } + + async function getPull(number: number): Promise { + const { data } = await github.rest.pulls.get({ + owner, + repo, + pull_number: number, + }) + return data as unknown as Pull + } + + async function findPredecessor(pull: Pull): Promise<{ + ambiguous: boolean + pull?: Pull + }> { + const matches = ( + await listOpenPulls({ head: `${owner}:${pull.base.ref}` }) + ).filter( + (candidate) => + candidate.number !== pull.number && + candidate.head.ref === pull.base.ref && + sameRepository(candidate, repository) + ) + return { ambiguous: matches.length > 1, pull: matches[0] } + } + + async function findSuccessors(pull: Pull): Promise { + return (await listOpenPulls({ base: pull.head.ref })).filter( + (candidate) => + candidate.number !== pull.number && + candidate.base.ref === pull.head.ref && + sameRepository(candidate, repository) + ) + } + + // Base/head branch links also describe stacks not registered in GitHub's + // stack API. A leaf runs immediately so the top PR is never held by polling. + async function discoverTopology(): Promise { + const prNumber = context.payload.pull_request?.number + if (!prNumber) throw new Error('Missing pull request number') + const current = await getPull(prNumber) + + if (!sameRepository(current, repository)) { + return { + current, + role: 'fork', + reason: 'fork PRs always run immediately', + } + } + if ( + current.labels.some((label) => label.name === process.env.BYPASS_LABEL) + ) { + return { current, role: 'bypass', reason: 'bypass label is present' } + } + + if ((await findSuccessors(current)).length === 0) { + return { + current, + role: 'top', + reason: 'no open PR is based on this head branch', + } + } + + const predecessors: Pull[] = [] + const seen = new Set([current.number]) + let cursor = current + while (predecessors.length < 3) { + const result = await findPredecessor(cursor) + if (result.ambiguous) { + return { + current, + role: 'ambiguous', + reason: `multiple open PRs have head branch ${cursor.base.ref}`, + } + } + if (!result.pull) break + if (seen.has(result.pull.number)) { + return { + current, + role: 'ambiguous', + reason: 'cycle detected in PR base branches', + } + } + seen.add(result.pull.number) + predecessors.push(result.pull) + cursor = result.pull + } + if (predecessors.length < 3) { + return { + current, + role: 'first-three', + reason: `only ${predecessors.length} open predecessor PR(s) are reachable`, + } + } + return { current, role: 'middle', predecessors } + } + + // The required check may attach to the PR head rather than its test-merge + // SHA. Match the current PR and base too, rejecting a green check from a + // previous rebase before it can release downstream CI. + async function latestRequiredCheck(pull: Pull): Promise { + const { data } = await github.rest.checks.listForRef({ + owner, + repo, + ref: pull.head.sha, + check_name: REQUIRED_CHECK, + filter: 'latest', + per_page: 100, + }) + const check = (data.check_runs as unknown as Check[]) + .filter( + (candidate) => + candidate.name === REQUIRED_CHECK && + candidate.app?.slug === 'github-actions' && + candidate.head_sha === pull.head.sha && + candidate.pull_requests?.some( + (associated) => + associated.number === pull.number && + associated.head?.sha === pull.head.sha && + associated.base?.ref === pull.base.ref && + associated.base?.sha === pull.base.sha + ) + ) + .sort((a, b) => b.id - a.id)[0] + return { pull, check: check ?? null, state: checkState(check ?? null) } + } + + // pulls.list already includes head/base metadata. Fetching each PR again + // during *every* pending poll wastes three REST requests (11 -> 8). Before + // any decisive open/fail, validate live PR revisions and re-read their checks + // so a rebase, retarget, closure or rerun cannot reuse a stale result. + async function stillDecisive( + topology: Topology, + candidates: Candidate[], + decision: 'open' | 'fail' + ): Promise { + const freshCurrent = await getPull(topology.current.number) + if (!sameRevision(topology.current, freshCurrent)) return false + + const relevant = + decision === 'open' + ? [candidates.find((candidate) => candidate.state === 'success')!] + : candidates + for (const candidate of relevant) { + const fresh = await getPull(candidate.pull.number) + if (!sameRevision(candidate.pull, fresh)) return false + const latest = await latestRequiredCheck(fresh) + if ( + latest.state !== candidate.state || + latest.check?.id !== candidate.check?.id + ) { + return false + } + } + return true + } + + function fingerprint(snapshot: Snapshot): string { + return JSON.stringify({ + role: snapshot.role, + reason: snapshot.reason, + candidates: snapshot.candidates?.map((candidate) => ({ + number: candidate.pull.number, + head: candidate.pull.head.sha, + base: candidate.pull.base.sha, + status: candidate.check?.status, + conclusion: candidate.check?.conclusion, + })), + }) + } + + async function writeSummary( + snapshot: Snapshot, + outcome: 'open' | 'failed', + reason: string + ): Promise { + try { + const current = snapshot.current + const elapsedMinutes = Math.floor((Date.now() - startedAt) / 60000) + const lines = [ + '# PR Stack CI Gate', + '', + `- PR: #${current?.number ?? context.payload.pull_request?.number ?? 'n/a'}`, + `- Branches: \`${escapeCell(current?.base?.ref)}\` ← \`${escapeCell(current?.head?.ref)}\``, + `- Role: **${escapeCell(snapshot.role)}**`, + `- Result: **${escapeCell(outcome)}**`, + `- Reason: ${escapeCell(reason)}`, + `- Elapsed: ${elapsedMinutes} minute(s)`, + ] + if (snapshot.candidates?.length) { + lines.push( + '', + '| PR | Base ← Head | Head SHA | Base SHA | Check | State |', + '|---:|---|---|---|---|---|' + ) + for (const candidate of snapshot.candidates) { + const checkText = candidate.check + ? `[${candidate.check.status}/${candidate.check.conclusion ?? ''}](${candidate.check.html_url})` + : 'not reported' + lines.push( + `| #${candidate.pull.number} | \`${escapeCell(candidate.pull.base.ref)}\` ← \`${escapeCell(candidate.pull.head.ref)}\` | \`${escapeCell(candidate.pull.head.sha?.slice(0, 12))}\` | \`${escapeCell(candidate.pull.base.sha?.slice(0, 12))}\` | ${checkText} | ${candidate.state} |` + ) + } + } + await core.summary.addRaw(`${lines.join('\n')}\n`).write() + } catch (error) { + core.warning( + `Could not write PR Stack CI Gate summary: ${errorMessage(error)}` + ) + } + } + + try { + if (context.eventName !== 'pull_request') { + const reason = `${context.eventName} runs immediately` + await writeSummary({ role: 'non-pr', reason }, 'open', reason) + return + } + + while (true) { + let topology: Topology + const candidates: Candidate[] = [] + try { + topology = await discoverTopology() + if (topology.role === 'middle') { + // Always check all three, even if the nearest is pending: a more + // distant PR can have succeeded. A failed PR may also pass on rerun. + for (const predecessor of topology.predecessors!) { + candidates.push(await latestRequiredCheck(predecessor)) + } + const decision = gateDecision(candidates) + if ( + decision !== 'wait' && + !(await stillDecisive(topology, candidates, decision)) + ) { + core.info('Stack/check state changed during verification; retrying') + await new Promise((resolve) => + setTimeout(resolve, POLL_INTERVAL_MS) + ) + continue + } + } + } catch (error) { + // A brief GitHub outage should not start all waiting CI at once; + // retry transient errors until the same five-hour waiting deadline. + if (!isTransientApiError(error)) throw error + if (Date.now() - startedAt >= WAIT_DEADLINE_MS) { + await writeSummary( + { + current: context.payload.pull_request as unknown as Pull, + role: 'error', + reason: errorMessage(error), + }, + 'open', + 'Five-hour transient API error deadline reached; starting full CI' + ) + return + } + core.warning( + `Transient GitHub API error (${errorStatus(error)}); retrying in five minutes: ${errorMessage(error)}` + ) + await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)) + continue + } + + if (topology.role !== 'middle') { + await writeSummary(topology, 'open', topology.reason ?? '') + return + } + const snapshot: Snapshot = { ...topology, candidates } + const decision = gateDecision(candidates) + const currentFingerprint = fingerprint(snapshot) + if (currentFingerprint !== lastFingerprint) { + core.info( + `PR #${topology.current.number}: ${candidates + .map((candidate) => `#${candidate.pull.number}=${candidate.state}`) + .join(', ')}` + ) + lastFingerprint = currentFingerprint + } + + if (decision === 'open') { + const successful = candidates.find( + (candidate) => candidate.state === 'success' + )! + await writeSummary( + snapshot, + 'open', + `PR #${successful.pull.number} passed ${REQUIRED_CHECK}` + ) + return + } + if (decision === 'fail') { + const reason = `All three predecessor PRs completed ${REQUIRED_CHECK} without success. Rerun this workflow after a predecessor passes, or apply the ${process.env.BYPASS_LABEL} label.` + await writeSummary(snapshot, 'failed', reason) + core.setFailed(reason) + return + } + if (Date.now() - startedAt >= WAIT_DEADLINE_MS) { + await writeSummary( + snapshot, + 'open', + 'Five-hour waiting deadline reached; failing open and starting full CI' + ) + return + } + const nextPoll = new Date(Date.now() + POLL_INTERVAL_MS) + core.info( + `No predecessor has passed yet; polling again at ${nextPoll.toISOString()}` + ) + await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)) + } + } catch (error) { + // Unexpected errors must run the required full-CI graph, not turn a + // missing classification result into a mergeable skipped-check success. + core.warning( + `PR stack classification failed; failing open and starting full CI: ${String(error)}` + ) + await writeSummary( + { + current: context.payload.pull_request as unknown as Pull | undefined, + role: 'error', + reason: errorMessage(error), + }, + 'open', + `Classification/API error; failing open: ${errorMessage(error)}` + ) + } +} diff --git a/.github/actions/pr-stack-ci-gate/src/index.ts b/.github/actions/pr-stack-ci-gate/src/index.ts new file mode 100644 index 000000000000..afc51e26e2b3 --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/src/index.ts @@ -0,0 +1,20 @@ +import * as core from '@actions/core' +import { context, getOctokit } from '@actions/github' +import { runGate } from './gate' + +async function main(): Promise { + // Never accept a separately supplied token: the workflow's read-only job + // token is the only credential this action should see. + const token = process.env.GITHUB_TOKEN + if (!token) throw new Error('GITHUB_TOKEN is unavailable') + await runGate({ github: getOctokit(token), context, core }) +} + +main().catch((error: unknown) => { + // An unavailable token or unexpected entry-point error cannot make CI green: + // open the gate so the normal, required full-CI graph still runs. + core.warning( + `PR stack gate could not start; running full CI: ${String(error)}` + ) + core.setOutput('skip', 'false') +}) diff --git a/.github/actions/pr-stack-ci-gate/tsconfig.json b/.github/actions/pr-stack-ci-gate/tsconfig.json new file mode 100644 index 000000000000..36e875a83255 --- /dev/null +++ b/.github/actions/pr-stack-ci-gate/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2024", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "rootDir": "./src", + "strict": true, + "skipLibCheck": false, + "esModuleInterop": true + }, + "include": ["src/**/*.ts"] +} diff --git a/.github/pnpm-lock.yaml b/.github/pnpm-lock.yaml index f67fb7ed4a20..454e874a9797 100644 --- a/.github/pnpm-lock.yaml +++ b/.github/pnpm-lock.yaml @@ -75,6 +75,22 @@ importers: specifier: 0.38.4 version: 0.38.4 + actions/pr-stack-ci-gate: + dependencies: + '@actions/core': + specifier: 1.11.1 + version: 1.11.1 + '@actions/github': + specifier: 6.0.0 + version: 6.0.0 + devDependencies: + '@vercel/ncc': + specifier: 0.38.4 + version: 0.38.4 + typescript: + specifier: 6.0.2 + version: 6.0.2 + actions/validate-docs-links: dependencies: '@actions/core': diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 4c481f42dfe1..19607dcb49d3 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -22,11 +22,15 @@ env: jobs: optimize-ci: + permissions: + checks: read + contents: read + pull-requests: read uses: ./.github/workflows/pr_stack_optimizer.yml - secrets: inherit changes: name: Determine changes + needs: ['optimize-ci'] runs-on: ubuntu-latest permissions: contents: read @@ -157,6 +161,7 @@ jobs: build-next: name: build-next + needs: ['optimize-ci'] permissions: contents: read id-token: write @@ -251,11 +256,16 @@ jobs: # `*.browser.{ts,tsx}` sibling was added/removed without running # `pnpm generate-browser-variant-aliases` and committing the result. pnpm generate-browser-variant-aliases + pnpm --dir .github/actions/pr-stack-ci-gate install --frozen-lockfile --ignore-scripts + pnpm --dir .github/actions/pr-stack-ci-gate types + pnpm --dir .github/actions/pr-stack-ci-gate build + pnpm --dir .github/actions/pr-stack-ci-gate test git diff --exit-code stepName: 'lint' secrets: inherit validate-docs-links: + needs: ['optimize-ci'] runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/pr_stack_optimizer.yml b/.github/workflows/pr_stack_optimizer.yml index 3f0b757c10bf..2efbbc725599 100644 --- a/.github/workflows/pr_stack_optimizer.yml +++ b/.github/workflows/pr_stack_optimizer.yml @@ -1,84 +1,68 @@ -# Avoid running the full CI on mid-stack PRs: https://graphite.dev/docs/stacking-and-ci +# Avoid running the full CI concurrently on every PR in a branch-based stack. # -# This supports both Graphite stacks and GitHub's native stacked PRs. For either kind of stack, we -# only run the full CI on the very top and very bottom of the stack. -# -# We still run some high-signal low-cost jobs (e.g. lint, unit tests) on mid-stack PRs, just not -# anything slow (e.g. integration tests). -# -# Because we don't use Graphite's CI batching, full CI will still run on every PR individually -# before it merges. The goal is just to avoid wasting CI capacity when frequently rebasing large -# stacks. -# -# This can by bypassed by labeling a PR with 'CI Bypass PR Stack Optimization' (or the legacy -# 'CI Bypass Graphite Optimization' label), and manually re-running CI. +# A stack is inferred from ordinary PR branch relationships (the next PR's base +# branch is the previous PR's head branch). The first three PRs and every top +# PR run immediately. A middle PR waits until any of its previous three PRs has +# passed `thank you, next`, while a five-hour deadline fails open. -name: PR Stack Optimizer +name: PR Stack CI Gate on: workflow_call: outputs: skip: - description: "'true' if expensive CI checks should be skipped, 'false' otherwise." + description: 'Kept for compatibility; expensive CI is never skipped after the gate opens.' value: ${{ jobs.optimize-ci.outputs.skip }} - secrets: - GRAPHITE_CI_OPTIMIZER_TOKEN: - description: 'The Graphite CI optimization secret' - # secrets are not available in forks, check-skip will just fail-open with a warning - required: false + +permissions: {} + env: - # FYI, if you add this label, you must *push* to the repository again to trigger a new event. Just - # re-running in the GitHub actions UI won't work, as it will re-use the old event with the old - # labels. - HAS_BYPASS_LABEL: |- - ${{ - github.event_name == 'pull_request' && - ( - contains(github.event.pull_request.labels.*.name, 'CI Bypass PR Stack Optimization') || - contains(github.event.pull_request.labels.*.name, 'CI Bypass Graphite Optimization') - ) - }} - # GitHub's native stacked PRs expose the PR's position within the stack. We skip expensive CI on - # mid-stack PRs. Fail open if we don't have `stack.size`. - IS_MID_STACK_NATIVE: |- - ${{ - github.event_name == 'pull_request' && - github.event.pull_request.stack.size != '' && - github.event.pull_request.stack.position != 1 && - github.event.pull_request.stack.position != github.event.pull_request.stack.size - }} + BYPASS_LABEL: CI Bypass PR Stack Optimization + jobs: optimize-ci: - name: PR Stack Optimizer + name: PR Stack CI Gate runs-on: ubuntu-latest + timeout-minutes: 360 + # The build-and-test caller grants these same read scopes; reusable + # workflows cannot increase the caller's GITHUB_TOKEN permissions. + permissions: + checks: read + contents: read + pull-requests: read outputs: - # `== 'true'` comparison: If `step.check-skip` fails (`continue-on-error`), that output will - # be an empty string, which sets our `skip` output to `'false'`. - # - # We skip if either Graphite's optimizer says to skip, or the PR is a mid-stack PR in a - # GitHub native stack. The bypass label overrides both. - skip: ${{ env.HAS_BYPASS_LABEL == 'false' && steps.check-skip.outputs.skip == 'true' }} - # Note: IS_MID_STACK_NATIVE is disabled for now because it doesn't - # understand if we're not on the bottom of the stack, but all the PRs - # below us are merged. - # - # || env.IS_MID_STACK_NATIVE == 'true' + skip: ${{ steps.gate.outputs.skip || steps.fork.outputs.skip }} steps: - - name: Optimize CI - id: check-skip - # Graphite's action is designed to fail open, but still sometimes fails if GH's infra flakes - continue-on-error: true - uses: withgraphite/graphite-ci-action@402a89bc8aa6db18ae1f9c61953d001d5f9d828f # main - with: - graphite_token: ${{ secrets.GRAPHITE_CI_OPTIMIZER_TOKEN }} - - name: Debug Output + # Do not check out or execute code supplied by a fork. It cannot be part + # of a same-repository branch stack, so let its full CI run immediately. + - name: Run full CI for fork PRs + id: fork + if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }} run: | - echo 'github.event_name: ${{ github.event_name }}' - echo "secrets.GRAPHITE_CI_OPTIMIZER_TOKEN != '': ${GRAPHITE_CI_OPTIMIZER_TOKEN_NON_EMPTY}" - echo 'env.HAS_BYPASS_LABEL: ${{ env.HAS_BYPASS_LABEL }}' - echo 'env.IS_MID_STACK_NATIVE: ${{ env.IS_MID_STACK_NATIVE }}' - echo 'github.event.pull_request.stack.position: ${{ github.event.pull_request.stack.position }}' - echo 'github.event.pull_request.stack.size: ${{ github.event.pull_request.stack.size }}' - echo 'steps.check-skip.outputs.skip: ${{ steps.check-skip.outputs.skip }}' + set -euo pipefail + echo 'skip=false' >> "$GITHUB_OUTPUT" + { + echo '# PR Stack CI Gate' + echo '' + echo '- Role: **fork**' + echo '- Result: **open**' + echo '- Reason: fork PRs always run immediately' + } >> "$GITHUB_STEP_SUMMARY" + + # Fetch the compiled action from the exact commit this CI run tests. + - name: Check out stack gate from this CI commit + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ github.sha }} + sparse-checkout: | + .github/actions/pr-stack-ci-gate/action.yml + .github/actions/pr-stack-ci-gate/dist/index.js + sparse-checkout-cone-mode: false + persist-credentials: false + - name: Wait for previous stack CI + id: gate + if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }} + uses: ./.github/actions/pr-stack-ci-gate env: - GRAPHITE_CI_OPTIMIZER_TOKEN_NON_EMPTY: ${{ secrets.GRAPHITE_CI_OPTIMIZER_TOKEN != '' }} + GITHUB_TOKEN: ${{ github.token }} From fea76de6881692f4f157070d1261f373f09a84ee Mon Sep 17 00:00:00 2001 From: Luke Sandberg Date: Thu, 24 Sep 2026 22:53:04 -0700 Subject: [PATCH 13/13] turbo-tasks-gc: tear down reverse edges when collecting a task (#99145) When deleting a task, remove the reverse dependeny edges also. Generally this should be empty, but there can be tasks if those tasks are owned by a root that is pinned but not active. So in the rare case that such tasks are 'reactivated' this also dirties the task. This closes a hole where a task with a cell_dependency can reference a deleted task. Then new test demonstrates the bug, without the fix it will panic when collecting the second task --- .../turbo-tasks-backend/src/backend/gc.rs | 44 ++++- .../turbo-tasks-backend/src/backend/mod.rs | 4 +- .../backend/operation/aggregation_update.rs | 22 ++- .../backend/operation/cleanup_old_edges.rs | 161 ++++++++++++------ .../src/backend/operation/invalidate.rs | 2 +- .../src/backend/operation/mod.rs | 12 +- .../tests/gc_cross_session.rs | 136 ++++++++++++++- turbopack/crates/turbo-tasks/src/manager.rs | 27 +++ .../turbo-tasks/src/task_dirty_cause.rs | 4 + 9 files changed, 338 insertions(+), 74 deletions(-) diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/gc.rs b/turbopack/crates/turbo-tasks-backend/src/backend/gc.rs index dc4bb3bc1e0c..9911036ae9ca 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/gc.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/gc.rs @@ -31,8 +31,8 @@ use crate::{ backend::{ AnyOperation, TurboTasksBackend, operation::{ - AggregationUpdateQueue, CleanupOldEdgesOperation, ExecuteContext, ExecuteContextImpl, - TaskGuard, capture_all_outgoing_edges, + AggregationUpdateJob, AggregationUpdateQueue, CleanupOldEdgesOperation, ExecuteContext, + ExecuteContextImpl, TaskGuard, capture_all_edges, }, snapshot_coordinator::SnapshotPhase, storage::{SpecificTaskDataCategory, TaskDataCategory}, @@ -129,6 +129,9 @@ pub struct GcPassResult { deleted_roots: Vec, /// Aggregation rebalance requests that were deferred from the main GC loop. deferred_balance_edges: FxHashSet<(TaskId, TaskId)>, + /// Dependents of collected tasks whose forward edge was scrubbed, deferred from the main GC + /// loop. Dirtying propagates through the aggregation graph, so it must not race the cascade. + deferred_dirty_dependents: FxHashSet, /// The gc loop was interrupted by competing work. pub interrupted: bool, } @@ -176,6 +179,15 @@ impl GcPassResult { } self.deferred_balance_edges .extend(other.deferred_balance_edges); + // merge into the larger set and keep that one + if other.deferred_dirty_dependents.len() > self.deferred_dirty_dependents.len() { + std::mem::swap( + &mut self.deferred_dirty_dependents, + &mut other.deferred_dirty_dependents, + ); + } + self.deferred_dirty_dependents + .extend(other.deferred_dirty_dependents); self.interrupted |= other.interrupted; self } @@ -251,7 +263,7 @@ impl TurboTasksBackend { }; let collector = |child_id| spawner.spawn(GcJob::Collect(child_id)); let mut ctx = ExecuteContextImpl::new_for_gc(self, turbo_tasks, phase, &collector); - // `All` restores Data so `capture_all_outgoing_edges` below can read the + // `All` restores Data so `capture_all_edges` below can read the // Data-category dependency sets. The recheck itself only needs Meta. let mut task = ctx.task(task_id, TaskDataCategory::All); // Recheck under the guard: the shard scan saw this task without holding it, and a @@ -261,7 +273,7 @@ impl TurboTasksBackend { return ControlFlow::Continue(()); } - let old_edges = capture_all_outgoing_edges(&task); + let old_edges = capture_all_edges(&task); // Clear `immutable` defensively so `resurrect_deleted` can mark the task dirty if // it needs to task.set_immutable(false); @@ -287,9 +299,12 @@ impl TurboTasksBackend { // Delete outgoing edges but don't update the aggregation graph yet. // To avoid accidentally rebalancing on deleted tasks due to racing deletions, // we defer all rebalancing to the end - result.deferred_balance_edges.extend( - CleanupOldEdgesOperation::run_edge_deletions_only(task_id, old_edges, &mut ctx), - ); + let deferred = + CleanupOldEdgesOperation::run_edge_deletions_only(task_id, old_edges, &mut ctx); + result.deferred_balance_edges.extend(deferred.balance_edges); + result + .deferred_dirty_dependents + .extend(deferred.dirty_dependents); ControlFlow::Continue(()) }, |(stats, result), (other_stats, other_result)| { @@ -313,6 +328,21 @@ impl TurboTasksBackend { while !queue.process(&mut ctx) {} } + // Dirty the dependents whose edges were scrubbed. After the rebalance above so the + // aggregation graph is settled, and before the root scan below because dirtying can change + // activeness and therefore rootness. + let dirty_dependents = std::mem::take(&mut result.deferred_dirty_dependents); + if !dirty_dependents.is_empty() { + let noop_collector = |_task_id| {}; + let mut ctx = ExecuteContextImpl::new_for_gc(self, turbo_tasks, phase, &noop_collector); + let mut queue = AggregationUpdateQueue::new(); + // A dependent collected by this same pass is skipped: the job is weak by construction. + queue.push(AggregationUpdateJob::InvalidateDueToDependencyTornDown { + task_ids: dirty_dependents.into_iter().collect(), + }); + while !queue.process(&mut ctx) {} + } + // Collect all active roots // We don't do this in the GC pass because a task detected as a root 'early' might become a // non-root later due to other operations (e.g. it might get promoted to a live aggregation diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs index 27e2ee3e4ac2..24c80a309666 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/mod.rs @@ -68,7 +68,7 @@ use crate::{ AggregationUpdateJob, AggregationUpdateQueue, ChildExecuteContext, CleanupOldEdgesOperation, ConnectChildOperation, ExecuteContext, ExecuteContextImpl, LeafDistanceUpdateQueue, Operation, OutdatedEdge, TaskGuard, TaskType, TaskTypeRef, - capture_all_outgoing_edges, connect_children, get_aggregation_number, get_uppers, + capture_all_edges, connect_children, get_aggregation_number, get_uppers, make_task_dirty_internal, prepare_new_children, }, snapshot_coordinator::{OperationGuard, SnapshotCoordinator}, @@ -3442,7 +3442,7 @@ impl TurboTasksBackend { activeness_state.all_clean_event.notify(usize::MAX); } // Remove all the outgoing edges of this task. - let old_edges = capture_all_outgoing_edges(&task); + let old_edges = capture_all_edges(&task); drop(task); if !old_edges.is_empty() { diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs index 50a37a30ca42..f992b69cdb4e 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/aggregation_update.rs @@ -42,8 +42,9 @@ use crate::{ backend::{ TaskDataCategory, operation::{ - ExecuteContext, Operation, TaskGuard, connect_child::resurrect_deleted, - invalidate::make_task_dirty, + ExecuteContext, Operation, TaskGuard, + connect_child::resurrect_deleted, + invalidate::{make_task_dirty, try_make_task_dirty}, }, storage_schema::TaskStorageAccessors, }, @@ -306,6 +307,11 @@ pub enum AggregationUpdateJob { }, /// Notifies an upper task about changed data from an inner task. AggregatedDataUpdate(Box), + /// Mark these tasks dirty because they have a dependency on a task being deleted by GC. + /// + /// The id references are weak by construction: a dependent that was itself collected is + /// skipped. + InvalidateDueToDependencyTornDown { task_ids: TaskIdVec }, /// Invalidates tasks that are dependent on a collectible type. InvalidateDueToCollectiblesChange { task_ids: TaskIdVec, @@ -1535,6 +1541,18 @@ impl AggregationUpdateQueue { } self.aggregated_data_update(upper_ids, ctx, update); } + AggregationUpdateJob::InvalidateDueToDependencyTornDown { task_ids } => { + for task_id in task_ids { + // `try_*`: the dependent may itself have been collected in this cascade. + try_make_task_dirty( + task_id, + #[cfg(feature = "task_dirty_cause")] + TaskDirtyCause::DependencyTornDown, + self, + ctx, + ); + } + } AggregationUpdateJob::InvalidateDueToCollectiblesChange { task_ids, #[cfg(feature = "task_dirty_cause")] diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs index 592e3046ad75..88837f53b046 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/cleanup_old_edges.rs @@ -1,7 +1,8 @@ use std::mem::take; +use auto_hash_map::AutoSet; use bincode::{Decode, Encode}; -use rustc_hash::FxHashSet; +use rustc_hash::{FxBuildHasher, FxHashSet}; use smallvec::SmallVec; use turbo_tasks::TaskId; @@ -52,10 +53,16 @@ pub enum OutdatedEdge { HashedCellDependency(CellRef, u64), OutputDependency(TaskId), CollectiblesDependency(CollectiblesRef), + /// Reverse cell and output edges: some other task reads a cell of the task being deleted by GC. + /// Not modeled as a reversed `CellDependency` since the cleanup is assymetric, one side is + /// being deleted so we just tear down the other side. + CellDependentOfDeleted(CellRef), + HashedCellDependentOfDeleted(CellRef, u64), + OutputDependentOfDeleted(TaskId), } -/// Captures *all* of a task's outgoing edges as [`OutdatedEdge`]s -pub fn capture_all_outgoing_edges(task: &impl TaskStorageAccessors) -> Vec { +/// Captures *every* edge incident to a task -- both directions -- as [`OutdatedEdge`]s. +pub fn capture_all_edges(task: &impl TaskStorageAccessors) -> Vec { let mut old_edges: Vec = Vec::new(); old_edges.extend(task.iter_children().map(OutdatedEdge::Child)); old_edges.extend( @@ -74,24 +81,38 @@ pub fn capture_all_outgoing_edges(task: &impl TaskStorageAccessors) -> Vec>(ctx: &C) -> TaskDataCategory { - if ctx.collects_gc_candidates() { - // Under GC we need to query meta fields so be sure to recover Meta also - TaskDataCategory::All - } else { - TaskDataCategory::Data - } -} - #[cfg(feature = "trace_aggregation_update_stats")] type Stats = super::aggregation_update::AggregationUpdateQueueStats; #[cfg(not(feature = "trace_aggregation_update_stats"))] type Stats = (); +/// Work a GC-phase edge teardown produced but deliberately did not run, because it is unsafe while +/// other collect workers are still running. The caller replays it once the pass is quiescent. +#[derive(Default)] +pub struct DeferredCleanup { + /// Rebalance jobs. `balance_edge` *adds* aggregation edges, which must not happen mid-cascade. + pub balance_edges: Vec<(TaskId, TaskId)>, + /// Dependents whose forward edge to the torn-down task was scrubbed. They must be dirtied: the + /// value they were derived from no longer exists, so their cached result cannot be trusted. + pub dirty_dependents: AutoSet, +} + impl CleanupOldEdgesOperation { pub fn run( task_id: TaskId, @@ -109,24 +130,23 @@ impl CleanupOldEdgesOperation { /// GC variant: tears down `outdated`, running only the edge deletions. /// - /// Returns the balance jobs that the deletions produced, for the caller to replay once the - /// parallel collect is quiescent. Deletion is safe to run concurrently, but `balance_edge` - /// *adds* edges, which is not while other workers are still collecting. + /// Returns the work the deletions produced but did not run, for the caller to replay once the + /// parallel collect is quiescent. Deletion is safe to run concurrently; the deferred work is + /// not. `balance_edge` *adds* edges, and dirtying propagates through the aggregation graph -- + /// neither is safe while other workers are still collecting. pub fn run_edge_deletions_only<'a, C: ExecuteContext<'a>>( task_id: TaskId, outdated: Vec, ctx: &mut C, - ) -> impl Iterator + use { + ) -> DeferredCleanup { let op = CleanupOldEdgesOperation::RemoveEdges { task_id, outdated, queue: AggregationUpdateQueue::new_without_optimizations(), }; - op.execute_inner(ctx, true) - .1 - .map(|mut queue| queue.take_deferred_balance_edges()) - .into_iter() - .flatten() + let (_, stopped) = op.execute_inner(ctx, true); + // the option must be Some when stop_when_only_rebalance + stopped.unwrap() } fn execute_with_stats(self, ctx: &mut impl ExecuteContext<'_>) -> Stats { @@ -137,7 +157,8 @@ impl CleanupOldEdgesOperation { mut self, ctx: &mut impl ExecuteContext<'_>, stop_when_only_rebalance_remains: bool, - ) -> (Stats, Option) { + ) -> (Stats, Option) { + let mut dirty_dependents = AutoSet::default(); loop { ctx.operation_suspend_point(&self); match self { @@ -266,15 +287,11 @@ impl CleanupOldEdgesOperation { cell, } = forward; { - let category = dependent_scrub_category(ctx); - let mut task = ctx.task(cell_task_id, category); - let removed = task.remove_cell_dependents(&CellRef { + let mut task = ctx.task(cell_task_id, TaskDataCategory::Data); + task.remove_cell_dependents(&CellRef { task: task_id, cell, }); - if removed && task.is_cell_dependents_empty() { - ctx.note_maybe_collectible(&task); - } } { let mut task = ctx.task(task_id, TaskDataCategory::Data); @@ -288,19 +305,14 @@ impl CleanupOldEdgesOperation { cell, } = forward; { - let category = dependent_scrub_category(ctx); - let mut task = ctx.task(cell_task_id, category); - let removed = task.remove_cell_dependents_hashed(&( + let mut task = ctx.task(cell_task_id, TaskDataCategory::Data); + task.remove_cell_dependents_hashed(&( CellRef { task: task_id, cell, }, key, )); - - if removed && task.is_cell_dependents_hashed_empty() { - ctx.note_maybe_collectible(&task); - } } { let mut task = ctx.task(task_id, TaskDataCategory::Data); @@ -316,12 +328,8 @@ impl CleanupOldEdgesOperation { ) .entered(); { - let category = dependent_scrub_category(ctx); - let mut task = ctx.task(output_task_id, category); - let removed = task.remove_output_dependent(&task_id); - if removed && task.is_output_dependent_empty() { - ctx.note_maybe_collectible(&task); - } + let mut task = ctx.task(output_task_id, TaskDataCategory::Data); + task.remove_output_dependent(&task_id); } { let mut task = ctx.task(task_id, TaskDataCategory::Data); @@ -333,15 +341,12 @@ impl CleanupOldEdgesOperation { task: dependent_task_id, }) => { { - let category = dependent_scrub_category(ctx); - let mut task = ctx.task(dependent_task_id, category); - let removed = task.remove_collectibles_dependents(&( + let mut task = + ctx.task(dependent_task_id, TaskDataCategory::Meta); + task.remove_collectibles_dependents(&( collectible_type, task_id, )); - if removed && task.collectibles_dependents_len() == 0 { - ctx.note_maybe_collectible(&task); - } } { let mut task = ctx.task(task_id, TaskDataCategory::Data); @@ -351,9 +356,61 @@ impl CleanupOldEdgesOperation { }); } } + // Handle reverse edges, we remove these when `task_id` is being + // deleted. The reverse dependents must exist and we dirty them as we + // go. We also don't bother removing cell_dependents from `task_id` + // since that task is being deleted. + OutdatedEdge::CellDependentOfDeleted(CellRef { + task: dependent_task_id, + cell, + }) => { + // Orientation flip: the stored entry names the dependent, the + // forward entry we remove names `task_id`. + let forward = CellRef { + task: task_id, + cell, + }; + let mut task = ctx.task(dependent_task_id, TaskDataCategory::Data); + task.remove_cell_dependencies(&forward); + task.remove_outdated_cell_dependencies(&forward); + drop(task); + dirty_dependents.insert(dependent_task_id); + } + OutdatedEdge::HashedCellDependentOfDeleted( + CellRef { + task: dependent_task_id, + cell, + }, + key, + ) => { + let forward = CellRef { + task: task_id, + cell, + }; + let mut task = ctx.task(dependent_task_id, TaskDataCategory::Data); + task.remove_cell_dependencies_hashed(&(forward, key)); + task.remove_outdated_cell_dependencies_hashed(&(forward, key)); + drop(task); + dirty_dependents.insert(dependent_task_id); + } + OutdatedEdge::OutputDependentOfDeleted(dependent_task_id) => { + let mut task = ctx.task(dependent_task_id, TaskDataCategory::Data); + task.remove_output_dependencies(&task_id); + task.remove_outdated_output_dependencies(&task_id); + drop(task); + dirty_dependents.insert(dependent_task_id); + } } } + // If we accumulated any dirty_dependents flush them to the aggregation update + // queue before suspending. + if !stop_when_only_rebalance_remains && !dirty_dependents.is_empty() { + queue.push(AggregationUpdateJob::InvalidateDueToDependencyTornDown { + task_ids: take(&mut dirty_dependents).into_iter().collect(), + }); + } + if outdated.is_empty() { self = CleanupOldEdgesOperation::AggregationUpdate { queue: take(queue) }; } @@ -363,7 +420,13 @@ impl CleanupOldEdgesOperation { // Edge removal is done; hand the rebalance back to the caller. Any // other pending work would be dropped here, so `only_rebalance_remains` // asserts that nothing else is left. - return (Default::default(), Some(take(queue))); + return ( + Default::default(), + Some(DeferredCleanup { + balance_edges: queue.take_deferred_balance_edges().collect(), + dirty_dependents, + }), + ); } if queue.process(ctx) { self = CleanupOldEdgesOperation::Done { diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs index c426892d88ab..5cf7f79bb9db 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/invalidate.rs @@ -109,7 +109,7 @@ pub fn make_task_dirty( /// Marks a task dirty, doing nothing if it no longer exists. /// /// Intended for invalidation usecases. -fn try_make_task_dirty( +pub fn try_make_task_dirty( task_id: TaskId, #[cfg(feature = "task_dirty_cause")] cause: TaskDirtyCause, queue: &mut AggregationUpdateQueue, diff --git a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs index e05634e2ac56..e0e4c2ee87b9 100644 --- a/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs +++ b/turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs @@ -191,11 +191,6 @@ pub trait ExecuteContext<'e>: Sized { /// /// Only effective in a gc context see [`Self::collects_gc_candidates`]. fn note_maybe_collectible(&mut self, task: &impl TaskGuard); - /// Whether [`Self::note_maybe_collectible`] does anything, i.e. this is a GC context. - /// - /// Lets a caller skip work that only exists to feed the collector — in particular opening a - /// task with a wider [`TaskDataCategory`] than it would otherwise need. - fn collects_gc_candidates(&self) -> bool; fn should_track_dependencies(&self) -> bool; fn should_track_activeness(&self) -> bool; fn turbo_tasks(&self) -> Arc; @@ -1383,11 +1378,6 @@ impl<'e> ExecuteContext<'e> for ExecuteContextImpl<'e> { collector(task.id()); } } - - fn collects_gc_candidates(&self) -> bool { - matches!(self.phase, ExecutePhase::Gc(_)) - } - fn should_track_dependencies(&self) -> bool { self.backend.should_track_dependencies() } @@ -2099,7 +2089,7 @@ pub use self::{ AggregatedDataUpdate, AggregationUpdateJob, get_aggregation_number, get_uppers, is_aggregating_node, is_root_node, }, - cleanup_old_edges::{OutdatedEdge, capture_all_outgoing_edges}, + cleanup_old_edges::{OutdatedEdge, capture_all_edges}, connect_children::connect_children, invalidate::make_task_dirty_internal, prepare_new_children::prepare_new_children, diff --git a/turbopack/crates/turbo-tasks-backend/tests/gc_cross_session.rs b/turbopack/crates/turbo-tasks-backend/tests/gc_cross_session.rs index 8efa2c136afa..d55dff109039 100644 --- a/turbopack/crates/turbo-tasks-backend/tests/gc_cross_session.rs +++ b/turbopack/crates/turbo-tasks-backend/tests/gc_cross_session.rs @@ -15,11 +15,11 @@ use std::{ }; use anyhow::Result; -use turbo_tasks::{GcRoot, TurboTasks, Vc}; +use turbo_tasks::{GcRoot, ResolvedVc, TurboTasks, Vc}; use turbo_tasks_backend::TurboTasksBackend; use crate::{ - gc_fixture::{create_constant, diamond_root_op}, + gc_fixture::{Constant, Selector, create_constant, create_selector, diamond_root_op}, util::{create_persistence_dir, reopen_tt_with_gc, reopen_tt_with_gc_ttl}, }; @@ -219,3 +219,135 @@ async fn gc_collect_scrubs_disk_only_forward_dep_target() { result.unwrap(); } } + +// Regression test for a bug where a second cell reader owned by a different root task would panic +// when the root eventually ages out. +// +// This test merely asserts that such dependencies don't cause panics. +// +// `shared_target` is *called* by the owning side only, so that side is its sole parent. +// `borrowing_root` receives the target already resolved and only reads it through `shared_reader`: +// a forward cell-dependency, no child edge. So the target's `cell_dependents` holds reader 2 while +// its `parent_count` comes entirely from the owning side. +// +// Session 1 flips a selector to drop the owning side cleanly -- no invalidation, so the target +// keeps its edges -- and collects it. The shared target cascades with it, because +// `cell_dependents` do not keep a task alive (the documented `gc_collectible` heuristic). Reader 2 +// survives under the pinned borrowing root holding a forward dependency on a task that is now +// gone, and that state is persisted. +// +// Session 2 ages the borrowing root out. Tearing down reader 2 scrubs its forward dependency on +// the target collected back in session 1: a `MustExist` open of an already-collected task. The +// session does not need to execute anything -- the tasks only have to exist on disk for GC to +// reach them. + +#[turbo_tasks::function] +async fn shared_target(constant: ResolvedVc) -> Result> { + Ok(Vc::cell(*constant.await?.get() + 41)) +} + +/// Reads the shared target via an already-resolved `Vc`: a forward cell-dependency, no child edge. +#[turbo_tasks::function] +async fn shared_reader(target: ResolvedVc, n: u32) -> Result> { + Ok(Vc::cell(n + *target.await?)) +} + +/// Calls the shared target -- becoming its only parent -- and reads it through reader 1. +#[turbo_tasks::function] +async fn owning_subtree(constant: ResolvedVc) -> Result> { + let target = shared_target(*constant).to_resolved().await?; + assert_eq!(*shared_reader(*target, 1).await?, 42); + // Return the target's own `Vc`, so a caller that resolves this reaches `shared_target`. + Ok(*target) +} + +/// Selector-gated root over [`owning_subtree`]: flipping the selector to `true` drops the owning +/// subtree cleanly, without invalidating it, so it keeps its edges and becomes collectible. +#[turbo_tasks::function(operation, root)] +async fn select_owning( + selector: ResolvedVc, + constant: ResolvedVc, +) -> Result> { + // Pass the subtree's `Vc` through unchanged rather than re-celling it, so resolving this op + // names `shared_target` itself -- that is the task the borrowing side must depend on. + if !*selector.await?.get() { + Ok(owning_subtree(*constant)) + } else { + Ok(Vc::cell(0)) + } +} + +/// Reaches reader 2 through an **already-resolved** target, so it never calls the target and never +/// becomes its parent. Reader 2 keeps its forward cell-dependency on the target after the owning +/// subtree -- the target's only parent -- is collected. +#[turbo_tasks::function(operation, root)] +async fn borrowing_root(target: ResolvedVc) -> Result> { + Ok(Vc::cell(*shared_reader(*target, 2).await?)) +} + +/// A surviving task must not be left holding a forward cell-dependency on a collected task. +/// +/// Two readers share a cell target, but only one side *owns* it (is its parent). The owning side is +/// collected while the borrowing root is still pinned, so the target is destroyed with reader 2 +/// still depending on it -- and reader 2 is only torn down in the next session. +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +async fn shared_cell_target_collected_before_its_second_reader() { + let dir = create_persistence_dir("shared_cell_target_collected_before_its_second_reader"); + + // Session 1: build both sides over one shared target, drop the owning side, and collect it. + // That leaves reader 2 holding a dependency on the collected target, and persists it. + { + let tt = reopen_tt_with_gc(&dir); + let borrowing_op = turbo_tasks::run_once(tt.clone(), async move { + let selector_op = create_selector(false); + let selector_vc = selector_op.resolve().strongly_consistent().await?; + let selector = selector_op.read_strongly_consistent().await?; + + let constant_op = create_constant(); + let constant_vc = constant_op.resolve().strongly_consistent().await?; + + // The owning side parents the target and hands it back. + let owning = select_owning(selector_vc, constant_vc); + let target = owning.resolve().strongly_consistent().await?; + assert_eq!(*target.await?, 41); + + let borrowing = borrowing_root(target); + assert_eq!(*borrowing.read_strongly_consistent().await?, 43); + + // Drop the owning subtree cleanly: no invalidation, so the target keeps its edges. + selector.set(true); + assert_eq!(*owning.read_strongly_consistent().await?, 0); + anyhow::Ok(borrowing) + }) + .await + .unwrap(); + + // Pin the borrowing root so only the owning side is collectible. + let borrowing_pin = GcRoot::pin(tt.clone(), borrowing_op); + let collected = gc_until_collected(&tt, 3).await; + assert!( + collected >= 3, + "the owning subtree, reader 1 and the shared target should be collected (got {collected})" + ); + + tt.backend().snapshot_and_evict_for_testing(&tt); + drop(borrowing_pin); + + tt.stop_and_wait().await; + } + + // Session 2: nothing is pinned, so the borrowing root ages out. Tearing down reader 2 scrubs + // its forward dependency on the target collected in session 1 -- the dangling half-edge. + // Surviving these passes without a panic is the assertion. + { + let tt = reopen_tt_with_gc_ttl(&dir, Duration::ZERO); + let tt2 = tt.clone(); + turbo_tasks::run_once(tt.clone(), async move { + gc_until_collected(&tt2, 2).await; + anyhow::Ok(()) + }) + .await + .unwrap(); + tt.stop_and_wait().await; + } +} diff --git a/turbopack/crates/turbo-tasks/src/manager.rs b/turbopack/crates/turbo-tasks/src/manager.rs index eb648cb71157..4e8053b4e89d 100644 --- a/turbopack/crates/turbo-tasks/src/manager.rs +++ b/turbopack/crates/turbo-tasks/src/manager.rs @@ -964,6 +964,28 @@ impl TurboTasks { self.schedule(id, TaskPriority::initial()); } + /// Runs `future` as a top-level task and returns its result. + /// + /// # Returned values must not contain `Vc` or `ResolvedVc` or if they do they should be covered by a pin + /// + /// `T` is deliberately unconstrained for ergonomics, but a `Vc`/`ResolvedVc` that escapes this + /// call is **not** protected against garbage collection. It can be collected, and + /// dereferencing the stale handle then fails. + /// /// + /// To hand a computation out of a top-level task, return the [`OperationVc`] and pin it: + /// + /// ```ignore + /// let (resolved, op) = tt.run_once(async move { + /// let op = my_operation(args); + /// let resolved = op.resolve().strongly_consistent().await?; + /// // Return the operation too, so it can be pinned below. + /// Ok((resolved, op)) + /// }).await?; + /// let _gc_root = GcRoot::pin(tt.clone(), op); // unpins on drop + /// ``` + /// + /// Returning owned data -- [`ReadRef`](crate::ReadRef), `RcStr`, plain values -- is always + /// fine. pub async fn run_once( &self, future: impl Future> + Send + 'static, @@ -980,6 +1002,11 @@ impl TurboTasks { rx.await? } + /// Runs `future` as a top-level task and returns its result. + /// + /// The same constraint as [`run_once`](Self::run_once) applies to `T`: a `Vc`/`ResolvedVc` + /// returned from here is not anchored against garbage collection. See that method's docs for + /// why, and for the pin-the-`OperationVc` idiom. #[tracing::instrument(level = "trace", skip_all, name = "turbo_tasks::run")] pub async fn run( &self, diff --git a/turbopack/crates/turbo-tasks/src/task_dirty_cause.rs b/turbopack/crates/turbo-tasks/src/task_dirty_cause.rs index c9c49e5706f7..5d8cb28a1149 100644 --- a/turbopack/crates/turbo-tasks/src/task_dirty_cause.rs +++ b/turbopack/crates/turbo-tasks/src/task_dirty_cause.rs @@ -24,6 +24,9 @@ pub enum TaskDirtyCause { /// Re-dirtied because a GC-soft-deleted task was resurrected by a new connection before its /// hard-delete: its edges were scrubbed, so it must re-execute to rebuild them. Resurrected, + /// Dirtied because a task this one depended on was torn down: the value this task's result was + /// derived from no longer exists, so the result cannot be trusted. + DependencyTornDown, Unknown, } @@ -87,6 +90,7 @@ impl std::fmt::Display for TaskDirtyCause { } TaskDirtyCause::Invalidator => write!(f, "invalidator"), TaskDirtyCause::Resurrected => write!(f, "resurrected"), + TaskDirtyCause::DependencyTornDown => write!(f, "dependency torn down"), TaskDirtyCause::Unknown => write!(f, "unknown"), } }