@remix-run/router 0.0.0-experimental-e960cf1a → 0.0.0-experimental-a0888892

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/router.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v0.0.0-experimental-e960cf1a
2
+ * @remix-run/router v0.0.0-experimental-a0888892
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -417,6 +417,10 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
417
417
  // See https://bugzilla.mozilla.org/show_bug.cgi?id=878297
418
418
  let base = window.location.origin !== "null" ? window.location.origin : window.location.href;
419
419
  let href = typeof to === "string" ? to : createPath(to);
420
+ // Treating this as a full URL will strip any trailing spaces so we need to
421
+ // pre-encode them since they might be part of a matching splat param from
422
+ // an ancestor route
423
+ href = href.replace(/ $/, "%20");
420
424
  invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
421
425
  return new URL(href, base);
422
426
  }
@@ -523,14 +527,14 @@ function matchRoutes(routes, locationArg, basename) {
523
527
  rankRouteBranches(branches);
524
528
  let matches = null;
525
529
  for (let i = 0; matches == null && i < branches.length; ++i) {
526
- matches = matchRouteBranch(branches[i],
527
530
  // Incoming pathnames are generally encoded from either window.location
528
531
  // or from router.navigate, but we want to match against the unencoded
529
532
  // paths in the route definitions. Memory router locations won't be
530
533
  // encoded here but there also shouldn't be anything to decode so this
531
534
  // should be a safe operation. This avoids needing matchRoutes to be
532
535
  // history-aware.
533
- safelyDecodeURI(pathname));
536
+ let decoded = decodePath(pathname);
537
+ matches = matchRouteBranch(branches[i], decoded);
534
538
  }
535
539
  return matches;
536
540
  }
@@ -653,7 +657,7 @@ function rankRouteBranches(branches) {
653
657
  branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
654
658
  : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
655
659
  }
656
- const paramRe = /^:\w+$/;
660
+ const paramRe = /^:[\w-]+$/;
657
661
  const dynamicSegmentValue = 3;
658
662
  const indexRouteValue = 2;
659
663
  const emptySegmentValue = 1;
@@ -740,7 +744,7 @@ function generatePath(originalPath, params) {
740
744
  // Apply the splat
741
745
  return stringify(params[star]);
742
746
  }
743
- const keyMatch = segment.match(/^:(\w+)(\??)$/);
747
+ const keyMatch = segment.match(/^:([\w-]+)(\??)$/);
744
748
  if (keyMatch) {
745
749
  const [, key, optional] = keyMatch;
746
750
  let param = params[key];
@@ -789,7 +793,7 @@ function matchPath(pattern, pathname) {
789
793
  if (isOptional && !value) {
790
794
  memo[paramName] = undefined;
791
795
  } else {
792
- memo[paramName] = safelyDecodeURIComponent(value || "", paramName);
796
+ memo[paramName] = (value || "").replace(/%2F/g, "/");
793
797
  }
794
798
  return memo;
795
799
  }, {});
