Ignore:
Timestamp:
12/12/24 17:06:06 (5 weeks ago)
Author:
stefan toskovski <stefantoska84@…>
Branches:
main
Parents:
d565449
Message:

Pred finalna verzija

File:
1 edited

Legend:

Unmodified
Added
Removed
  • imaps-frontend/node_modules/@remix-run/router/router.ts

    rd565449 r0c6b92a  
    2323  FormMethod,
    2424  HTMLFormMethod,
    25   HandlerResult,
     25  DataStrategyResult,
    2626  ImmutableRouteKey,
    2727  MapRoutePropertiesFunction,
     
    3636  V7_FormMethod,
    3737  V7_MutationFormMethod,
    38   AgnosticPatchRoutesOnMissFunction,
     38  AgnosticPatchRoutesOnNavigationFunction,
    3939  DataWithResponseInit,
    4040} from "./utils";
     
    392392  hydrationData?: HydrationState;
    393393  window?: Window;
    394   unstable_patchRoutesOnMiss?: AgnosticPatchRoutesOnMissFunction;
    395   unstable_dataStrategy?: DataStrategyFunction;
     394  dataStrategy?: DataStrategyFunction;
     395  patchRoutesOnNavigation?: AgnosticPatchRoutesOnNavigationFunction;
    396396}
    397397
     
    423423      requestContext?: unknown;
    424424      skipLoaderErrorBubbling?: boolean;
    425       unstable_dataStrategy?: DataStrategyFunction;
     425      dataStrategy?: DataStrategyFunction;
    426426    }
    427427  ): Promise<StaticHandlerContext | Response>;
     
    431431      routeId?: string;
    432432      requestContext?: unknown;
    433       unstable_dataStrategy?: DataStrategyFunction;
     433      dataStrategy?: DataStrategyFunction;
    434434    }
    435435  ): Promise<any>;
     
    449449    opts: {
    450450      deletedFetchers: string[];
    451       unstable_viewTransitionOpts?: ViewTransitionOpts;
    452       unstable_flushSync: boolean;
     451      viewTransitionOpts?: ViewTransitionOpts;
     452      flushSync: boolean;
    453453    }
    454454  ): void;
     
    476476  preventScrollReset?: boolean;
    477477  relative?: RelativeRoutingType;
    478   unstable_flushSync?: boolean;
     478  flushSync?: boolean;
    479479};
    480480
     
    484484  state?: any;
    485485  fromRouteId?: string;
    486   unstable_viewTransition?: boolean;
     486  viewTransition?: boolean;
    487487};
    488488
     
    798798  let inFlightDataRoutes: AgnosticDataRouteObject[] | undefined;
    799799  let basename = init.basename || "/";
    800   let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
    801   let patchRoutesOnMissImpl = init.unstable_patchRoutesOnMiss;
     800  let dataStrategyImpl = init.dataStrategy || defaultDataStrategy;
     801  let patchRoutesOnNavigationImpl = init.patchRoutesOnNavigation;
    802802
    803803  // Config driven behavior flags
     
    832832  let initialErrors: RouteData | null = null;
    833833
    834   if (initialMatches == null && !patchRoutesOnMissImpl) {
     834  if (initialMatches == null && !patchRoutesOnNavigationImpl) {
    835835    // If we do not match a user-provided-route, fall back to the root
    836836    // to allow the error boundary to take over
     
    843843  }
    844844
    845   // In SPA apps, if the user provided a patchRoutesOnMiss implementation and
     845  // In SPA apps, if the user provided a patchRoutesOnNavigation implementation and
    846846  // our initial match is a splat route, clear them out so we run through lazy
    847847  // discovery on hydration in case there's a more accurate lazy route match.
     
    866866
    867867    // If partial hydration and fog of war is enabled, we will be running
    868     // `patchRoutesOnMiss` during hydration so include any partial matches as
     868    // `patchRoutesOnNavigation` during hydration so include any partial matches as
    869869    // the initial matches so we can properly render `HydrateFallback`'s
    870870    if (future.v7_partialHydration) {
     
    891891    let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
    892892    let errors = init.hydrationData ? init.hydrationData.errors : null;
    893     let isRouteInitialized = (m: AgnosticDataRouteMatch) => {
    894       // No loader, nothing to initialize
    895       if (!m.route.loader) {
    896         return true;
    897       }
    898       // Explicitly opting-in to running on hydration
    899       if (
    900         typeof m.route.loader === "function" &&
    901         m.route.loader.hydrate === true
    902       ) {
    903         return false;
    904       }
    905       // Otherwise, initialized if hydrated with data or an error
    906       return (
    907         (loaderData && loaderData[m.route.id] !== undefined) ||
    908         (errors && errors[m.route.id] !== undefined)
    909       );
    910     };
    911 
    912893    // If errors exist, don't consider routes below the boundary
    913894    if (errors) {
     
    915896        (m) => errors![m.route.id] !== undefined
    916897      );
    917       initialized = initialMatches.slice(0, idx + 1).every(isRouteInitialized);
     898      initialized = initialMatches
     899        .slice(0, idx + 1)
     900        .every((m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors));
    918901    } else {
    919       initialized = initialMatches.every(isRouteInitialized);
     902      initialized = initialMatches.every(
     903        (m) => !shouldLoadRouteOnHydration(m.route, loaderData, errors)
     904      );
    920905    }
    921906  } else {
     
    10211006  let blockerFunctions = new Map<string, BlockerFunction>();
    10221007
    1023   // Map of pending patchRoutesOnMiss() promises (keyed by path/matches) so
     1008  // Map of pending patchRoutesOnNavigation() promises (keyed by path/matches) so
    10241009  // that we only kick them off once for a given combo
    10251010  let pendingPatchRoutes = new Map<
    10261011    string,
    1027     ReturnType<AgnosticPatchRoutesOnMissFunction>
     1012    ReturnType<AgnosticPatchRoutesOnNavigationFunction>
    10281013  >();
    10291014
    10301015  // Flag to ignore the next history update, so we can revert the URL change on
    10311016  // a POP navigation that was blocked by the user without touching router state
    1032   let ignoreNextHistoryUpdate = false;
     1017  let unblockBlockerHistoryUpdate: (() => void) | undefined = undefined;
    10331018
    10341019  // Initialize the router, all side effects should be kicked off from here.
     
    10421027        // Ignore this event if it was just us resetting the URL from a
    10431028        // blocked POP navigation
    1044         if (ignoreNextHistoryUpdate) {
    1045           ignoreNextHistoryUpdate = false;
     1029        if (unblockBlockerHistoryUpdate) {
     1030          unblockBlockerHistoryUpdate();
     1031          unblockBlockerHistoryUpdate = undefined;
    10461032          return;
    10471033        }
     
    10651051        if (blockerKey && delta != null) {
    10661052          // Restore the URL to match the current UI, but don't update router state
    1067           ignoreNextHistoryUpdate = true;
     1053          let nextHistoryUpdatePromise = new Promise<void>((resolve) => {
     1054            unblockBlockerHistoryUpdate = resolve;
     1055          });
    10681056          init.history.go(delta * -1);
    10691057
     
    10791067                location,
    10801068              });
    1081               // Re-do the same POP navigation we just blocked
    1082               init.history.go(delta);
     1069              // Re-do the same POP navigation we just blocked, after the url
     1070              // restoration is also complete.  See:
     1071              // https://github.com/remix-run/react-router/issues/11613
     1072              nextHistoryUpdatePromise.then(() => init.history.go(delta));
    10831073            },
    10841074            reset() {
     
    11791169      subscriber(state, {
    11801170        deletedFetchers: deletedFetchersKeys,
    1181         unstable_viewTransitionOpts: opts.viewTransitionOpts,
    1182         unstable_flushSync: opts.flushSync === true,
     1171        viewTransitionOpts: opts.viewTransitionOpts,
     1172        flushSync: opts.flushSync === true,
    11831173      })
    11841174    );
     
    14031393        : undefined;
    14041394
    1405     let flushSync = (opts && opts.unstable_flushSync) === true;
     1395    let flushSync = (opts && opts.flushSync) === true;
    14061396
    14071397    let blockerKey = shouldBlockNavigation({
     
    14421432      preventScrollReset,
    14431433      replace: opts && opts.replace,
    1444       enableViewTransition: opts && opts.unstable_viewTransition,
     1434      enableViewTransition: opts && opts.viewTransition,
    14451435      flushSync,
    14461436    });
     
    14761466      pendingAction || state.historyAction,
    14771467      state.navigation.location,
    1478       { overrideNavigation: state.navigation }
     1468      {
     1469        overrideNavigation: state.navigation,
     1470        // Proxy through any rending view transition
     1471        enableViewTransition: pendingViewTransitionEnabled === true,
     1472      }
    14791473    );
    14801474  }
     
    15471541    // mutation submission.
    15481542    //
    1549     // Ignore on initial page loads because since the initial load will always
     1543    // Ignore on initial page loads because since the initial hydration will always
    15501544    // be "same hash".  For example, on /page#hash and submit a <Form method="post">
    15511545    // which will default to a navigation to /page
     
    16971691        return { shortCircuited: true };
    16981692      } else if (discoverResult.type === "error") {
    1699         let { boundaryId, error } = handleDiscoverRouteError(
    1700           location.pathname,
    1701           discoverResult
    1702         );
     1693        let boundaryId = findNearestBoundary(discoverResult.partialMatches)
     1694          .route.id;
    17031695        return {
    17041696          matches: discoverResult.partialMatches,
     
    17071699            {
    17081700              type: ResultType.error,
    1709               error,
     1701              error: discoverResult.error,
    17101702            },
    17111703          ],
     
    17461738      let results = await callDataStrategy(
    17471739        "action",
     1740        state,
    17481741        request,
    17491742        [actionMatch],
    1750         matches
     1743        matches,
     1744        null
    17511745      );
    1752       result = results[0];
     1746      result = results[actionMatch.route.id];
    17531747
    17541748      if (request.signal.aborted) {
     
    17721766        replace = location === state.location.pathname + state.location.search;
    17731767      }
    1774       await startRedirectNavigation(request, result, {
     1768      await startRedirectNavigation(request, result, true, {
    17751769        submission,
    17761770        replace,
     
    18731867        return { shortCircuited: true };
    18741868      } else if (discoverResult.type === "error") {
    1875         let { boundaryId, error } = handleDiscoverRouteError(
    1876           location.pathname,
    1877           discoverResult
    1878         );
     1869        let boundaryId = findNearestBoundary(discoverResult.partialMatches)
     1870          .route.id;
    18791871        return {
    18801872          matches: discoverResult.partialMatches,
    18811873          loaderData: {},
    18821874          errors: {
    1883             [boundaryId]: error,
     1875            [boundaryId]: discoverResult.error,
    18841876          },
    18851877        };
     
    19691961
    19701962    revalidatingFetchers.forEach((rf) => {
    1971       if (fetchControllers.has(rf.key)) {
    1972         abortFetcher(rf.key);
    1973       }
     1963      abortFetcher(rf.key);
    19741964      if (rf.controller) {
    19751965        // Fetchers use an independent AbortController so that aborting a fetcher
     
    19921982    let { loaderResults, fetcherResults } =
    19931983      await callLoadersAndMaybeResolveData(
    1994         state.matches,
     1984        state,
    19951985        matches,
    19961986        matchesToLoad,
     
    20122002      );
    20132003    }
     2004
    20142005    revalidatingFetchers.forEach((rf) => fetchControllers.delete(rf.key));
    20152006
    20162007    // If any loaders returned a redirect Response, start a new REPLACE navigation
    2017     let redirect = findRedirect([...loaderResults, ...fetcherResults]);
     2008    let redirect = findRedirect(loaderResults);
    20182009    if (redirect) {
    2019       if (redirect.idx >= matchesToLoad.length) {
    2020         // If this redirect came from a fetcher make sure we mark it in
    2021         // fetchRedirectIds so it doesn't get revalidated on the next set of
    2022         // loader executions
    2023         let fetcherKey =
    2024           revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
    2025         fetchRedirectIds.add(fetcherKey);
    2026       }
    2027       await startRedirectNavigation(request, redirect.result, {
     2010      await startRedirectNavigation(request, redirect.result, true, {
     2011        replace,
     2012      });
     2013      return { shortCircuited: true };
     2014    }
     2015
     2016    redirect = findRedirect(fetcherResults);
     2017    if (redirect) {
     2018      // If this redirect came from a fetcher make sure we mark it in
     2019      // fetchRedirectIds so it doesn't get revalidated on the next set of
     2020      // loader executions
     2021      fetchRedirectIds.add(redirect.key);
     2022      await startRedirectNavigation(request, redirect.result, true, {
    20282023        replace,
    20292024      });
     
    20352030      state,
    20362031      matches,
    2037       matchesToLoad,
    20382032      loaderResults,
    20392033      pendingActionResult,
     
    20552049    });
    20562050
    2057     // During partial hydration, preserve SSR errors for routes that don't re-run
     2051    // Preserve SSR errors during partial hydration
    20582052    if (future.v7_partialHydration && initialHydration && state.errors) {
    2059       Object.entries(state.errors)
    2060         .filter(([id]) => !matchesToLoad.some((m) => m.route.id === id))
    2061         .forEach(([routeId, error]) => {
    2062           errors = Object.assign(errors || {}, { [routeId]: error });
    2063         });
     2053      errors = { ...state.errors, ...errors };
    20642054    }
    20652055
     
    21252115    }
    21262116
    2127     if (fetchControllers.has(key)) abortFetcher(key);
    2128     let flushSync = (opts && opts.unstable_flushSync) === true;
     2117    abortFetcher(key);
     2118
     2119    let flushSync = (opts && opts.flushSync) === true;
    21292120
    21302121    let routesToUse = inFlightDataRoutes || dataRoutes;
     
    21702161    let match = getTargetMatch(matches, path);
    21712162
    2172     pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
     2163    let preventScrollReset = (opts && opts.preventScrollReset) === true;
    21732164
    21742165    if (submission && isMutationMethod(submission.formMethod)) {
     
    21812172        fogOfWar.active,
    21822173        flushSync,
     2174        preventScrollReset,
    21832175        submission
    21842176      );
     
    21972189      fogOfWar.active,
    21982190      flushSync,
     2191      preventScrollReset,
    21992192      submission
    22002193    );
     
    22112204    isFogOfWar: boolean,
    22122205    flushSync: boolean,
     2206    preventScrollReset: boolean,
    22132207    submission: Submission
    22142208  ) {
     
    22572251        return;
    22582252      } else if (discoverResult.type === "error") {
    2259         let { error } = handleDiscoverRouteError(path, discoverResult);
    2260         setFetcherError(key, routeId, error, { flushSync });
     2253        setFetcherError(key, routeId, discoverResult.error, { flushSync });
    22612254        return;
    22622255      } else if (!discoverResult.matches) {
     
    22842277    let actionResults = await callDataStrategy(
    22852278      "action",
     2279      state,
    22862280      fetchRequest,
    22872281      [match],
    2288       requestMatches
     2282      requestMatches,
     2283      key
    22892284    );
    2290     let actionResult = actionResults[0];
     2285    let actionResult = actionResults[match.route.id];
    22912286
    22922287    if (fetchRequest.signal.aborted) {
     
    23212316          fetchRedirectIds.add(key);
    23222317          updateFetcherState(key, getLoadingFetcher(submission));
    2323           return startRedirectNavigation(fetchRequest, actionResult, {
     2318          return startRedirectNavigation(fetchRequest, actionResult, false, {
    23242319            fetcherSubmission: submission,
     2320            preventScrollReset,
    23252321          });
    23262322        }
     
    23922388        );
    23932389        state.fetchers.set(staleKey, revalidatingFetcher);
    2394         if (fetchControllers.has(staleKey)) {
    2395           abortFetcher(staleKey);
    2396         }
     2390        abortFetcher(staleKey);
    23972391        if (rf.controller) {
    23982392          fetchControllers.set(staleKey, rf.controller);
     
    24122406    let { loaderResults, fetcherResults } =
    24132407      await callLoadersAndMaybeResolveData(
    2414         state.matches,
     2408        state,
    24152409        matches,
    24162410        matchesToLoad,
     
    24322426    revalidatingFetchers.forEach((r) => fetchControllers.delete(r.key));
    24332427
    2434     let redirect = findRedirect([...loaderResults, ...fetcherResults]);
     2428    let redirect = findRedirect(loaderResults);
    24352429    if (redirect) {
    2436       if (redirect.idx >= matchesToLoad.length) {
    2437         // If this redirect came from a fetcher make sure we mark it in
    2438         // fetchRedirectIds so it doesn't get revalidated on the next set of
    2439         // loader executions
    2440         let fetcherKey =
    2441           revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
    2442         fetchRedirectIds.add(fetcherKey);
    2443       }
    2444       return startRedirectNavigation(revalidationRequest, redirect.result);
     2430      return startRedirectNavigation(
     2431        revalidationRequest,
     2432        redirect.result,
     2433        false,
     2434        { preventScrollReset }
     2435      );
     2436    }
     2437
     2438    redirect = findRedirect(fetcherResults);
     2439    if (redirect) {
     2440      // If this redirect came from a fetcher make sure we mark it in
     2441      // fetchRedirectIds so it doesn't get revalidated on the next set of
     2442      // loader executions
     2443      fetchRedirectIds.add(redirect.key);
     2444      return startRedirectNavigation(
     2445        revalidationRequest,
     2446        redirect.result,
     2447        false,
     2448        { preventScrollReset }
     2449      );
    24452450    }
    24462451
     
    24482453    let { loaderData, errors } = processLoaderData(
    24492454      state,
    2450       state.matches,
    2451       matchesToLoad,
     2455      matches,
    24522456      loaderResults,
    24532457      undefined,
     
    25092513    isFogOfWar: boolean,
    25102514    flushSync: boolean,
     2515    preventScrollReset: boolean,
    25112516    submission?: Submission
    25122517  ) {
     
    25382543        return;
    25392544      } else if (discoverResult.type === "error") {
    2540         let { error } = handleDiscoverRouteError(path, discoverResult);
    2541         setFetcherError(key, routeId, error, { flushSync });
     2545        setFetcherError(key, routeId, discoverResult.error, { flushSync });
    25422546        return;
    25432547      } else if (!discoverResult.matches) {
     
    25612565    let results = await callDataStrategy(
    25622566      "loader",
     2567      state,
    25632568      fetchRequest,
    25642569      [match],
    2565       matches
     2570      matches,
     2571      key
    25662572    );
    2567     let result = results[0];
     2573    let result = results[match.route.id];
    25682574
    25692575    // Deferred isn't supported for fetcher loads, await everything and treat it
     
    26032609      } else {
    26042610        fetchRedirectIds.add(key);
    2605         await startRedirectNavigation(fetchRequest, result);
     2611        await startRedirectNavigation(fetchRequest, result, false, {
     2612          preventScrollReset,
     2613        });
    26062614        return;
    26072615      }
     
    26422650    request: Request,
    26432651    redirect: RedirectResult,
     2652    isNavigation: boolean,
    26442653    {
    26452654      submission,
    26462655      fetcherSubmission,
     2656      preventScrollReset,
    26472657      replace,
    26482658    }: {
    26492659      submission?: Submission;
    26502660      fetcherSubmission?: Submission;
     2661      preventScrollReset?: boolean;
    26512662      replace?: boolean;
    26522663    } = {}
     
    27282739          formAction: location,
    27292740        },
    2730         // Preserve this flag across redirects
    2731         preventScrollReset: pendingPreventScrollReset,
     2741        // Preserve these flags across redirects
     2742        preventScrollReset: preventScrollReset || pendingPreventScrollReset,
     2743        enableViewTransition: isNavigation
     2744          ? pendingViewTransitionEnabled
     2745          : undefined,
    27322746      });
    27332747    } else {
     
    27422756        // Send fetcher submissions through for shouldRevalidate
    27432757        fetcherSubmission,
    2744         // Preserve this flag across redirects
    2745         preventScrollReset: pendingPreventScrollReset,
     2758        // Preserve these flags across redirects
     2759        preventScrollReset: preventScrollReset || pendingPreventScrollReset,
     2760        enableViewTransition: isNavigation
     2761          ? pendingViewTransitionEnabled
     2762          : undefined,
    27462763      });
    27472764    }
     
    27522769  async function callDataStrategy(
    27532770    type: "loader" | "action",
     2771    state: RouterState,
    27542772    request: Request,
    27552773    matchesToLoad: AgnosticDataRouteMatch[],
    2756     matches: AgnosticDataRouteMatch[]
    2757   ): Promise<DataResult[]> {
     2774    matches: AgnosticDataRouteMatch[],
     2775    fetcherKey: string | null
     2776  ): Promise<Record<string, DataResult>> {
     2777    let results: Record<string, DataStrategyResult>;
     2778    let dataResults: Record<string, DataResult> = {};
    27582779    try {
    2759       let results = await callDataStrategyImpl(
     2780      results = await callDataStrategyImpl(
    27602781        dataStrategyImpl,
    27612782        type,
     2783        state,
    27622784        request,
    27632785        matchesToLoad,
    27642786        matches,
     2787        fetcherKey,
    27652788        manifest,
    27662789        mapRouteProperties
    2767       );
    2768 
    2769       return await Promise.all(
    2770         results.map((result, i) => {
    2771           if (isRedirectHandlerResult(result)) {
    2772             let response = result.result as Response;
    2773             return {
    2774               type: ResultType.redirect,
    2775               response: normalizeRelativeRoutingRedirectResponse(
    2776                 response,
    2777                 request,
    2778                 matchesToLoad[i].route.id,
    2779                 matches,
    2780                 basename,
    2781                 future.v7_relativeSplatPath
    2782               ),
    2783             };
    2784           }
    2785 
    2786           return convertHandlerResultToDataResult(result);
    2787         })
    27882790      );
    27892791    } catch (e) {
    27902792      // If the outer dataStrategy method throws, just return the error for all
    27912793      // matches - and it'll naturally bubble to the root
    2792       return matchesToLoad.map(() => ({
    2793         type: ResultType.error,
    2794         error: e,
    2795       }));
    2796     }
     2794      matchesToLoad.forEach((m) => {
     2795        dataResults[m.route.id] = {
     2796          type: ResultType.error,
     2797          error: e,
     2798        };
     2799      });
     2800      return dataResults;
     2801    }
     2802
     2803    for (let [routeId, result] of Object.entries(results)) {
     2804      if (isRedirectDataStrategyResultResult(result)) {
     2805        let response = result.result as Response;
     2806        dataResults[routeId] = {
     2807          type: ResultType.redirect,
     2808          response: normalizeRelativeRoutingRedirectResponse(
     2809            response,
     2810            request,
     2811            routeId,
     2812            matches,
     2813            basename,
     2814            future.v7_relativeSplatPath
     2815          ),
     2816        };
     2817      } else {
     2818        dataResults[routeId] = await convertDataStrategyResultToDataResult(
     2819          result
     2820        );
     2821      }
     2822    }
     2823
     2824    return dataResults;
    27972825  }
    27982826
    27992827  async function callLoadersAndMaybeResolveData(
    2800     currentMatches: AgnosticDataRouteMatch[],
     2828    state: RouterState,
    28012829    matches: AgnosticDataRouteMatch[],
    28022830    matchesToLoad: AgnosticDataRouteMatch[],
     
    28042832    request: Request
    28052833  ) {
    2806     let [loaderResults, ...fetcherResults] = await Promise.all([
    2807       matchesToLoad.length
    2808         ? callDataStrategy("loader", request, matchesToLoad, matches)
    2809         : [],
    2810       ...fetchersToLoad.map((f) => {
     2834    let currentMatches = state.matches;
     2835
     2836    // Kick off loaders and fetchers in parallel
     2837    let loaderResultsPromise = callDataStrategy(
     2838      "loader",
     2839      state,
     2840      request,
     2841      matchesToLoad,
     2842      matches,
     2843      null
     2844    );
     2845
     2846    let fetcherResultsPromise = Promise.all(
     2847      fetchersToLoad.map(async (f) => {
    28112848        if (f.matches && f.match && f.controller) {
    2812           let fetcherRequest = createClientSideRequest(
    2813             init.history,
    2814             f.path,
    2815             f.controller.signal
     2849          let results = await callDataStrategy(
     2850            "loader",
     2851            state,
     2852            createClientSideRequest(init.history, f.path, f.controller.signal),
     2853            [f.match],
     2854            f.matches,
     2855            f.key
    28162856          );
    2817           return callDataStrategy(
    2818             "loader",
    2819             fetcherRequest,
    2820             [f.match],
    2821             f.matches
    2822           ).then((r) => r[0]);
     2857          let result = results[f.match.route.id];
     2858          // Fetcher results are keyed by fetcher key from here on out, not routeId
     2859          return { [f.key]: result };
    28232860        } else {
    2824           return Promise.resolve<DataResult>({
    2825             type: ResultType.error,
    2826             error: getInternalRouterError(404, {
    2827               pathname: f.path,
    2828             }),
     2861          return Promise.resolve({
     2862            [f.key]: {
     2863              type: ResultType.error,
     2864              error: getInternalRouterError(404, {
     2865                pathname: f.path,
     2866              }),
     2867            } as ErrorResult,
    28292868          });
    28302869        }
    2831       }),
    2832     ]);
     2870      })
     2871    );
     2872
     2873    let loaderResults = await loaderResultsPromise;
     2874    let fetcherResults = (await fetcherResultsPromise).reduce(
     2875      (acc, r) => Object.assign(acc, r),
     2876      {}
     2877    );
    28332878
    28342879    await Promise.all([
    2835       resolveDeferredResults(
     2880      resolveNavigationDeferredResults(
     2881        matches,
     2882        loaderResults,
     2883        request.signal,
    28362884        currentMatches,
    2837         matchesToLoad,
    2838         loaderResults,
    2839         loaderResults.map(() => request.signal),
    2840         false,
    28412885        state.loaderData
    28422886      ),
    2843       resolveDeferredResults(
    2844         currentMatches,
    2845         fetchersToLoad.map((f) => f.match),
    2846         fetcherResults,
    2847         fetchersToLoad.map((f) => (f.controller ? f.controller.signal : null)),
    2848         true
    2849       ),
     2887      resolveFetcherDeferredResults(matches, fetcherResults, fetchersToLoad),
    28502888    ]);
    28512889
     
    28682906      if (fetchControllers.has(key)) {
    28692907        cancelledFetcherLoads.add(key);
    2870         abortFetcher(key);
    2871       }
     2908      }
     2909      abortFetcher(key);
    28722910    });
    28732911  }
     
    29522990  function abortFetcher(key: string) {
    29532991    let controller = fetchControllers.get(key);
    2954     invariant(controller, `Expected fetch controller: ${key}`);
    2955     controller.abort();
    2956     fetchControllers.delete(key);
     2992    if (controller) {
     2993      controller.abort();
     2994      fetchControllers.delete(key);
     2995    }
    29572996  }
    29582997
     
    30783117
    30793118    return { notFoundMatches: matches, route, error };
    3080   }
    3081 
    3082   function handleDiscoverRouteError(
    3083     pathname: string,
    3084     discoverResult: DiscoverRoutesErrorResult
    3085   ) {
    3086     return {
    3087       boundaryId: findNearestBoundary(discoverResult.partialMatches).route.id,
    3088       error: getInternalRouterError(400, {
    3089         type: "route-discovery",
    3090         pathname,
    3091         message:
    3092           discoverResult.error != null && "message" in discoverResult.error
    3093             ? discoverResult.error
    3094             : String(discoverResult.error),
    3095       }),
    3096     };
    30973119  }
    30983120
     
    31833205    pathname: string
    31843206  ): { active: boolean; matches: AgnosticDataRouteMatch[] | null } {
    3185     if (patchRoutesOnMissImpl) {
     3207    if (patchRoutesOnNavigationImpl) {
    31863208      if (!matches) {
    31873209        let fogMatches = matchRoutesImpl<AgnosticDataRouteObject>(
     
    31943216        return { active: true, matches: fogMatches || [] };
    31953217      } else {
    3196         let leafRoute = matches[matches.length - 1].route;
    3197         if (
    3198           leafRoute.path &&
    3199           (leafRoute.path === "*" || leafRoute.path.endsWith("/*"))
    3200         ) {
    3201           // If we matched a splat, it might only be because we haven't yet fetched
    3202           // the children that would match with a higher score, so let's fetch
    3203           // around and find out
     3218        if (Object.keys(matches[0].params).length > 0) {
     3219          // If we matched a dynamic param or a splat, it might only be because
     3220          // we haven't yet discovered other routes that would match with a
     3221          // higher score.  Call patchRoutesOnNavigation just to be sure
    32043222          let partialMatches = matchRoutesImpl<AgnosticDataRouteObject>(
    32053223            routesToUse,
     
    32363254    signal: AbortSignal
    32373255  ): Promise<DiscoverRoutesResult> {
     3256    if (!patchRoutesOnNavigationImpl) {
     3257      return { type: "success", matches };
     3258    }
     3259
    32383260    let partialMatches: AgnosticDataRouteMatch[] | null = matches;
    3239     let route =
    3240       partialMatches.length > 0
    3241         ? partialMatches[partialMatches.length - 1].route
    3242         : null;
    32433261    while (true) {
    32443262      let isNonHMR = inFlightDataRoutes == null;
    32453263      let routesToUse = inFlightDataRoutes || dataRoutes;
     3264      let localManifest = manifest;
    32463265      try {
    3247         await loadLazyRouteChildren(
    3248           patchRoutesOnMissImpl!,
    3249           pathname,
    3250           partialMatches,
    3251           routesToUse,
    3252           manifest,
    3253           mapRouteProperties,
    3254           pendingPatchRoutes,
    3255           signal
    3256         );
     3266        await patchRoutesOnNavigationImpl({
     3267          path: pathname,
     3268          matches: partialMatches,
     3269          patch: (routeId, children) => {
     3270            if (signal.aborted) return;
     3271            patchRoutesImpl(
     3272              routeId,
     3273              children,
     3274              routesToUse,
     3275              localManifest,
     3276              mapRouteProperties
     3277            );
     3278          },
     3279        });
    32573280      } catch (e) {
    32583281        return { type: "error", error: e, partialMatches };
     
    32643287        // HMR will already update the identity and reflow when it lands
    32653288        // `inFlightDataRoutes` in `completeNavigation`
    3266         if (isNonHMR) {
     3289        if (isNonHMR && !signal.aborted) {
    32673290          dataRoutes = [...dataRoutes];
    32683291        }
     
    32743297
    32753298      let newMatches = matchRoutes(routesToUse, pathname, basename);
    3276       let matchedSplat = false;
    32773299      if (newMatches) {
    3278         let leafRoute = newMatches[newMatches.length - 1].route;
    3279 
    3280         if (leafRoute.index) {
    3281           // If we found an index route, we can stop
    3282           return { type: "success", matches: newMatches };
    3283         }
    3284 
    3285         if (leafRoute.path && leafRoute.path.length > 0) {
    3286           if (leafRoute.path === "*") {
    3287             // If we found a splat route, we can't be sure there's not a
    3288             // higher-scoring route down some partial matches trail so we need
    3289             // to check that out
    3290             matchedSplat = true;
    3291           } else {
    3292             // If we found a non-splat route, we can stop
    3293             return { type: "success", matches: newMatches };
    3294           }
    3295         }
     3300        return { type: "success", matches: newMatches };
    32963301      }
    32973302
     
    33033308      );
    33043309
    3305       // If we are no longer partially matching anything, this was either a
    3306       // legit splat match above, or it's a 404.  Also avoid loops if the
    3307       // second pass results in the same partial matches
     3310      // Avoid loops if the second pass results in the same partial matches
    33083311      if (
    33093312        !newPartialMatches ||
    3310         partialMatches.map((m) => m.route.id).join("-") ===
    3311           newPartialMatches.map((m) => m.route.id).join("-")
     3313        (partialMatches.length === newPartialMatches.length &&
     3314          partialMatches.every(
     3315            (m, i) => m.route.id === newPartialMatches![i].route.id
     3316          ))
    33123317      ) {
    3313         return { type: "success", matches: matchedSplat ? newMatches : null };
     3318        return { type: "success", matches: null };
    33143319      }
    33153320
    33163321      partialMatches = newPartialMatches;
    3317       route = partialMatches[partialMatches.length - 1].route;
    3318       if (route.path === "*") {
    3319         // The splat is still our most accurate partial, so run with it
    3320         return { type: "success", matches: partialMatches };
    3321       }
    33223322    }
    33233323  }
     
    34933493      requestContext,
    34943494      skipLoaderErrorBubbling,
    3495       unstable_dataStrategy,
     3495      dataStrategy,
    34963496    }: {
    34973497      requestContext?: unknown;
    34983498      skipLoaderErrorBubbling?: boolean;
    3499       unstable_dataStrategy?: DataStrategyFunction;
     3499      dataStrategy?: DataStrategyFunction;
    35003500    } = {}
    35013501  ): Promise<StaticHandlerContext | Response> {
     
    35493549      matches,
    35503550      requestContext,
    3551       unstable_dataStrategy || null,
     3551      dataStrategy || null,
    35523552      skipLoaderErrorBubbling === true,
    35533553      null
     
    35943594      routeId,
    35953595      requestContext,
    3596       unstable_dataStrategy,
     3596      dataStrategy,
    35973597    }: {
    35983598      requestContext?: unknown;
    35993599      routeId?: string;
    3600       unstable_dataStrategy?: DataStrategyFunction;
     3600      dataStrategy?: DataStrategyFunction;
    36013601    } = {}
    36023602  ): Promise<any> {
     
    36323632      matches,
    36333633      requestContext,
    3634       unstable_dataStrategy || null,
     3634      dataStrategy || null,
    36353635      false,
    36363636      match
     
    36713671    matches: AgnosticDataRouteMatch[],
    36723672    requestContext: unknown,
    3673     unstable_dataStrategy: DataStrategyFunction | null,
     3673    dataStrategy: DataStrategyFunction | null,
    36743674    skipLoaderErrorBubbling: boolean,
    36753675    routeMatch: AgnosticDataRouteMatch | null
     
    36873687          routeMatch || getTargetMatch(matches, location),
    36883688          requestContext,
    3689           unstable_dataStrategy,
     3689          dataStrategy,
    36903690          skipLoaderErrorBubbling,
    36913691          routeMatch != null
     
    36983698        matches,
    36993699        requestContext,
    3700         unstable_dataStrategy,
     3700        dataStrategy,
    37013701        skipLoaderErrorBubbling,
    37023702        routeMatch
     
    37113711    } catch (e) {
    37123712      // If the user threw/returned a Response in callLoaderOrAction for a
    3713       // `queryRoute` call, we throw the `HandlerResult` to bail out early
     3713      // `queryRoute` call, we throw the `DataStrategyResult` to bail out early
    37143714      // and then return or throw the raw Response here accordingly
    3715       if (isHandlerResult(e) && isResponse(e.result)) {
     3715      if (isDataStrategyResult(e) && isResponse(e.result)) {
    37163716        if (e.type === ResultType.error) {
    37173717          throw e.result;
     
    37333733    actionMatch: AgnosticDataRouteMatch,
    37343734    requestContext: unknown,
    3735     unstable_dataStrategy: DataStrategyFunction | null,
     3735    dataStrategy: DataStrategyFunction | null,
    37363736    skipLoaderErrorBubbling: boolean,
    37373737    isRouteRequest: boolean
     
    37603760        isRouteRequest,
    37613761        requestContext,
    3762         unstable_dataStrategy
     3762        dataStrategy
    37633763      );
    3764       result = results[0];
     3764      result = results[actionMatch.route.id];
    37653765
    37663766      if (request.signal.aborted) {
     
    38323832        matches,
    38333833        requestContext,
    3834         unstable_dataStrategy,
     3834        dataStrategy,
    38353835        skipLoaderErrorBubbling,
    38363836        null,
     
    38573857      matches,
    38583858      requestContext,
    3859       unstable_dataStrategy,
     3859      dataStrategy,
    38603860      skipLoaderErrorBubbling,
    38613861      null
     
    38793879    matches: AgnosticDataRouteMatch[],
    38803880    requestContext: unknown,
    3881     unstable_dataStrategy: DataStrategyFunction | null,
     3881    dataStrategy: DataStrategyFunction | null,
    38823882    skipLoaderErrorBubbling: boolean,
    38833883    routeMatch: AgnosticDataRouteMatch | null,
     
    39423942      isRouteRequest,
    39433943      requestContext,
    3944       unstable_dataStrategy
     3944      dataStrategy
    39453945    );
    39463946
     
    39533953    let context = processRouteLoaderData(
    39543954      matches,
    3955       matchesToLoad,
    39563955      results,
    39573956      pendingActionResult,
     
    39893988    isRouteRequest: boolean,
    39903989    requestContext: unknown,
    3991     unstable_dataStrategy: DataStrategyFunction | null
    3992   ): Promise<DataResult[]> {
     3990    dataStrategy: DataStrategyFunction | null
     3991  ): Promise<Record<string, DataResult>> {
    39933992    let results = await callDataStrategyImpl(
    3994       unstable_dataStrategy || defaultDataStrategy,
     3993      dataStrategy || defaultDataStrategy,
    39953994      type,
     3995      null,
    39963996      request,
    39973997      matchesToLoad,
    39983998      matches,
     3999      null,
    39994000      manifest,
    40004001      mapRouteProperties,
     
    40024003    );
    40034004
    4004     return await Promise.all(
    4005       results.map((result, i) => {
    4006         if (isRedirectHandlerResult(result)) {
     4005    let dataResults: Record<string, DataResult> = {};
     4006    await Promise.all(
     4007      matches.map(async (match) => {
     4008        if (!(match.route.id in results)) {
     4009          return;
     4010        }
     4011        let result = results[match.route.id];
     4012        if (isRedirectDataStrategyResultResult(result)) {
    40074013          let response = result.result as Response;
    40084014          // Throw redirects and let the server handle them with an HTTP redirect
     
    40104016            response,
    40114017            request,
    4012             matchesToLoad[i].route.id,
     4018            match.route.id,
    40134019            matches,
    40144020            basename,
     
    40224028        }
    40234029
    4024         return convertHandlerResultToDataResult(result);
     4030        dataResults[match.route.id] =
     4031          await convertDataStrategyResultToDataResult(result);
    40254032      })
    40264033    );
     4034    return dataResults;
    40274035  }
    40284036
     
    41264134  }
    41274135
    4128   // Add an ?index param for matched index routes if we don't already have one
    4129   if (
    4130     (to == null || to === "" || to === ".") &&
    4131     activeRouteMatch &&
    4132     activeRouteMatch.route.index &&
    4133     !hasNakedIndexQuery(path.search)
    4134   ) {
    4135     path.search = path.search
    4136       ? path.search.replace(/^\?/, "?index&")
    4137       : "?index";
     4136  // Account for `?index` params when routing to the current location
     4137  if ((to == null || to === "" || to === ".") && activeRouteMatch) {
     4138    let nakedIndex = hasNakedIndexQuery(path.search);
     4139    if (activeRouteMatch.route.index && !nakedIndex) {
     4140      // Add one when we're targeting an index route
     4141      path.search = path.search
     4142        ? path.search.replace(/^\?/, "?index&")
     4143        : "?index";
     4144    } else if (!activeRouteMatch.route.index && nakedIndex) {
     4145      // Remove existing ones when we're not
     4146      let params = new URLSearchParams(path.search);
     4147      let indexValues = params.getAll("index");
     4148      params.delete("index");
     4149      indexValues.filter((v) => v).forEach((v) => params.append("index", v));
     4150      let qs = params.toString();
     4151      path.search = qs ? `?${qs}` : "";
     4152    }
    41384153  }
    41394154
     
    42994314}
    43004315
    4301 // Filter out all routes below any caught error as they aren't going to
     4316// Filter out all routes at/below any caught error as they aren't going to
    43024317// render so we don't need to load them
    43034318function getLoaderMatchesUntilBoundary(
    43044319  matches: AgnosticDataRouteMatch[],
    4305   boundaryId: string
     4320  boundaryId: string,
     4321  includeBoundary = false
    43064322) {
    4307   let boundaryMatches = matches;
    4308   if (boundaryId) {
    4309     let index = matches.findIndex((m) => m.route.id === boundaryId);
    4310     if (index >= 0) {
    4311       boundaryMatches = matches.slice(0, index);
    4312     }
    4313   }
    4314   return boundaryMatches;
     4323  let index = matches.findIndex((m) => m.route.id === boundaryId);
     4324  if (index >= 0) {
     4325    return matches.slice(0, includeBoundary ? index + 1 : index);
     4326  }
     4327  return matches;
    43154328}
    43164329
     
    43214334  submission: Submission | undefined,
    43224335  location: Location,
    4323   isInitialLoad: boolean,
     4336  initialHydration: boolean,
    43244337  skipActionErrorRevalidation: boolean,
    43254338  isRevalidationRequired: boolean,
     
    43424355
    43434356  // Pick navigation matches that are net-new or qualify for revalidation
    4344   let boundaryId =
    4345     pendingActionResult && isErrorResult(pendingActionResult[1])
    4346       ? pendingActionResult[0]
    4347       : undefined;
    4348   let boundaryMatches = boundaryId
    4349     ? getLoaderMatchesUntilBoundary(matches, boundaryId)
    4350     : matches;
     4357  let boundaryMatches = matches;
     4358  if (initialHydration && state.errors) {
     4359    // On initial hydration, only consider matches up to _and including_ the boundary.
     4360    // This is inclusive to handle cases where a server loader ran successfully,
     4361    // a child server loader bubbled up to this route, but this route has
     4362    // `clientLoader.hydrate` so we want to still run the `clientLoader` so that
     4363    // we have a complete version of `loaderData`
     4364    boundaryMatches = getLoaderMatchesUntilBoundary(
     4365      matches,
     4366      Object.keys(state.errors)[0],
     4367      true
     4368    );
     4369  } else if (pendingActionResult && isErrorResult(pendingActionResult[1])) {
     4370    // If an action threw an error, we call loaders up to, but not including the
     4371    // boundary
     4372    boundaryMatches = getLoaderMatchesUntilBoundary(
     4373      matches,
     4374      pendingActionResult[0]
     4375    );
     4376  }
    43514377
    43524378  // Don't revalidate loaders by default after action 4xx/5xx responses
     
    43704396    }
    43714397
    4372     if (isInitialLoad) {
    4373       if (typeof route.loader !== "function" || route.loader.hydrate) {
    4374         return true;
    4375       }
    4376       return (
    4377         state.loaderData[route.id] === undefined &&
    4378         // Don't re-run if the loader ran and threw an error
    4379         (!state.errors || state.errors[route.id] === undefined)
    4380       );
     4398    if (initialHydration) {
     4399      return shouldLoadRouteOnHydration(route, state.loaderData, state.errors);
    43814400    }
    43824401
     
    44204439  fetchLoadMatches.forEach((f, key) => {
    44214440    // Don't revalidate:
    4422     //  - on initial load (shouldn't be any fetchers then anyway)
     4441    //  - on initial hydration (shouldn't be any fetchers then anyway)
    44234442    //  - if fetcher won't be present in the subsequent render
    44244443    //    - no longer matches the URL (v7_fetcherPersist=false)
    44254444    //    - was unmounted but persisted due to v7_fetcherPersist=true
    44264445    if (
    4427       isInitialLoad ||
     4446      initialHydration ||
    44284447      !matches.some((m) => m.route.id === f.routeId) ||
    44294448      deletedFetchers.has(key)
     
    45054524}
    45064525
     4526function shouldLoadRouteOnHydration(
     4527  route: AgnosticDataRouteObject,
     4528  loaderData: RouteData | null | undefined,
     4529  errors: RouteData | null | undefined
     4530) {
     4531  // We dunno if we have a loader - gotta find out!
     4532  if (route.lazy) {
     4533    return true;
     4534  }
     4535
     4536  // No loader, nothing to initialize
     4537  if (!route.loader) {
     4538    return false;
     4539  }
     4540
     4541  let hasData = loaderData != null && loaderData[route.id] !== undefined;
     4542  let hasError = errors != null && errors[route.id] !== undefined;
     4543
     4544  // Don't run if we error'd during SSR
     4545  if (!hasData && hasError) {
     4546    return false;
     4547  }
     4548
     4549  // Explicitly opting-in to running on hydration
     4550  if (typeof route.loader === "function" && route.loader.hydrate === true) {
     4551    return true;
     4552  }
     4553
     4554  // Otherwise, run if we're not yet initialized with anything
     4555  return !hasData && !hasError;
     4556}
     4557
    45074558function isNewLoader(
    45084559  currentLoaderData: RouteData,
     
    45544605}
    45554606
    4556 /**
    4557  * Idempotent utility to execute patchRoutesOnMiss() to lazily load route
    4558  * definitions and update the routes/routeManifest
    4559  */
    4560 async function loadLazyRouteChildren(
    4561   patchRoutesOnMissImpl: AgnosticPatchRoutesOnMissFunction,
    4562   path: string,
    4563   matches: AgnosticDataRouteMatch[],
    4564   routes: AgnosticDataRouteObject[],
    4565   manifest: RouteManifest,
    4566   mapRouteProperties: MapRoutePropertiesFunction,
    4567   pendingRouteChildren: Map<string, ReturnType<typeof patchRoutesOnMissImpl>>,
    4568   signal: AbortSignal
    4569 ) {
    4570   let key = [path, ...matches.map((m) => m.route.id)].join("-");
    4571   try {
    4572     let pending = pendingRouteChildren.get(key);
    4573     if (!pending) {
    4574       pending = patchRoutesOnMissImpl({
    4575         path,
    4576         matches,
    4577         patch: (routeId, children) => {
    4578           if (!signal.aborted) {
    4579             patchRoutesImpl(
    4580               routeId,
    4581               children,
    4582               routes,
    4583               manifest,
    4584               mapRouteProperties
    4585             );
    4586           }
    4587         },
    4588       });
    4589       pendingRouteChildren.set(key, pending);
    4590     }
    4591 
    4592     if (pending && isPromise<AgnosticRouteObject[]>(pending)) {
    4593       await pending;
    4594     }
    4595   } finally {
    4596     pendingRouteChildren.delete(key);
    4597   }
    4598 }
    4599 
    46004607function patchRoutesImpl(
    46014608  routeId: string | null,
     
    46054612  mapRouteProperties: MapRoutePropertiesFunction
    46064613) {
     4614  let childrenToPatch: AgnosticDataRouteObject[];
    46074615  if (routeId) {
    46084616    let route = manifest[routeId];
     
    46114619      `No route found to patch children into: routeId = ${routeId}`
    46124620    );
    4613     let dataChildren = convertRoutesToDataRoutes(
    4614       children,
    4615       mapRouteProperties,
    4616       [routeId, "patch", String(route.children?.length || "0")],
    4617       manifest
    4618     );
    4619     if (route.children) {
    4620       route.children.push(...dataChildren);
    4621     } else {
    4622       route.children = dataChildren;
    4623     }
     4621    if (!route.children) {
     4622      route.children = [];
     4623    }
     4624    childrenToPatch = route.children;
    46244625  } else {
    4625     let dataChildren = convertRoutesToDataRoutes(
    4626       children,
    4627       mapRouteProperties,
    4628       ["patch", String(routesToUse.length || "0")],
    4629       manifest
    4630     );
    4631     routesToUse.push(...dataChildren);
    4632   }
     4626    childrenToPatch = routesToUse;
     4627  }
     4628
     4629  // Don't patch in routes we already know about so that `patch` is idempotent
     4630  // to simplify user-land code. This is useful because we re-call the
     4631  // `patchRoutesOnNavigation` function for matched routes with params.
     4632  let uniqueChildren = children.filter(
     4633    (newRoute) =>
     4634      !childrenToPatch.some((existingRoute) =>
     4635        isSameRoute(newRoute, existingRoute)
     4636      )
     4637  );
     4638
     4639  let newRoutes = convertRoutesToDataRoutes(
     4640    uniqueChildren,
     4641    mapRouteProperties,
     4642    [routeId || "_", "patch", String(childrenToPatch?.length || "0")],
     4643    manifest
     4644  );
     4645
     4646  childrenToPatch.push(...newRoutes);
     4647}
     4648
     4649function isSameRoute(
     4650  newRoute: AgnosticRouteObject,
     4651  existingRoute: AgnosticRouteObject
     4652): boolean {
     4653  // Most optimal check is by id
     4654  if (
     4655    "id" in newRoute &&
     4656    "id" in existingRoute &&
     4657    newRoute.id === existingRoute.id
     4658  ) {
     4659    return true;
     4660  }
     4661
     4662  // Second is by pathing differences
     4663  if (
     4664    !(
     4665      newRoute.index === existingRoute.index &&
     4666      newRoute.path === existingRoute.path &&
     4667      newRoute.caseSensitive === existingRoute.caseSensitive
     4668    )
     4669  ) {
     4670    return false;
     4671  }
     4672
     4673  // Pathless layout routes are trickier since we need to check children.
     4674  // If they have no children then they're the same as far as we can tell
     4675  if (
     4676    (!newRoute.children || newRoute.children.length === 0) &&
     4677    (!existingRoute.children || existingRoute.children.length === 0)
     4678  ) {
     4679    return true;
     4680  }
     4681
     4682  // Otherwise, we look to see if every child in the new route is already
     4683  // represented in the existing route's children
     4684  return newRoute.children!.every((aChild, i) =>
     4685    existingRoute.children?.some((bChild) => isSameRoute(aChild, bChild))
     4686  );
    46334687}
    46344688
     
    47124766
    47134767// Default implementation of `dataStrategy` which fetches all loaders in parallel
    4714 function defaultDataStrategy(
    4715   opts: DataStrategyFunctionArgs
    4716 ): ReturnType<DataStrategyFunction> {
    4717   return Promise.all(opts.matches.map((m) => m.resolve()));
     4768async function defaultDataStrategy({
     4769  matches,
     4770}: DataStrategyFunctionArgs): ReturnType<DataStrategyFunction> {
     4771  let matchesToLoad = matches.filter((m) => m.shouldLoad);
     4772  let results = await Promise.all(matchesToLoad.map((m) => m.resolve()));
     4773  return results.reduce(
     4774    (acc, result, i) =>
     4775      Object.assign(acc, { [matchesToLoad[i].route.id]: result }),
     4776    {}
     4777  );
    47184778}
    47194779
     
    47214781  dataStrategyImpl: DataStrategyFunction,
    47224782  type: "loader" | "action",
     4783  state: RouterState | null,
    47234784  request: Request,
    47244785  matchesToLoad: AgnosticDataRouteMatch[],
    47254786  matches: AgnosticDataRouteMatch[],
     4787  fetcherKey: string | null,
    47264788  manifest: RouteManifest,
    47274789  mapRouteProperties: MapRoutePropertiesFunction,
    47284790  requestContext?: unknown
    4729 ): Promise<HandlerResult[]> {
    4730   let routeIdsToLoad = matchesToLoad.reduce(
    4731     (acc, m) => acc.add(m.route.id),
    4732     new Set<string>()
     4791): Promise<Record<string, DataStrategyResult>> {
     4792  let loadRouteDefinitionsPromises = matches.map((m) =>
     4793    m.route.lazy
     4794      ? loadLazyRouteModule(m.route, mapRouteProperties, manifest)
     4795      : undefined
    47334796  );
    4734   let loadedMatches = new Set<string>();
     4797
     4798  let dsMatches = matches.map((match, i) => {
     4799    let loadRoutePromise = loadRouteDefinitionsPromises[i];
     4800    let shouldLoad = matchesToLoad.some((m) => m.route.id === match.route.id);
     4801    // `resolve` encapsulates route.lazy(), executing the loader/action,
     4802    // and mapping return values/thrown errors to a `DataStrategyResult`.  Users
     4803    // can pass a callback to take fine-grained control over the execution
     4804    // of the loader/action
     4805    let resolve: DataStrategyMatch["resolve"] = async (handlerOverride) => {
     4806      if (
     4807        handlerOverride &&
     4808        request.method === "GET" &&
     4809        (match.route.lazy || match.route.loader)
     4810      ) {
     4811        shouldLoad = true;
     4812      }
     4813      return shouldLoad
     4814        ? callLoaderOrAction(
     4815            type,
     4816            request,
     4817            match,
     4818            loadRoutePromise,
     4819            handlerOverride,
     4820            requestContext
     4821          )
     4822        : Promise.resolve({ type: ResultType.data, result: undefined });
     4823    };
     4824
     4825    return {
     4826      ...match,
     4827      shouldLoad,
     4828      resolve,
     4829    };
     4830  });
    47354831
    47364832  // Send all matches here to allow for a middleware-type implementation.
     
    47384834  // back out below.
    47394835  let results = await dataStrategyImpl({
    4740     matches: matches.map((match) => {
    4741       let shouldLoad = routeIdsToLoad.has(match.route.id);
    4742       // `resolve` encapsulates the route.lazy, executing the
    4743       // loader/action, and mapping return values/thrown errors to a
    4744       // HandlerResult.  Users can pass a callback to take fine-grained control
    4745       // over the execution of the loader/action
    4746       let resolve: DataStrategyMatch["resolve"] = (handlerOverride) => {
    4747         loadedMatches.add(match.route.id);
    4748         return shouldLoad
    4749           ? callLoaderOrAction(
    4750               type,
    4751               request,
    4752               match,
    4753               manifest,
    4754               mapRouteProperties,
    4755               handlerOverride,
    4756               requestContext
    4757             )
    4758           : Promise.resolve({ type: ResultType.data, result: undefined });
    4759       };
    4760 
    4761       return {
    4762         ...match,
    4763         shouldLoad,
    4764         resolve,
    4765       };
    4766     }),
     4836    matches: dsMatches,
    47674837    request,
    47684838    params: matches[0].params,
     4839    fetcherKey,
    47694840    context: requestContext,
    47704841  });
    47714842
    4772   // Throw if any loadRoute implementations not called since they are what
    4773   // ensures a route is fully loaded
    4774   matches.forEach((m) =>
    4775     invariant(
    4776       loadedMatches.has(m.route.id),
    4777       `\`match.resolve()\` was not called for route id "${m.route.id}". ` +
    4778         "You must call `match.resolve()` on every match passed to " +
    4779         "`dataStrategy` to ensure all routes are properly loaded."
    4780     )
    4781   );
    4782 
    4783   // Filter out any middleware-only matches for which we didn't need to run handlers
    4784   return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
     4843  // Wait for all routes to load here but 'swallow the error since we want
     4844  // it to bubble up from the `await loadRoutePromise` in `callLoaderOrAction` -
     4845  // called from `match.resolve()`
     4846  try {
     4847    await Promise.all(loadRouteDefinitionsPromises);
     4848  } catch (e) {
     4849    // No-op
     4850  }
     4851
     4852  return results;
    47854853}
    47864854
     
    47904858  request: Request,
    47914859  match: AgnosticDataRouteMatch,
    4792   manifest: RouteManifest,
    4793   mapRouteProperties: MapRoutePropertiesFunction,
     4860  loadRoutePromise: Promise<void> | undefined,
    47944861  handlerOverride: Parameters<DataStrategyMatch["resolve"]>[0],
    47954862  staticContext?: unknown
    4796 ): Promise<HandlerResult> {
    4797   let result: HandlerResult;
     4863): Promise<DataStrategyResult> {
     4864  let result: DataStrategyResult;
    47984865  let onReject: (() => void) | undefined;
    47994866
    48004867  let runHandler = (
    48014868    handler: AgnosticRouteObject["loader"] | AgnosticRouteObject["action"]
    4802   ): Promise<HandlerResult> => {
     4869  ): Promise<DataStrategyResult> => {
    48034870    // Setup a promise we can race against so that abort signals short circuit
    48044871    let reject: () => void;
    4805     // This will never resolve so safe to type it as Promise<HandlerResult> to
     4872    // This will never resolve so safe to type it as Promise<DataStrategyResult> to
    48064873    // satisfy the function return value
    4807     let abortPromise = new Promise<HandlerResult>((_, r) => (reject = r));
     4874    let abortPromise = new Promise<DataStrategyResult>((_, r) => (reject = r));
    48084875    onReject = () => reject();
    48094876    request.signal.addEventListener("abort", onReject);
     
    48284895    };
    48294896
    4830     let handlerPromise: Promise<HandlerResult>;
    4831     if (handlerOverride) {
    4832       handlerPromise = handlerOverride((ctx: unknown) => actualHandler(ctx));
    4833     } else {
    4834       handlerPromise = (async () => {
    4835         try {
    4836           let val = await actualHandler();
    4837           return { type: "data", result: val };
    4838         } catch (e) {
    4839           return { type: "error", result: e };
    4840         }
    4841       })();
    4842     }
     4897    let handlerPromise: Promise<DataStrategyResult> = (async () => {
     4898      try {
     4899        let val = await (handlerOverride
     4900          ? handlerOverride((ctx: unknown) => actualHandler(ctx))
     4901          : actualHandler());
     4902        return { type: "data", result: val };
     4903      } catch (e) {
     4904        return { type: "error", result: e };
     4905      }
     4906    })();
    48434907
    48444908    return Promise.race([handlerPromise, abortPromise]);
     
    48484912    let handler = match.route[type];
    48494913
    4850     if (match.route.lazy) {
     4914    // If we have a route.lazy promise, await that first
     4915    if (loadRoutePromise) {
    48514916      if (handler) {
    48524917        // Run statically defined handler in parallel with lazy()
     
    48594924            handlerError = e;
    48604925          }),
    4861           loadLazyRouteModule(match.route, mapRouteProperties, manifest),
     4926          loadRoutePromise,
    48624927        ]);
    48634928        if (handlerError !== undefined) {
     
    48674932      } else {
    48684933        // Load lazy route module, then run any returned handler
    4869         await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
     4934        await loadRoutePromise;
    48704935
    48714936        handler = match.route[type];
     
    49074972  } catch (e) {
    49084973    // We should already be catching and converting normal handler executions to
    4909     // HandlerResults and returning them, so anything that throws here is an
     4974    // DataStrategyResults and returning them, so anything that throws here is an
    49104975    // unexpected error we still need to wrap
    49114976    return { type: ResultType.error, result: e };
     
    49194984}
    49204985
    4921 async function convertHandlerResultToDataResult(
    4922   handlerResult: HandlerResult
     4986async function convertDataStrategyResultToDataResult(
     4987  dataStrategyResult: DataStrategyResult
    49234988): Promise<DataResult> {
    4924   let { result, type } = handlerResult;
     4989  let { result, type } = dataStrategyResult;
    49254990
    49264991  if (isResponse(result)) {
     
    49715036      }
    49725037
    4973       // Convert thrown unstable_data() to ErrorResponse instances
     5038      // Convert thrown data() to ErrorResponse instances
    49745039      result = new ErrorResponseImpl(
    49755040        result.init?.status || 500,
     
    51245189function processRouteLoaderData(
    51255190  matches: AgnosticDataRouteMatch[],
    5126   matchesToLoad: AgnosticDataRouteMatch[],
    5127   results: DataResult[],
     5191  results: Record<string, DataResult>,
    51285192  pendingActionResult: PendingActionResult | undefined,
    51295193  activeDeferreds: Map<string, DeferredData>,
     
    51475211
    51485212  // Process loader results into state.loaderData/state.errors
    5149   results.forEach((result, index) => {
    5150     let id = matchesToLoad[index].route.id;
     5213  matches.forEach((match) => {
     5214    if (!(match.route.id in results)) {
     5215      return;
     5216    }
     5217    let id = match.route.id;
     5218    let result = results[id];
    51515219    invariant(
    51525220      !isRedirectResult(result),
     
    52405308  state: RouterState,
    52415309  matches: AgnosticDataRouteMatch[],
    5242   matchesToLoad: AgnosticDataRouteMatch[],
    5243   results: DataResult[],
     5310  results: Record<string, DataResult>,
    52445311  pendingActionResult: PendingActionResult | undefined,
    52455312  revalidatingFetchers: RevalidatingFetcher[],
    5246   fetcherResults: DataResult[],
     5313  fetcherResults: Record<string, DataResult>,
    52475314  activeDeferreds: Map<string, DeferredData>
    52485315): {
     
    52525319  let { loaderData, errors } = processRouteLoaderData(
    52535320    matches,
    5254     matchesToLoad,
    52555321    results,
    52565322    pendingActionResult,
     
    52605326
    52615327  // Process results from our revalidating fetchers
    5262   for (let index = 0; index < revalidatingFetchers.length; index++) {
    5263     let { key, match, controller } = revalidatingFetchers[index];
    5264     invariant(
    5265       fetcherResults !== undefined && fetcherResults[index] !== undefined,
    5266       "Did not find corresponding fetcher result"
    5267     );
    5268     let result = fetcherResults[index];
     5328  revalidatingFetchers.forEach((rf) => {
     5329    let { key, match, controller } = rf;
     5330    let result = fetcherResults[key];
     5331    invariant(result, "Did not find corresponding fetcher result");
    52695332
    52705333    // Process fetcher non-redirect errors
    52715334    if (controller && controller.signal.aborted) {
    52725335      // Nothing to do for aborted fetchers
    5273       continue;
     5336      return;
    52745337    } else if (isErrorResult(result)) {
    52755338      let boundaryMatch = findNearestBoundary(state.matches, match?.route.id);
     
    52935356      state.fetchers.set(key, doneFetcher);
    52945357    }
    5295   }
     5358  });
    52965359
    52975360  return { loaderData, errors };
     
    54005463    routeId?: string;
    54015464    method?: string;
    5402     type?: "defer-action" | "invalid-body" | "route-discovery";
     5465    type?: "defer-action" | "invalid-body";
    54035466    message?: string;
    54045467  } = {}
     
    54095472  if (status === 400) {
    54105473    statusText = "Bad Request";
    5411     if (type === "route-discovery") {
    5412       errorMessage =
    5413         `Unable to match URL "${pathname}" - the \`unstable_patchRoutesOnMiss()\` ` +
    5414         `function threw the following error:\n${message}`;
    5415     } else if (method && pathname && routeId) {
     5474    if (method && pathname && routeId) {
    54165475      errorMessage =
    54175476        `You made a ${method} request to "${pathname}" but ` +
     
    54515510// Find any returned redirect errors, starting from the lowest match
    54525511function findRedirect(
    5453   results: DataResult[]
    5454 ): { result: RedirectResult; idx: number } | undefined {
    5455   for (let i = results.length - 1; i >= 0; i--) {
    5456     let result = results[i];
     5512  results: Record<string, DataResult>
     5513): { key: string; result: RedirectResult } | undefined {
     5514  let entries = Object.entries(results);
     5515  for (let i = entries.length - 1; i >= 0; i--) {
     5516    let [key, result] = entries[i];
    54575517    if (isRedirectResult(result)) {
    5458       return { result, idx: i };
     5518      return { key, result };
    54595519    }
    54605520  }
     
    54915551}
    54925552
    5493 function isHandlerResult(result: unknown): result is HandlerResult {
     5553function isDataStrategyResult(result: unknown): result is DataStrategyResult {
    54945554  return (
    54955555    result != null &&
     
    55015561}
    55025562
    5503 function isRedirectHandlerResult(result: HandlerResult) {
     5563function isRedirectDataStrategyResultResult(result: DataStrategyResult) {
    55045564  return (
    55055565    isResponse(result.result) && redirectStatusCodes.has(result.result.status)
     
    55745634}
    55755635
    5576 async function resolveDeferredResults(
     5636async function resolveNavigationDeferredResults(
     5637  matches: (AgnosticDataRouteMatch | null)[],
     5638  results: Record<string, DataResult>,
     5639  signal: AbortSignal,
    55775640  currentMatches: AgnosticDataRouteMatch[],
    5578   matchesToLoad: (AgnosticDataRouteMatch | null)[],
    5579   results: DataResult[],
    5580   signals: (AbortSignal | null)[],
    5581   isFetcher: boolean,
    5582   currentLoaderData?: RouteData
     5641  currentLoaderData: RouteData
    55835642) {
    5584   for (let index = 0; index < results.length; index++) {
    5585     let result = results[index];
    5586     let match = matchesToLoad[index];
     5643  let entries = Object.entries(results);
     5644  for (let index = 0; index < entries.length; index++) {
     5645    let [routeId, result] = entries[index];
     5646    let match = matches.find((m) => m?.route.id === routeId);
    55875647    // If we don't have a match, then we can have a deferred result to do
    55885648    // anything with.  This is for revalidating fetchers where the route was
     
    56005660      (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
    56015661
    5602     if (isDeferredResult(result) && (isFetcher || isRevalidatingLoader)) {
     5662    if (isDeferredResult(result) && isRevalidatingLoader) {
    56035663      // Note: we do not have to touch activeDeferreds here since we race them
    56045664      // against the signal in resolveDeferredData and they'll get aborted
    56055665      // there if needed
    5606       let signal = signals[index];
    5607       invariant(
    5608         signal,
    5609         "Expected an AbortSignal for revalidating fetcher deferred result"
    5610       );
    5611       await resolveDeferredData(result, signal, isFetcher).then((result) => {
     5666      await resolveDeferredData(result, signal, false).then((result) => {
    56125667        if (result) {
    5613           results[index] = result || results[index];
     5668          results[routeId] = result;
    56145669        }
    56155670      });
     5671    }
     5672  }
     5673}
     5674
     5675async function resolveFetcherDeferredResults(
     5676  matches: (AgnosticDataRouteMatch | null)[],
     5677  results: Record<string, DataResult>,
     5678  revalidatingFetchers: RevalidatingFetcher[]
     5679) {
     5680  for (let index = 0; index < revalidatingFetchers.length; index++) {
     5681    let { key, routeId, controller } = revalidatingFetchers[index];
     5682    let result = results[key];
     5683    let match = matches.find((m) => m?.route.id === routeId);
     5684    // If we don't have a match, then we can have a deferred result to do
     5685    // anything with.  This is for revalidating fetchers where the route was
     5686    // removed during HMR
     5687    if (!match) {
     5688      continue;
     5689    }
     5690
     5691    if (isDeferredResult(result)) {
     5692      // Note: we do not have to touch activeDeferreds here since we race them
     5693      // against the signal in resolveDeferredData and they'll get aborted
     5694      // there if needed
     5695      invariant(
     5696        controller,
     5697        "Expected an AbortController for revalidating fetcher deferred result"
     5698      );
     5699      await resolveDeferredData(result, controller.signal, true).then(
     5700        (result) => {
     5701          if (result) {
     5702            results[key] = result;
     5703          }
     5704        }
     5705      );
    56165706    }
    56175707  }
Note: See TracChangeset for help on using the changeset viewer.