Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data Strategy Configuration #11098

Merged
merged 56 commits into from Mar 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
8e2e259
wip
jacob-ebey Dec 6, 2023
6bbc243
2 hours...... 😮‍💨
jacob-ebey Dec 7, 2023
4c21cf9
add static handler paths
jacob-ebey Dec 7, 2023
5ecb636
added some initial tests
jacob-ebey Dec 7, 2023
2ca1506
fetchers are always called on their own
jacob-ebey Dec 7, 2023
48d23f1
remove unused lines
jacob-ebey Dec 7, 2023
66c8b7d
add a few more tests
jacob-ebey Dec 8, 2023
562cd60
feat: initial support for lazy
jacob-ebey Dec 8, 2023
d6707a7
chore: add ssr single fetch example
jacob-ebey Dec 10, 2023
32b4ad6
update fixture
jacob-ebey Dec 10, 2023
42530e0
await lazy routes in example
jacob-ebey Dec 10, 2023
9225980
callLoaderOrAction -> callLoaderOrActionImplementation
brophdawg11 Dec 11, 2023
20230d5
filter matches in fixture
jacob-ebey Dec 11, 2023
7e3e660
Merge branch 'dev' into data_strategy
jacob-ebey Dec 13, 2023
ef927da
chore: rename to unstable_dataStrategy
jacob-ebey Jan 22, 2024
fae7726
chore: fix lint
jacob-ebey Jan 22, 2024
e960cf1
chore: update test after rename
jacob-ebey Jan 22, 2024
af7fbcd
Minor refactors
brophdawg11 Jan 29, 2024
4d42692
Merge branch 'dev' into data_strategy
brophdawg11 Jan 29, 2024
719defb
Remove excess callDataStratgy fn
brophdawg11 Jan 29, 2024
109f464
Add more unit tests
brophdawg11 Jan 30, 2024
359a714
Add decision doc
brophdawg11 Jan 31, 2024
0bba762
Rework internals to support HandlerResult and match.handler()
brophdawg11 Feb 1, 2024
57333e0
Switch to match.handler API
brophdawg11 Feb 2, 2024
fad8182
Bump bundle
brophdawg11 Feb 2, 2024
f49bb0c
Update decision doc
brophdawg11 Feb 2, 2024
6bb2c92
Remove match.route promise implementation in favor of handler
brophdawg11 Feb 2, 2024
53eb1a2
Refactor SuccessResult/ErrorResult to hold the raw response
brophdawg11 Feb 2, 2024
64a420e
Convert RedirectResult to use raw response, code cleanup
brophdawg11 Feb 2, 2024
c670e77
Bump bundle
brophdawg11 Feb 2, 2024
4181202
Refactor to new loadRoute/handler API
brophdawg11 Feb 5, 2024
31e619e
Don't export HandlerResult
brophdawg11 Feb 5, 2024
1a60803
Bump bundle
brophdawg11 Feb 5, 2024
dfdc2ce
Allow loaders/actions to be a boolean
brophdawg11 Feb 6, 2024
a088889
Merge branch 'dev' into data_strategy
brophdawg11 Feb 6, 2024
acfea93
Add loadRouteIds and bikeshed_load functionality
brophdawg11 Feb 14, 2024
bf37bae
Bump bundle
brophdawg11 Feb 14, 2024
a8bedaa
Move dataStrategys test to their own file
brophdawg11 Feb 14, 2024
3ba3024
Handle falsy errors correctly
brophdawg11 Feb 14, 2024
2272fa7
Rename bikeshed_loaderRoutes->resolve/bikeshed_load->shouldLoad
brophdawg11 Feb 15, 2024
5bedc16
Add v7_skipActionErrorRevalidation fiuture flag
brophdawg11 Feb 16, 2024
cbcd94b
Proxy along ErrorResponse status codes to ErrorResult
brophdawg11 Feb 16, 2024
0c7e98b
Bump bundle
brophdawg11 Feb 16, 2024
de419c3
Updates
brophdawg11 Feb 16, 2024
885bd5c
Mark DecodedResponse exports as unstable
brophdawg11 Feb 29, 2024
0141b5e
Merge branch 'dev' into data_strategy
brophdawg11 Feb 29, 2024
432fcb2
Remove DecodedResponse and use HandlerResult
brophdawg11 Mar 1, 2024
7110845
Merge branch 'dev' into data_strategy
brophdawg11 Mar 5, 2024
921ace2
Apply suggestions from code review
brophdawg11 Mar 6, 2024
ec2ae97
Merge branch 'dev' into data_strategy
brophdawg11 Mar 8, 2024
c72d536
Updates from final review
brophdawg11 Mar 8, 2024
aa19ad4
Add skipLoaderErrorBubbling and final tests
brophdawg11 Mar 11, 2024
496788e
Fix lint issue
brophdawg11 Mar 11, 2024
a68dbaa
Revert "Fix lint issue"
brophdawg11 Mar 11, 2024
e516247
Revert "Revert "Fix lint issue"" to trigger CI?
brophdawg11 Mar 11, 2024
2a8dc42
Bump buindle
brophdawg11 Mar 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
185 changes: 137 additions & 48 deletions packages/router/router.ts
Expand Up @@ -13,6 +13,8 @@ import type {
AgnosticDataRouteObject,
AgnosticRouteObject,
DataResult,
DataStrategyFunction,
DataStrategyFunctionArgs,
DeferredData,
DeferredResult,
DetectErrorBoundaryFunction,
Expand Down Expand Up @@ -370,6 +372,7 @@ export interface RouterInit {
* @deprecated Use `mapRouteProperties` instead
*/
detectErrorBoundary?: DetectErrorBoundaryFunction;
dataStrategy?: DataStrategyFunction;
mapRouteProperties?: MapRoutePropertiesFunction;
future?: Partial<FutureConfig>;
hydrationData?: HydrationState;
Expand Down Expand Up @@ -752,6 +755,8 @@ export function createRouter(init: RouterInit): Router {
"You must provide a non-empty routes array to createRouter"
);

const dataStrategy = init.dataStrategy || defaultDataStrategy;

let mapRouteProperties: MapRoutePropertiesFunction;
if (init.mapRouteProperties) {
mapRouteProperties = init.mapRouteProperties;
Expand Down Expand Up @@ -1567,7 +1572,7 @@ export function createRouter(init: RouterInit): Router {
}),
};
} else {
result = await callLoaderOrAction(
result = await loadOrMutateData(
"action",
request,
actionMatch,
Expand Down Expand Up @@ -1761,8 +1766,8 @@ export function createRouter(init: RouterInit): Router {
);
}

let { results, loaderResults, fetcherResults } =
await callLoadersAndMaybeResolveData(
let { loaderResults, fetcherResults } =
await loadDataAndMaybeResolveDeferred(
state.matches,
matches,
matchesToLoad,
Expand All @@ -1786,7 +1791,7 @@ export function createRouter(init: RouterInit): Router {
revalidatingFetchers.forEach((rf) => fetchControllers.delete(rf.key));

// If any loaders returned a redirect Response, start a new REPLACE navigation
let redirect = findRedirect(results);
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
if (redirect) {
if (redirect.idx >= matchesToLoad.length) {
// If this redirect came from a fetcher make sure we mark it in
Expand Down Expand Up @@ -1961,7 +1966,7 @@ export function createRouter(init: RouterInit): Router {
fetchControllers.set(key, abortController);

let originatingLoadId = incrementingLoadId;
let actionResult = await callLoaderOrAction(
let actionResult = await loadOrMutateData(
"action",
fetchRequest,
match,
Expand Down Expand Up @@ -2086,8 +2091,8 @@ export function createRouter(init: RouterInit): Router {
abortPendingFetchRevalidations
);

let { results, loaderResults, fetcherResults } =
await callLoadersAndMaybeResolveData(
let { loaderResults, fetcherResults } =
await loadDataAndMaybeResolveDeferred(
state.matches,
matches,
matchesToLoad,
Expand All @@ -2108,7 +2113,7 @@ export function createRouter(init: RouterInit): Router {
fetchControllers.delete(key);
revalidatingFetchers.forEach((r) => fetchControllers.delete(r.key));

let redirect = findRedirect(results);
let redirect = findRedirect([...loaderResults, ...fetcherResults]);
if (redirect) {
if (redirect.idx >= matchesToLoad.length) {
// If this redirect came from a fetcher make sure we mark it in
Expand Down Expand Up @@ -2206,7 +2211,7 @@ export function createRouter(init: RouterInit): Router {
fetchControllers.set(key, abortController);

let originatingLoadId = incrementingLoadId;
let result: DataResult = await callLoaderOrAction(
let result: DataResult = await loadOrMutateData(
"loader",
fetchRequest,
match,
Expand Down Expand Up @@ -2391,52 +2396,126 @@ export function createRouter(init: RouterInit): Router {
}
}

async function callLoadersAndMaybeResolveData(
currentMatches: AgnosticDataRouteMatch[],
async function loadOrMutateData(
type: "loader" | "action",
request: Request,
match: AgnosticDataRouteMatch,
matches: AgnosticDataRouteMatch[],
matchesToLoad: AgnosticDataRouteMatch[],
fetchersToLoad: RevalidatingFetcher[],
request: Request
) {
// Call all navigation loaders and revalidating fetcher loaders in parallel,
// then slice off the results into separate arrays so we can handle them
// accordingly
let results = await Promise.all([
...matchesToLoad.map((match) =>
callLoaderOrAction(
"loader",
manifest: RouteManifest,
mapRouteProperties: MapRoutePropertiesFunction,
basename: string,
v7_relativeSplatPath: boolean,
opts: {
isStaticRequest?: boolean;
isRouteRequest?: boolean;
requestContext?: unknown;
} = {}
): Promise<DataResult> {
let [result] = await dataStrategy({
matches: [match],
request,
type,
defaultStrategy(match) {
return callLoaderOrAction(
type,
request,
match,
matches,
manifest,
mapRouteProperties,
basename,
future.v7_relativeSplatPath
)
),
...fetchersToLoad.map((f) => {
if (f.matches && f.match && f.controller) {
return callLoaderOrAction(
"loader",
createClientSideRequest(init.history, f.path, f.controller.signal),
f.match,
f.matches,
manifest,
mapRouteProperties,
basename,
future.v7_relativeSplatPath
);
} else {
let error: ErrorResult = {
type: ResultType.error,
error: getInternalRouterError(404, { pathname: f.path }),
};
return error;
}
}),
v7_relativeSplatPath,
opts
);
},
});

return result;
}

async function loadDataAndMaybeResolveDeferred(
currentMatches: AgnosticDataRouteMatch[],
matches: AgnosticDataRouteMatch[],
matchesToLoad: AgnosticDataRouteMatch[],
fetchersToLoad: RevalidatingFetcher[],
request: Request
) {
let fetchersToError: number[] = [];
let [loaderResults, fetcherResults] = await Promise.all([
matchesToLoad.length
? dataStrategy({
matches: matchesToLoad,
request,
type: "loader",
defaultStrategy(match) {
return callLoaderOrAction(
jacob-ebey marked this conversation as resolved.
Show resolved Hide resolved
"loader",
request,
match,
matches,
manifest,
mapRouteProperties,
basename,
future.v7_relativeSplatPath
);
},
})
: [],
fetchersToLoad.length
? dataStrategy({
jacob-ebey marked this conversation as resolved.
Show resolved Hide resolved
matches: fetchersToLoad
.filter((f, index) => {
if (f.matches && f.match && f.controller) {
return true;
}

fetchersToError.push(index);
return false;
})
.map((f) => f.match!),
request,
type: "loader",
defaultStrategy(match) {
let f = fetchersToLoad.find((f) => f.match === match);
invariant(f, "Expected fetcher for match in defaultStrategy");
invariant(
f.controller,
"Expected controller for fetcher in defaultStrategy"
);
invariant(
f.matches,
"Expected matches for fetcher in defaultStrategy"
);

return callLoaderOrAction(
"loader",
createClientSideRequest(
init.history,
f.path,
f.controller.signal
),
match,
f.matches,
manifest,
mapRouteProperties,
basename,
future.v7_relativeSplatPath
);
},
})
: [],
]);
let loaderResults = results.slice(0, matchesToLoad.length);
let fetcherResults = results.slice(matchesToLoad.length);

// insert an error result for fetchers that didn't have either a
// match, matches, or controller
fetchersToError.forEach((idx) => {
fetcherResults.splice(idx, 0, {
type: ResultType.error,
error: getInternalRouterError(404, {
pathname: fetchersToLoad[idx].path,
}),
});
});

await Promise.all([
resolveDeferredResults(
Expand All @@ -2456,7 +2535,10 @@ export function createRouter(init: RouterInit): Router {
),
]);

return { results, loaderResults, fetcherResults };
return {
loaderResults,
fetcherResults,
};
}

function interruptActiveLoads() {
Expand Down Expand Up @@ -3342,6 +3424,13 @@ export function createStaticHandler(
//#region Helpers
////////////////////////////////////////////////////////////////////////////////

function defaultDataStrategy({
defaultStrategy,
matches,
}: DataStrategyFunctionArgs) {
return Promise.all(matches.map((match) => defaultStrategy(match)));
}

/**
* Given an existing StaticHandlerContext and an error thrown at render time,
* provide an updated StaticHandlerContext suitable for a second SSR render
Expand Down
11 changes: 11 additions & 0 deletions packages/router/utils.ts
Expand Up @@ -223,6 +223,17 @@ export interface DetectErrorBoundaryFunction {
(route: AgnosticRouteObject): boolean;
}

export interface DataStrategyFunctionArgs {
request: Request;
matches: AgnosticDataRouteMatch[];
type: "loader" | "action";
defaultStrategy(match: AgnosticDataRouteMatch): Promise<DataResult>;
}

export interface DataStrategyFunction {
(args: DataStrategyFunctionArgs): Promise<DataResult[]>;
}

/**
* Function provided by the framework-aware layers to set any framework-specific
* properties from framework-agnostic properties
Expand Down