@@ -812,7 +816,7 @@ function compilePath(path, caseSensitive, end) {
812
816
  let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
813
817
  .replace(/^\/*/, "/") // Make sure it has a leading /
814
818
  .replace(/[\\.*+^${}|()[\]]/g, "\\$&") // Escape special regex chars
815
- .replace(/\/:(\w+)(\?)?/g, (_, paramName, isOptional) => {
819
+ .replace(/\/:([\w-]+)(\?)?/g, (_, paramName, isOptional) => {
816
820
  params.push({
817
821
  paramName,
818
822
  isOptional: isOptional != null
@@ -841,22 +845,14 @@ function compilePath(path, caseSensitive, end) {
841
845
  let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
842
846
  return [matcher, params];
843
847
  }
844
- function safelyDecodeURI(value) {
848
+ function decodePath(value) {
845
849
  try {
846
- return decodeURI(value);
850
+ return value.split("/").map(v => decodeURIComponent(v).replace(/\//g, "%2F")).join("/");
847
851
  } catch (error) {
848
852
  warning(false, "The URL path \"" + value + "\" could not be decoded because it is is a " + "malformed URL segment. This is probably due to a bad percent " + ("encoding (" + error + ")."));
849
853
  return value;
850
854
  }
851
855
  }
852
- function safelyDecodeURIComponent(value, paramName) {
853
- try {
854
- return decodeURIComponent(value);
855
- } catch (error) {
856
- warning(false, "The value for the URL param \"" + paramName + "\" will not be decoded because" + (" the string \"" + value + "\" is a malformed URL segment. This is probably") + (" due to a bad percent encoding (" + error + ")."));
857
- return value;
858
- }
859
- }
860
856
  /**
861
857
  * @private
862
858
  */
@@ -1306,8 +1302,6 @@ function createRouter(init) {
1306
1302
  const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
1307
1303
  const isServer = !isBrowser;
1308
1304
  invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
1309
- const dataStrategy = init.unstable_dataStrategy || defaultDataStrategy;
1310
- const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
1311
1305
  let mapRouteProperties;
1312
1306
  if (init.mapRouteProperties) {
1313
1307
  mapRouteProperties = init.mapRouteProperties;
@@ -1326,6 +1320,7 @@ function createRouter(init) {
1326
1320
  let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
1327
1321
  let inFlightDataRoutes;
1328
1322
  let basename = init.basename || "/";
1323
+ let dataStrategyImpl = init.unstable_dataStrategy || defaultDataStrategy;
1329
1324
  // Config driven behavior flags
1330
1325
  let future = _extends({
1331
1326
  v7_fetcherPersist: false,
@@ -1368,17 +1363,28 @@ function createRouter(init) {
1368
1363
  [route.id]: error
1369
1364
  };
1370
1365
  }
1371
- // "Initialized" here really means "Can `RouterProvider` render my route tree?"
1372
- // Prior to `route.HydrateFallback`, we only had a root `fallbackElement` so we used
1373
- // `state.initialized` to render that instead of `<DataRoutes>`. Now that we
1374
- // support route level fallbacks we can always render and we'll just render
1375
- // as deep as we have data for and detect the nearest ancestor HydrateFallback
1376
- let initialized = future.v7_partialHydration ||
1377
- // All initialMatches need to be loaded before we're ready. If we have lazy
1378
- // functions around still then we'll need to run them in initialize()
1379
- !initialMatches.some(m => m.route.lazy) && (
1380
- // And we have to either have no loaders or have been provided hydrationData
1381
- !initialMatches.some(m => m.route.loader) || init.hydrationData != null);
1366
+ let initialized;
1367
+ let hasLazyRoutes = initialMatches.some(m => m.route.lazy);
1368
+ let hasLoaders = initialMatches.some(m => m.route.loader);
1369
+ if (hasLazyRoutes) {
1370
+ // All initialMatches need to be loaded before we're ready. If we have lazy
1371
+ // functions around still then we'll need to run them in initialize()
1372
+ initialized = false;
1373
+ } else if (!hasLoaders) {
1374
+ // If we've got no loaders to run, then we're good to go
1375
+ initialized = true;
1376
+ } else if (future.v7_partialHydration) {
1377
+ // If partial hydration is enabled, we're initialized so long as we were
1378
+ // provided with hydrationData for every route with a loader, and no loaders
1379
+ // were marked for explicit hydration
1380
+ let loaderData = init.hydrationData ? init.hydrationData.loaderData : null;
1381
+ let errors = init.hydrationData ? init.hydrationData.errors : null;
1382
+ initialized = initialMatches.every(m => m.route.loader && (typeof m.route.loader !== "function" || m.route.loader.hydrate !== true) && (loaderData && loaderData[m.route.id] !== undefined || errors && errors[m.route.id] !== undefined));
1383
+ } else {
1384
+ // Without partial hydration - we're initialized if we were provided any
1385
+ // hydrationData - which is expected to be complete
1386
+ initialized = init.hydrationData != null;
1387
+ }
1382
1388
  let router;
1383
1389
  let state = {
1384
1390
  historyAction: init.history.action,
@@ -1521,7 +1527,7 @@ function createRouter(init) {
1521
1527
  // in the normal navigation flow. For SSR it's expected that lazy modules are
1522
1528
  // resolved prior to router creation since we can't go into a fallbackElement
1523
1529
  // UI for SSR'd apps
1524
- if (!state.initialized || future.v7_partialHydration && state.matches.some(m => isUnhydratedRoute(state, m.route))) {
1530
+ if (!state.initialized) {
1525
1531
  startNavigation(Action.Pop, state.location, {
1526
1532
  initialHydration: true
1527
1533
  });
@@ -1932,7 +1938,8 @@ function createRouter(init) {
1932
1938
  })
1933
1939
  };
1934
1940
  } else {
1935
- result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
1941
+ let results = await callDataStrategy("action", request, [actionMatch], matches);
1942
+ result = results[0];
1936
1943
  if (request.signal.aborted) {
1937
1944
  return {
1938
1945
  shortCircuited: true
@@ -1947,9 +1954,9 @@ function createRouter(init) {
1947
1954
  // If the user didn't explicity indicate replace behavior, replace if
1948
1955
  // we redirected to the exact same location we're currently at to avoid
1949
1956
  // double back-buttons
1950
- replace = result.location === state.location.pathname + state.location.search;
1957
+ replace = result.response.headers.get("Location") === state.location.pathname + state.location.search;
1951
1958
  }
1952
- await startRedirectNavigation(state, result, {
1959
+ await startRedirectNavigation(request, result, {
1953
1960
  submission,
1954
1961
  replace
1955
1962
  });
@@ -2065,7 +2072,7 @@ function createRouter(init) {
2065
2072
  let {
2066
2073
  loaderResults,
2067
2074
  fetcherResults
2068
- } = await loadDataAndMaybeResolveDeferred(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
2075
+ } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
2069
2076
  if (request.signal.aborted) {
2070
2077
  return {
2071
2078
  shortCircuited: true
@@ -2088,7 +2095,7 @@ function createRouter(init) {
2088
2095
  let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
2089
2096
  fetchRedirectIds.add(fetcherKey);
2090
2097
  }
2091
- await startRedirectNavigation(state, redirect.result, {
2098
+ await startRedirectNavigation(request, redirect.result, {
2092
2099
  replace
2093
2100
  });
2094
2101
  return {
@@ -2190,7 +2197,8 @@ function createRouter(init) {
2190
2197
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
2191
2198
  fetchControllers.set(key, abortController);
2192
2199
  let originatingLoadId = incrementingLoadId;
2193
- let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2200
+ let actionResults = await callDataStrategy("action", fetchRequest, [match], requestMatches);
2201
+ let actionResult = actionResults[0];
2194
2202
  if (fetchRequest.signal.aborted) {
2195
2203
  // We can delete this so long as we weren't aborted by our own fetcher
2196
2204
  // re-submit which would have put _new_ controller is in fetchControllers
@@ -2221,7 +2229,7 @@ function createRouter(init) {
2221
2229
  } else {
2222
2230
  fetchRedirectIds.add(key);
2223
2231
  updateFetcherState(key, getLoadingFetcher(submission));
2224
- return startRedirectNavigation(state, actionResult, {
2232
+ return startRedirectNavigation(fetchRequest, actionResult, {
2225
2233
  fetcherSubmission: submission
2226
2234
  });
2227
2235
  }
@@ -2275,7 +2283,7 @@ function createRouter(init) {
2275
2283
  let {
2276
2284
  loaderResults,
2277
2285
  fetcherResults
2278
- } = await loadDataAndMaybeResolveDeferred(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
2286
+ } = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
2279
2287
  if (abortController.signal.aborted) {
2280
2288
  return;
2281
2289
  }
@@ -2292,7 +2300,7 @@ function createRouter(init) {
2292
2300
  let fetcherKey = revalidatingFetchers[redirect.idx - matchesToLoad.length].key;
2293
2301
  fetchRedirectIds.add(fetcherKey);
2294
2302
  }
2295
- return startRedirectNavigation(state, redirect.result);
2303
+ return startRedirectNavigation(revalidationRequest, redirect.result);
2296
2304
  }
2297
2305
  // Process and commit output from loaders
2298
2306
  let {
@@ -2341,7 +2349,8 @@ function createRouter(init) {
2341
2349
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
2342
2350
  fetchControllers.set(key, abortController);
2343
2351
  let originatingLoadId = incrementingLoadId;
2344
- let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath);
2352
+ let results = await callDataStrategy("loader", fetchRequest, [match], matches);
2353
+ let result = results[0];
2345
2354
  // Deferred isn't supported for fetcher loads, await everything and treat it
2346
2355
  // as a normal load. resolveDeferredData will return undefined if this
2347
2356
  // fetcher gets aborted, so we just leave result untouched and short circuit
@@ -2372,7 +2381,7 @@ function createRouter(init) {
2372
2381
  return;
2373
2382
  } else {
2374
2383
  fetchRedirectIds.add(key);
2375
- await startRedirectNavigation(state, result);
2384
+ await startRedirectNavigation(fetchRequest, result);
2376
2385
  return;
2377
2386
  }
2378
2387
  }
@@ -2404,26 +2413,40 @@ function createRouter(init) {
2404
2413
  * actually touch history until we've processed redirects, so we just use
2405
2414
  * the history action from the original navigation (PUSH or REPLACE).
2406
2415
  */
2407
- async function startRedirectNavigation(state, redirect, _temp2) {
2416
+ async function startRedirectNavigation(request, redirect, _temp2) {
2408
2417
  let {
2409
2418
  submission,
2410
2419
  fetcherSubmission,
2411
2420
  replace
2412
2421
  } = _temp2 === void 0 ? {} : _temp2;
2413
- if (redirect.revalidate) {
2422
+ if (redirect.response.headers.has("X-Remix-Revalidate")) {
2414
2423
  isRevalidationRequired = true;
2415
2424
  }
2416
- let redirectLocation = createLocation(state.location, redirect.location, {
2425
+ let location = redirect.response.headers.get("Location");
2426
+ invariant(location, "Expected a Location header on the redirect Response");
2427
+ let redirectLocation = createLocation(state.location, location, {
2417
2428
  _isRedirect: true
2418
2429
  });
2419
- invariant(redirectLocation, "Expected a location on the redirect navigation");
2430
+ if (ABSOLUTE_URL_REGEX.test(location)) {
2431
+ // Strip off the protocol+origin for same-origin + same-basename absolute redirects
2432
+ let normalizedLocation = location;
2433
+ let currentUrl = new URL(request.url);
2434
+ let url = normalizedLocation.startsWith("//") ? new URL(currentUrl.protocol + normalizedLocation) : new URL(normalizedLocation);
2435
+ let isSameBasename = stripBasename(url.pathname, basename) != null;
2436
+ if (url.origin === currentUrl.origin && isSameBasename) {
2437
+ normalizedLocation = url.pathname + url.search + url.hash;
2438
+ redirectLocation = createLocation(state.location, normalizedLocation, {
2439
+ _isRedirect: true
2440
+ });
2441
+ }
2442
+ }
2420
2443
  if (isBrowser) {
2421
2444
  let isDocumentReload = false;
2422
- if (redirect.reloadDocument) {
2445
+ if (redirect.response.headers.has("X-Remix-Reload-Document")) {
2423
2446
  // Hard reload if the response contained X-Remix-Reload-Document
2424
2447
  isDocumentReload = true;
2425
- } else if (ABSOLUTE_URL_REGEX.test(redirect.location)) {
2426
- const url = init.history.createURL(redirect.location);
2448
+ } else if (ABSOLUTE_URL_REGEX.test(location)) {
2449
+ const url = init.history.createURL(location);
2427
2450
  isDocumentReload =
2428
2451
  // Hard reload if it's an absolute URL to a new origin
2429
2452
  url.origin !== routerWindow.location.origin ||
@@ -2432,9 +2455,9 @@ function createRouter(init) {
2432
2455
  }
2433
2456
  if (isDocumentReload) {
2434
2457
  if (replace) {
2435
- routerWindow.location.replace(redirect.location);
2458
+ routerWindow.location.replace(location);
2436
2459
  } else {
2437
- routerWindow.location.assign(redirect.location);
2460
+ routerWindow.location.assign(location);
2438
2461
  }
2439
2462
  return;
2440
2463
  }
@@ -2457,10 +2480,10 @@ function createRouter(init) {
2457
2480
  // re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
2458
2481
  // redirected location
2459
2482
  let activeSubmission = submission || fetcherSubmission;
2460
- if (redirectPreserveMethodStatusCodes.has(redirect.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
2483
+ if (redirectPreserveMethodStatusCodes.has(redirect.response.status) && activeSubmission && isMutationMethod(activeSubmission.formMethod)) {
2461
2484
  await startNavigation(redirectHistoryAction, redirectLocation, {
2462
2485
  submission: _extends({}, activeSubmission, {
2463
- formAction: redirect.location
2486
+ formAction: location
2464
2487
  }),
2465
2488
  // Preserve this flag across redirects
2466
2489
  preventScrollReset: pendingPreventScrollReset
@@ -2478,16 +2501,35 @@ function createRouter(init) {
2478
2501
  });
2479
2502
  }
2480
2503
  }
2481
- async function loadDataAndMaybeResolveDeferred(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
2482
- let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? dataStrategy({
2483
- matches: matchesToLoad.map(m => finesseToAgnosticDataStrategyMatch(m, mapRouteProperties, manifest)),
2484
- request,
2485
- type: "loader",
2486
- defaultStrategy(match) {
2487
- return callLoaderOrActionImplementation("loader", request, match, matches, basename, future.v7_relativeSplatPath);
2488
- }
2489
- }) : [], ...fetchersToLoad.map(f => {
2490
- if (!f.matches || !f.match || !f.controller) {
2504
+ // Utility wrapper for calling dataStrategy client-side without having to
2505
+ // pass around the manifest, mapRouteProperties, etc.
2506
+ async function callDataStrategy(type, request, matchesToLoad, matches) {
2507
+ try {
2508
+ let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties);
2509
+ return await Promise.all(results.map((result, i) => {
2510
+ if (isRedirectHandlerResult(result)) {
2511
+ return {
2512
+ type: ResultType.redirect,
2513
+ response: normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath)
2514
+ };
2515
+ }
2516
+ return convertHandlerResultToDataResult(result);
2517
+ }));
2518
+ } catch (e) {
2519
+ // If the outer dataStrategy method throws, just return the error for all
2520
+ // matches - and it'll naturally bubble to the root
2521
+ return matchesToLoad.map(() => ({
2522
+ type: ResultType.error,
2523
+ error: e
2524
+ }));
2525
+ }
2526
+ }
2527
+ async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
2528
+ let [loaderResults, ...fetcherResults] = await Promise.all([matchesToLoad.length ? callDataStrategy("loader", request, matchesToLoad, matches) : [], ...fetchersToLoad.map(f => {
2529
+ if (f.matches && f.match && f.controller) {
2530
+ let fetcherRequest = createClientSideRequest(init.history, f.path, f.controller.signal);
2531
+ return callDataStrategy("loader", fetcherRequest, [f.match], f.matches).then(r => r[0]);
2532
+ } else {
2491
2533
  return Promise.resolve({
2492
2534
  type: ResultType.error,
2493
2535
  error: getInternalRouterError(404, {
@@ -2495,16 +2537,6 @@ function createRouter(init) {
2495
2537
  })
2496
2538
  });
2497
2539
  }
2498
- return dataStrategy({
2499
- matches: [finesseToAgnosticDataStrategyMatch(f.match, mapRouteProperties, manifest)],
2500
- request,
2501
- type: "loader",
2502
- defaultStrategy(match) {
2503
- invariant(f.controller, "Expected controller for fetcher in defaultStrategy");
2504
- invariant(f.matches, "Expected matches for fetcher in defaultStrategy");
2505
- return callLoaderOrActionImplementation("loader", createClientSideRequest(init.history, f.path, f.controller.signal), match, f.matches, basename, future.v7_relativeSplatPath);
2506
- }
2507
- }).then(r => r[0]);
2508
2540
  })]);
2509
2541
  await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, loaderResults.map(() => request.signal), false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, fetchersToLoad.map(f => f.controller ? f.controller.signal : null), true)]);
2510
2542
  return {
@@ -2803,10 +2835,9 @@ function createRouter(init) {
2803
2835
  const UNSAFE_DEFERRED_SYMBOL = Symbol("deferred");
2804
2836
  function createStaticHandler(routes, opts) {
2805
2837
  invariant(routes.length > 0, "You must provide a non-empty routes array to createStaticHandler");
2806
- const dataStrategy = (opts == null ? void 0 : opts.dataStrategy) || defaultDataStrategy;
2807
- const callLoaderOrAction = createCallLoaderOrAction(dataStrategy);
2808
2838
  let manifest = {};
2809
2839
  let basename = (opts ? opts.basename : null) || "/";
2840
+ let dataStrategyImpl = (opts == null ? void 0 : opts.unstable_dataStrategy) || defaultDataStrategy;
2810
2841
  let mapRouteProperties;
2811
2842
  if (opts != null && opts.mapRouteProperties) {
2812
2843
  mapRouteProperties = opts.mapRouteProperties;
@@ -2821,7 +2852,8 @@ function createStaticHandler(routes, opts) {
2821
2852
  }
2822
2853
  // Config driven behavior flags
2823
2854
  let future = _extends({
2824
- v7_relativeSplatPath: false
2855
+ v7_relativeSplatPath: false,
2856
+ v7_throwAbortReason: false
2825
2857
  }, opts ? opts.future : null);
2826
2858
  let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
2827
2859
  /**
@@ -2999,14 +3031,14 @@ function createStaticHandler(routes, opts) {
2999
3031
  actionHeaders: {}
3000
3032
  });
3001
3033
  } catch (e) {
3002
- // If the user threw/returned a Response in callLoaderOrAction, we throw
3003
- // it to bail out and then return or throw here based on whether the user
3004
- // returned or threw
3005
- if (isQueryRouteResponse(e)) {
3034
+ // If the user threw/returned a Response in callLoaderOrAction for a
3035
+ // `queryRoute` call, we throw the `HandlerResult` to bail out early
3036
+ // and then return or throw the raw Response here accordingly
3037
+ if (isHandlerResult(e) && isResponse(e.result)) {
3006
3038
  if (e.type === ResultType.error) {
3007
- throw e.response;
3039
+ throw e.result;
3008
3040
  }
3009
- return e.response;
3041
+ return e.result;
3010
3042
  }
3011
3043
  // Redirects are always returned since they don't propagate to catch
3012
3044
  // boundaries
@@ -3032,14 +3064,10 @@ function createStaticHandler(routes, opts) {
3032
3064
  error
3033
3065
  };
3034
3066
  } else {
3035
- result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, future.v7_relativeSplatPath, {
3036
- isStaticRequest: true,
3037
- isRouteRequest,
3038
- requestContext
3039
- });
3067
+ let results = await callDataStrategy("action", request, [actionMatch], matches, isRouteRequest, requestContext);
3068
+ result = results[0];
3040
3069
  if (request.signal.aborted) {
3041
- let method = isRouteRequest ? "queryRoute" : "query";
3042
- throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3070
+ throwStaticHandlerAbortedError(request, isRouteRequest, future);
3043
3071
  }
3044
3072
  }
3045
3073
  if (isRedirectResult(result)) {
@@ -3048,9 +3076,9 @@ function createStaticHandler(routes, opts) {
3048
3076
  // can get back on the "throw all redirect responses" train here should
3049
3077
  // this ever happen :/
3050
3078
  throw new Response(null, {
3051
- status: result.status,
3079
+ status: result.response.status,
3052
3080
  headers: {
3053
- Location: result.location
3081
+ Location: result.response.headers.get("Location")
3054
3082
  }
3055
3083
  });
3056
3084
  }
@@ -3098,8 +3126,8 @@ function createStaticHandler(routes, opts) {
3098
3126
  return _extends({}, context, {
3099
3127
  statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
3100
3128
  actionData: null,
3101
- actionHeaders: _extends({}, result.headers ? {
3102
- [actionMatch.route.id]: result.headers
3129
+ actionHeaders: _extends({}, result.response ? {
3130
+ [actionMatch.route.id]: result.response.headers
3103
3131
  } : {})
3104
3132
  });
3105
3133
  }
@@ -3110,15 +3138,17 @@ function createStaticHandler(routes, opts) {
3110
3138
  signal: request.signal
3111
3139
  });
3112
3140
  let context = await loadRouteData(loaderRequest, matches, requestContext);
3113
- return _extends({}, context, result.statusCode ? {
3114
- statusCode: result.statusCode
3115
- } : {}, {
3141
+ return _extends({}, context, {
3116
3142
  actionData: {
3117
3143
  [actionMatch.route.id]: result.data
3118
- },
3119
- actionHeaders: _extends({}, result.headers ? {
3120
- [actionMatch.route.id]: result.headers
3121
- } : {})
3144
+ }
3145
+ }, result.response ? {
3146
+ statusCode: result.response.status,
3147
+ actionHeaders: {
3148
+ [actionMatch.route.id]: result.response.headers
3149
+ }
3150
+ } : {
3151
+ actionHeaders: {}
3122
3152
  });
3123
3153
  }
3124
3154
  async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
@@ -3147,21 +3177,9 @@ function createStaticHandler(routes, opts) {
3147
3177
  activeDeferreds: null
3148
3178
  };
3149
3179
  }
3150
- let results = await dataStrategy({
3151
- matches: matchesToLoad.map(m => finesseToAgnosticDataStrategyMatch(m, mapRouteProperties, manifest)),
3152
- request,
3153
- type: "loader",
3154
- defaultStrategy(match) {
3155
- return callLoaderOrActionImplementation("loader", request, match, matches, basename, future.v7_relativeSplatPath, {
3156
- isStaticRequest: true,
3157
- isRouteRequest,
3158
- requestContext
3159
- });
3160
- }
3161
- });
3180
+ let results = await callDataStrategy("loader", request, matchesToLoad, matches, isRouteRequest, requestContext);
3162
3181
  if (request.signal.aborted) {
3163
- let method = isRouteRequest ? "queryRoute" : "query";
3164
- throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3182
+ throwStaticHandlerAbortedError(request, isRouteRequest, future);
3165
3183
  }
3166
3184
  // Process and commit output from loaders
3167
3185
  let activeDeferreds = new Map();
@@ -3178,6 +3196,24 @@ function createStaticHandler(routes, opts) {
3178
3196
  activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
3179
3197
  });
3180
3198
  }
3199
+ // Utility wrapper for calling dataStrategy server-side without having to
3200
+ // pass around the manifest, mapRouteProperties, etc.
3201
+ async function callDataStrategy(type, request, matchesToLoad, matches, isRouteRequest, requestContext) {
3202
+ let results = await callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext);
3203
+ return await Promise.all(results.map((result, i) => {
3204
+ if (isRedirectHandlerResult(result)) {
3205
+ // Throw redirects and let the server handle them with an HTTP redirect
3206
+ throw normalizeRelativeRoutingRedirectResponse(result.result, request, matchesToLoad[i].route.id, matches, basename, future.v7_relativeSplatPath);
3207
+ }
3208
+ if (isResponse(result.result) && isRouteRequest) {
3209
+ // For SSR single-route requests, we want to hand Responses back directly
3210
+ // without unwrapping. We do this with the QueryRouteResponse wrapper
3211
+ // interface so we can know whether it was returned or thrown
3212
+ throw result;
3213
+ }
3214
+ return convertHandlerResultToDataResult(result);
3215
+ }));
3216
+ }
3181
3217
  return {
3182
3218
  dataRoutes,
3183
3219
  query,
@@ -3188,26 +3224,26 @@ function createStaticHandler(routes, opts) {
3188
3224
  ////////////////////////////////////////////////////////////////////////////////
3189
3225
  //#region Helpers
3190
3226
  ////////////////////////////////////////////////////////////////////////////////
3191
- function defaultDataStrategy(_ref3) {
3192
- let {
3193
- defaultStrategy,
3194
- matches
3195
- } = _ref3;
3196
- return Promise.all(matches.map(match => defaultStrategy(match)));
3197
- }
3198
3227
  /**
3199
3228
  * Given an existing StaticHandlerContext and an error thrown at render time,
3200
3229
  * provide an updated StaticHandlerContext suitable for a second SSR render
3201
3230
  */
3202
3231
  function getStaticContextFromError(routes, context, error) {
3203
3232
  let newContext = _extends({}, context, {
3204
- statusCode: 500,
3233
+ statusCode: isRouteErrorResponse(error) ? error.status : 500,
3205
3234
  errors: {
3206
3235
  [context._deepestRenderedBoundaryId || routes[0].id]: error
3207
3236
  }
3208
3237
  });
3209
3238
  return newContext;
3210
3239
  }
3240
+ function throwStaticHandlerAbortedError(request, isRouteRequest, future) {
3241
+ if (future.v7_throwAbortReason && request.signal.reason !== undefined) {
3242
+ throw request.signal.reason;
3243
+ }
3244
+ let method = isRouteRequest ? "queryRoute" : "query";
3245
+ throw new Error(method + "() call aborted: " + request.method + " " + request.url);
3246
+ }
3211
3247
  function isSubmissionNavigation(opts) {
3212
3248
  return opts != null && ("formData" in opts && opts.formData != null || "body" in opts && opts.body !== undefined);
3213
3249
  }
@@ -3286,8 +3322,8 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
3286
3322
  }
3287
3323
  let text = typeof opts.body === "string" ? opts.body : opts.body instanceof FormData || opts.body instanceof URLSearchParams ?
3288
3324
  // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#plain-text-form-data
3289
- Array.from(opts.body.entries()).reduce((acc, _ref4) => {
3290
- let [name, value] = _ref4;
3325
+ Array.from(opts.body.entries()).reduce((acc, _ref3) => {
3326
+ let [name, value] = _ref3;
3291
3327
  return "" + acc + name + "=" + value + "\n";
3292
3328
  }, "") : String(opts.body);
3293
3329
  return {
@@ -3395,18 +3431,24 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3395
3431
  let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
3396
3432
  let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
3397
3433
  let navigationMatches = boundaryMatches.filter((match, index) => {
3398
- if (isInitialLoad) {
3399
- // On initial hydration we don't do any shouldRevalidate stuff - we just
3400
- // call the unhydrated loaders
3401
- return isUnhydratedRoute(state, match.route);
3402
- }
3403
- if (match.route.lazy) {
3434
+ let {
3435
+ route
3436
+ } = match;
3437
+ if (route.lazy) {
3404
3438
  // We haven't loaded this route yet so we don't know if it's got a loader!
3405
3439
  return true;
3406
3440
  }
3407
- if (match.route.loader == null) {
3441
+ if (route.loader == null) {
3408
3442
  return false;
3409
3443
  }
3444
+ if (isInitialLoad) {
3445
+ if (typeof route.loader !== "function" || route.loader.hydrate) {
3446
+ return true;
3447
+ }
3448
+ return state.loaderData[route.id] === undefined && (
3449
+ // Don't re-run if the loader ran and threw an error
3450
+ !state.errors || state.errors[route.id] === undefined);
3451
+ }
3410
3452
  // Always call the loader on new route instances and pending defer cancellations
3411
3453
  if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
3412
3454
  return true;
@@ -3503,19 +3545,6 @@ function getMatchesToLoad(history, state, matches, submission, location, isIniti
3503
3545
  });
3504
3546
  return [navigationMatches, revalidatingFetchers];
3505
3547
  }
3506
- // Is this route unhydrated (when v7_partialHydration=true) such that we need
3507
- // to call it's loader on the initial router creation
3508
- function isUnhydratedRoute(state, route) {
3509
- if (!route.loader) {
3510
- return false;
3511
- }
3512
- if (route.loader.hydrate) {
3513
- return true;
3514
- }
3515
- return state.loaderData[route.id] === undefined && (!state.errors ||
3516
- // Loader ran but errored - don't re-run
3517
- state.errors[route.id] === undefined);
3518
- }
3519
3548
  function isNewLoader(currentLoaderData, currentMatch, match) {
3520
3549
  let isNew =
3521
3550
  // [a] -> [a, b]
@@ -3596,44 +3625,52 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
3596
3625
  }));
3597
3626
  return routeToUpdate;
3598
3627
  }
3599
- function createCallLoaderOrAction(dataStrategy) {
3600
- return async function (type, request, match, matches, manifest, mapRouteProperties, basename, v7_relativeSplatPath, opts) {
3601
- if (opts === void 0) {
3602
- opts = {};
3603
- }
3604
- let [result] = await dataStrategy({
3605
- matches: [finesseToAgnosticDataStrategyMatch(match, mapRouteProperties, manifest)],
3606
- request,
3607
- type,
3608
- defaultStrategy(match) {
3609
- return callLoaderOrActionImplementation(type, request, match, matches, basename, v7_relativeSplatPath, opts);
3610
- }
3611
- });
3612
- return result;
3613
- };
3628
+ // Default implementation of `dataStrategy` which fetches all loaders in parallel
3629
+ function defaultDataStrategy(opts) {
3630
+ return Promise.all(opts.matches.map(m => m.bikeshed_loadRoute()));
3614
3631
  }
3615
- function finesseToAgnosticDataStrategyMatch(match, mapRouteProperties, manifest) {
3616
- let loadRoutePromise;
3617
- if (match.route.lazy) {
3618
- try {
3619
- loadRoutePromise = loadLazyRouteModule(match.route, mapRouteProperties, manifest);
3620
- } catch (error) {
3621
- loadRoutePromise = Promise.reject(error);
3622
- }
3623
- }
3624
- if (!loadRoutePromise) {
3625
- loadRoutePromise = Promise.resolve(match.route);
3626
- }
3627
- loadRoutePromise.catch(() => {});
3628
- return _extends({}, match, {
3629
- route: Object.assign(loadRoutePromise, match.route)
3632
+ async function callDataStrategyImpl(dataStrategyImpl, type, request, matchesToLoad, matches, manifest, mapRouteProperties, requestContext) {
3633
+ let routeIdsToLoad = matchesToLoad.reduce((acc, m) => acc.add(m.route.id), new Set());
3634
+ let loadedMatches = new Set();
3635
+ // Send all matches here to allow for a middleware-type implementation.
3636
+ // handler will be a no-op for unneeded routes and we filter those results
3637
+ // back out below.
3638
+ let results = await dataStrategyImpl({
3639
+ matches: matches.map(match => {
3640
+ // `bikeshed_loadRoute` encapsulates the route.lazy, executing the
3641
+ // loader/action, and mapping return values/thrown errors to a
3642
+ // HandlerResult. Users can pass a callback to take fine-grained control
3643
+ // over the execution of the loader/action
3644
+ let bikeshed_loadRoute = handlerOverride => {
3645
+ loadedMatches.add(match.route.id);
3646
+ return routeIdsToLoad.has(match.route.id) ? callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, requestContext) :
3647
+ // TODO: What's the best thing to do here - return an empty "success" result?
3648
+ // Or return a success result with the current route loader/action data?
3649
+ // We strip these results out if the route didn't need to be revalidated in
3650
+ // `callDataStrategy` so it doesn't matter for us. It's more of a question
3651
+ // of whether exposing the current data to the user is useful?
3652
+ Promise.resolve({
3653
+ type: ResultType.data,
3654
+ result: undefined
3655
+ });
3656
+ };
3657
+ return _extends({}, match, {
3658
+ bikeshed_loadRoute
3659
+ });
3660
+ }),
3661
+ request,
3662
+ params: matches[0].params
3630
3663
  });
3664
+ // Throw if any loadRoute implementations not called since they are what
3665
+ // ensures a route is fully loaded
3666
+ // Throw if any loadRoute implementations not called since they are what
3667
+ // ensure a route is fully loaded
3668
+ matches.forEach(m => invariant(loadedMatches.has(m.route.id), "`match.bikeshed_loadRoute()` was not called for route id \"" + m.route.id + "\". " + "You must call `match.bikeshed_loadRoute()` on every match passed to " + "`dataStrategy` to ensure all routes are properly loaded."));
3669
+ // Filter out any middleware-only matches for which we didn't need to run handlers
3670
+ return results.filter((_, i) => routeIdsToLoad.has(matches[i].route.id));
3631
3671
  }
3632
- async function callLoaderOrActionImplementation(type, request, match, matches, basename, v7_relativeSplatPath, opts) {
3633
- if (opts === void 0) {
3634
- opts = {};
3635
- }
3636
- let resultType;
3672
+ // Default logic for calling a loader/action is the user has no specified a dataStrategy
3673
+ async function callLoaderOrAction(type, request, match, manifest, mapRouteProperties, handlerOverride, staticContext) {
3637
3674
  let result;
3638
3675
  let onReject;
3639
3676
  let runHandler = handler => {
@@ -3642,11 +3679,18 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
3642
3679
  let abortPromise = new Promise((_, r) => reject = r);
3643
3680
  onReject = () => reject();
3644
3681
  request.signal.addEventListener("abort", onReject);
3645
- return Promise.race([handler({
3646
- request,
3647
- params: match.params,
3648
- context: opts.requestContext
3649
- }), abortPromise]);
3682
+ let runHandlerForReal = ctx => {
3683
+ if (typeof handler !== "function") {
3684
+ return Promise.reject(new Error("You cannot call the handler for a route which defines a boolean " + ("\"" + type + "\" [routeId: " + match.route.id + "]")));
3685
+ }
3686
+ return handler({
3687
+ request,
3688
+ params: match.params,
3689
+ context: staticContext
3690
+ }, ...(ctx !== undefined ? [ctx] : []));
3691
+ };
3692
+ let handlerPromise = handlerOverride ? handlerOverride(async ctx => runHandlerForReal(ctx)) : runHandlerForReal();
3693
+ return Promise.race([handlerPromise, abortPromise]);
3650
3694
  };
3651
3695
  try {
3652
3696
  let handler = match.route[type];
@@ -3660,17 +3704,17 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
3660
3704
  // route has a boundary that can handle the error
3661
3705
  runHandler(handler).catch(e => {
3662
3706
  handlerError = e;
3663
- }), match.route]);
3707
+ }), loadLazyRouteModule(match.route, mapRouteProperties, manifest)]);
3664
3708
  if (handlerError) {
3665
3709
  throw handlerError;
3666
3710
  }
3667
3711
  result = values[0];
3668
3712
  } else {
3669
3713
  // Load lazy route module, then run any returned handler
3670
- let route = await match.route;
3671
- handler = route[type];
3714
+ await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
3715
+ handler = match.route[type];
3672
3716
  if (handler) {
3673
- // Handler still run even if we got interrupted to maintain consistency
3717
+ // Handler still runs even if we got interrupted to maintain consistency
3674
3718
  // with un-abortable behavior of handler execution on non-lazy or
3675
3719
  // previously-lazy-loaded routes
3676
3720
  result = await runHandler(handler);
@@ -3687,7 +3731,7 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
3687
3731
  // hit the invariant below that errors on returning undefined.
3688
3732
  return {
3689
3733
  type: ResultType.data,
3690
- data: undefined
3734
+ result: undefined
3691
3735
  };
3692
3736
  }
3693
3737
  }
@@ -3702,66 +3746,37 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
3702
3746
  }
3703
3747
  invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
3704
3748
  } catch (e) {
3705
- resultType = ResultType.error;
3706
- result = e;
3749
+ return {
3750
+ type: ResultType.error,
3751
+ result: e
3752
+ };
3707
3753
  } finally {
3708
3754
  if (onReject) {
3709
3755
  request.signal.removeEventListener("abort", onReject);
3710
3756
  }
3711
3757
  }
3758
+ return {
3759
+ type: ResultType.data,
3760
+ result
3761
+ };
3762
+ }
3763
+ async function convertHandlerResultToDataResult(handlerResult) {
3764
+ let {
3765
+ result,
3766
+ type
3767
+ } = handlerResult;
3712
3768
  if (isResponse(result)) {
3713
- let status = result.status;
3714
- // Process redirects
3715
- if (redirectStatusCodes.has(status)) {
3716
- let location = result.headers.get("Location");
3717
- invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
3718
- // Support relative routing in internal redirects
3719
- if (!ABSOLUTE_URL_REGEX.test(location)) {
3720
- location = normalizeTo(new URL(request.url), matches.slice(0, matches.findIndex(m => m.route.id === match.route.id) + 1), basename, true, location, v7_relativeSplatPath);
3721
- } else if (!opts.isStaticRequest) {
3722
- // Strip off the protocol+origin for same-origin + same-basename absolute
3723
- // redirects. If this is a static request, we can let it go back to the
3724
- // browser as-is
3725
- let currentUrl = new URL(request.url);
3726
- let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
3727
- let isSameBasename = stripBasename(url.pathname, basename) != null;
3728
- if (url.origin === currentUrl.origin && isSameBasename) {
3729
- location = url.pathname + url.search + url.hash;
3730
- }
3731
- }
3732
- // Don't process redirects in the router during static requests requests.
3733
- // Instead, throw the Response and let the server handle it with an HTTP
3734
- // redirect. We also update the Location header in place in this flow so
3735
- // basename and relative routing is taken into account
3736
- if (opts.isStaticRequest) {
3737
- result.headers.set("Location", location);
3738
- throw result;
3739
- }
3740
- return {
3741
- type: ResultType.redirect,
3742
- status,
3743
- location,
3744
- revalidate: result.headers.get("X-Remix-Revalidate") !== null,
3745
- reloadDocument: result.headers.get("X-Remix-Reload-Document") !== null
3746
- };
3747
- }
3748
- // For SSR single-route requests, we want to hand Responses back directly
3749
- // without unwrapping. We do this with the QueryRouteResponse wrapper
3750
- // interface so we can know whether it was returned or thrown
3751
- if (opts.isRouteRequest) {
3752
- let queryRouteResponse = {
3753
- type: resultType === ResultType.error ? ResultType.error : ResultType.data,
3754
- response: result
3755
- };
3756
- throw queryRouteResponse;
3757
- }
3758
3769
  let data;
3759
3770
  try {
3760
3771
  let contentType = result.headers.get("Content-Type");
3761
3772
  // Check between word boundaries instead of startsWith() due to the last
3762
3773
  // paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
3763
3774
  if (contentType && /\bapplication\/json\b/.test(contentType)) {
3764
- data = await result.json();
3775
+ if (result.body == null) {
3776
+ data = null;
3777
+ } else {
3778
+ data = await result.json();
3779
+ }
3765
3780
  } else {
3766
3781
  data = await result.text();
3767
3782
  }
@@ -3771,23 +3786,22 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
3771
3786
  error: e
3772
3787
  };
3773
3788
  }
3774
- if (resultType === ResultType.error) {
3789
+ if (type === ResultType.error) {
3775
3790
  return {
3776
- type: resultType,
3777
- error: new ErrorResponseImpl(status, result.statusText, data),
3778
- headers: result.headers
3791
+ type,
3792
+ error: new ErrorResponseImpl(result.status, result.statusText, data),
3793
+ response: result
3779
3794
  };
3780
3795
  }
3781
3796
  return {
3782
3797
  type: ResultType.data,
3783
3798
  data,
3784
- statusCode: result.status,
3785
- headers: result.headers
3799
+ response: result
3786
3800
  };
3787
3801
  }
3788
- if (resultType === ResultType.error) {
3802
+ if (type === ResultType.error) {
3789
3803
  return {
3790
- type: resultType,
3804
+ type,
3791
3805
  error: result
3792
3806
  };
3793
3807
  }
@@ -3805,6 +3819,17 @@ async function callLoaderOrActionImplementation(type, request, match, matches, b
3805
3819
  data: result
3806
3820
  };
3807
3821
  }
3822
+ // Support relative routing in internal redirects
3823
+ function normalizeRelativeRoutingRedirectResponse(response, request, routeId, matches, basename, v7_relativeSplatPath) {
3824
+ let location = response.headers.get("Location");
3825
+ invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
3826
+ if (!ABSOLUTE_URL_REGEX.test(location)) {
3827
+ let trimmedMatches = matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1);
3828
+ location = normalizeTo(new URL(request.url), trimmedMatches, basename, true, location, v7_relativeSplatPath);
3829
+ response.headers.set("Location", location);
3830
+ }
3831
+ return response;
3832
+ }
3808
3833
  // Utility method for creating the Request instances for loaders/actions during
3809
3834
  // client-side navigations and fetches. During SSR we will always have a
3810
3835
  // Request instance from the static handler (query/queryRoute)
@@ -3891,23 +3916,31 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
3891
3916
  foundError = true;
3892
3917
  statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
3893
3918
  }
3894
- if (result.headers) {
3895
- loaderHeaders[id] = result.headers;
3919
+ if (result.response) {
3920
+ loaderHeaders[id] = result.response.headers;
3896
3921
  }
3897
3922
  } else {
3898
3923
  if (isDeferredResult(result)) {
3899
3924
  activeDeferreds.set(id, result.deferredData);
3900
3925
  loaderData[id] = result.deferredData.data;
3926
+ // Error status codes always override success status codes, but if all
3927
+ // loaders are successful we take the deepest status code.
3928
+ if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
3929
+ statusCode = result.statusCode;
3930
+ }
3931
+ if (result.headers) {
3932
+ loaderHeaders[id] = result.headers;
3933
+ }
3901
3934
  } else {
3902
3935
  loaderData[id] = result.data;
3903
- }
3904
- // Error status codes always override success status codes, but if all
3905
- // loaders are successful we take the deepest status code.
3906
- if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
3907
- statusCode = result.statusCode;
3908
- }
3909
- if (result.headers) {
3910
- loaderHeaders[id] = result.headers;
3936
+ // Error status codes always override success status codes, but if all
3937
+ // loaders are successful we take the deepest status code.
3938
+ if (result.response) {
3939
+ if (result.response.status !== 200 && !foundError) {
3940
+ statusCode = result.response.status;
3941
+ }
3942
+ loaderHeaders[id] = result.response.headers;
3943
+ }
3911
3944
  }
3912
3945
  }
3913
3946
  });
@@ -4081,6 +4114,12 @@ function isHashChangeOnly(a, b) {
4081
4114
  // /page#hash -> /page
4082
4115
  return false;
4083
4116
  }
4117
+ function isHandlerResult(result) {
4118
+ return result != null && typeof result === "object" && "type" in result && "result" in result && (result.type === ResultType.data || result.type === ResultType.error);
4119
+ }
4120
+ function isRedirectHandlerResult(result) {
4121
+ return isResponse(result.result) && redirectStatusCodes.has(result.result.status);
4122
+ }
4084
4123
  function isDeferredResult(result) {
4085
4124
  return result.type === ResultType.deferred;
4086
4125
  }
@@ -4105,9 +4144,6 @@ function isRedirectResponse(result) {
4105
4144
  let location = result.headers.get("Location");
4106
4145
  return status >= 300 && status <= 399 && location != null;
4107
4146
  }
4108
- function isQueryRouteResponse(obj) {
4109
- return obj && isResponse(obj.response) && (obj.type === ResultType.data || obj.type === ResultType.error);
4110
- }
4111
4147
  function isValidMethod(method) {
4112
4148
  return validRequestMethods.has(method.toLowerCase());
4113
4149
  }
@@ -4345,5 +4381,5 @@ function persistAppliedTransitions(_window, transitions) {
4345
4381
  }
4346
4382
  //#endregion
4347
4383
 
4348
- export { AbortedDeferredError, Action, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, ResultType, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, ErrorResponseImpl as UNSAFE_ErrorResponseImpl, convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getResolveToMatches as UNSAFE_getResolveToMatches, invariant as UNSAFE_invariant, warning as UNSAFE_warning, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, defer, generatePath, getStaticContextFromError, getToPathname, isDeferredData, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, redirectDocument, resolvePath, resolveTo, stripBasename };
4384
+ export { AbortedDeferredError, Action, IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, UNSAFE_DEFERRED_SYMBOL, DeferredData as UNSAFE_DeferredData, ErrorResponseImpl as UNSAFE_ErrorResponseImpl, convertRouteMatchToUiMatch as UNSAFE_convertRouteMatchToUiMatch, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getResolveToMatches as UNSAFE_getResolveToMatches, invariant as UNSAFE_invariant, warning as UNSAFE_warning, createBrowserHistory, createHashHistory, createMemoryHistory, createPath, createRouter, createStaticHandler, defer, generatePath, getStaticContextFromError, getToPathname, isDeferredData, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, parsePath, redirect, redirectDocument, resolvePath, resolveTo, stripBasename };
4349
4385
  //# sourceMappingURL=router.js.map