From c24ad806251a88777e697d7105bd54bef2ce3120 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 6 Dec 2022 15:19:55 -0500 Subject: [PATCH 1/4] Add requestContext to staticHandler query/queryRoute --- packages/router/__tests__/router-test.ts | 168 ++++++++++++++++++----- packages/router/router.ts | 66 ++++++--- packages/router/utils.ts | 1 + 3 files changed, 182 insertions(+), 53 deletions(-) diff --git a/packages/router/__tests__/router-test.ts b/packages/router/__tests__/router-test.ts index 767e0f2c2a..0ebf30e088 100644 --- a/packages/router/__tests__/router-test.ts +++ b/packages/router/__tests__/router-test.ts @@ -10932,6 +10932,42 @@ describe("a router", () => { expect(childLoaderRequest.url).toBe("http://localhost/child"); }); + it("should support a requestContext passed to loaders and actions", async () => { + let requestContext = { sessionId: "12345" }; + let rootStub = jest.fn(() => "ROOT"); + let childStub = jest.fn(() => "CHILD"); + let actionStub = jest.fn(() => "CHILD ACTION"); + let arg = (s) => s.mock.calls[0][0]; + let { query } = createStaticHandler([ + { + id: "root", + path: "/", + loader: rootStub, + children: [ + { + id: "child", + path: "child", + action: actionStub, + loader: childStub, + }, + ], + }, + ]); + + await query(createRequest("/child"), { requestContext }); + expect(arg(rootStub).requestContext.sessionId).toBe("12345"); + expect(arg(childStub).requestContext.sessionId).toBe("12345"); + + actionStub.mockClear(); + rootStub.mockClear(); + childStub.mockClear(); + + await query(createSubmitRequest("/child"), { requestContext }); + expect(arg(actionStub).requestContext.sessionId).toBe("12345"); + expect(arg(rootStub).requestContext.sessionId).toBe("12345"); + expect(arg(childStub).requestContext.sessionId).toBe("12345"); + }); + describe("statusCode", () => { it("should expose a 200 status code by default", async () => { let { query } = createStaticHandler([ @@ -11254,7 +11290,7 @@ describe("a router", () => { isError ? Promise.reject(data) : Promise.resolve(data), }, ]); - return handler.queryRoute(req, routeId); + return handler.queryRoute(req, { routeId }); } return { @@ -11307,7 +11343,9 @@ describe("a router", () => { data = await queryRoute(createRequest("/parent?index")); expect(data).toBe("PARENT INDEX LOADER"); - data = await queryRoute(createRequest("/parent/child"), "child"); + data = await queryRoute(createRequest("/parent/child"), { + routeId: "child", + }); expect(data).toBe("CHILD LOADER"); }); @@ -11324,19 +11362,27 @@ describe("a router", () => { let data; // Layout route - data = await queryRoute(createRequest("/parent"), "parent"); + data = await queryRoute(createRequest("/parent"), { + routeId: "parent", + }); expect(data).toBe("PARENT LOADER"); // Index route - data = await queryRoute(createRequest("/parent"), "parentIndex"); + data = await queryRoute(createRequest("/parent"), { + routeId: "parentIndex", + }); expect(data).toBe("PARENT INDEX LOADER"); // Parent in nested route - data = await queryRoute(createRequest("/parent/child"), "parent"); + data = await queryRoute(createRequest("/parent/child"), { + routeId: "parent", + }); expect(data).toBe("PARENT LOADER"); // Child in nested route - data = await queryRoute(createRequest("/parent/child"), "child"); + data = await queryRoute(createRequest("/parent/child"), { + routeId: "child", + }); expect(data).toBe("CHILD LOADER"); // Non-undefined falsey values should count @@ -11464,19 +11510,27 @@ describe("a router", () => { let data; // Layout route - data = await queryRoute(createRequest("/base/parent"), "parent"); + data = await queryRoute(createRequest("/base/parent"), { + routeId: "parent", + }); expect(data).toBe("PARENT LOADER"); // Index route - data = await queryRoute(createRequest("/base/parent"), "parentIndex"); + data = await queryRoute(createRequest("/base/parent"), { + routeId: "parentIndex", + }); expect(data).toBe("PARENT INDEX LOADER"); // Parent in nested route - data = await queryRoute(createRequest("/base/parent/child"), "parent"); + data = await queryRoute(createRequest("/base/parent/child"), { + routeId: "parent", + }); expect(data).toBe("PARENT LOADER"); // Child in nested route - data = await queryRoute(createRequest("/base/parent/child"), "child"); + data = await queryRoute(createRequest("/base/parent/child"), { + routeId: "child", + }); expect(data).toBe("CHILD LOADER"); // Non-undefined falsey values should count @@ -11494,19 +11548,27 @@ describe("a router", () => { let data; // Layout route - data = await queryRoute(createSubmitRequest("/parent"), "parent"); + data = await queryRoute(createSubmitRequest("/parent"), { + routeId: "parent", + }); expect(data).toBe("PARENT ACTION"); // Index route - data = await queryRoute(createSubmitRequest("/parent"), "parentIndex"); + data = await queryRoute(createSubmitRequest("/parent"), { + routeId: "parentIndex", + }); expect(data).toBe("PARENT INDEX ACTION"); // Parent in nested route - data = await queryRoute(createSubmitRequest("/parent/child"), "parent"); + data = await queryRoute(createSubmitRequest("/parent/child"), { + routeId: "parent", + }); expect(data).toBe("PARENT ACTION"); // Child in nested route - data = await queryRoute(createSubmitRequest("/parent/child"), "child"); + data = await queryRoute(createSubmitRequest("/parent/child"), { + routeId: "child", + }); expect(data).toBe("CHILD ACTION"); // Non-undefined falsey values should count @@ -11525,19 +11587,19 @@ describe("a router", () => { data = await queryRoute( createSubmitRequest("/parent", { method: "PUT" }), - "parent" + { routeId: "parent" } ); expect(data).toBe("PARENT ACTION"); data = await queryRoute( createSubmitRequest("/parent", { method: "PATCH" }), - "parent" + { routeId: "parent" } ); expect(data).toBe("PARENT ACTION"); data = await queryRoute( createSubmitRequest("/parent", { method: "DELETE" }), - "parent" + { routeId: "parent" } ); expect(data).toBe("PARENT ACTION"); }); @@ -11697,10 +11759,9 @@ describe("a router", () => { ], }, ]); - let response = await queryRoute( - createRequest("/parent/child"), - "child" - ); + let response = await queryRoute(createRequest("/parent/child"), { + routeId: "child", + }); expect(response instanceof Response).toBe(true); expect((response as Response).status).toBe(302); expect((response as Response).headers.get("Location")).toBe("/parent"); @@ -11724,10 +11785,9 @@ describe("a router", () => { ], }, ]); - let response = await queryRoute( - createSubmitRequest("/parent/child"), - "child" - ); + let response = await queryRoute(createSubmitRequest("/parent/child"), { + routeId: "child", + }); expect(response instanceof Response).toBe(true); expect((response as Response).status).toBe(302); expect((response as Response).headers.get("Location")).toBe("/parent"); @@ -11749,7 +11809,9 @@ describe("a router", () => { loader: () => redirect(url), }, ]); - let response = await handler.queryRoute(createRequest("/"), "root"); + let response = await handler.queryRoute(createRequest("/"), { + routeId: "root", + }); expect(response instanceof Response).toBe(true); expect((response as Response).status).toBe(302); expect((response as Response).headers.get("Location")).toBe(url); @@ -11766,7 +11828,7 @@ describe("a router", () => { }, ]); let request = createRequest("/"); - let data = await queryRoute(request, "root"); + let data = await queryRoute(request, { routeId: "root" }); expect(data instanceof Response).toBe(true); expect(await data.json()).toEqual({ key: "value" }); }); @@ -11781,7 +11843,7 @@ describe("a router", () => { }, ]); let request = createSubmitRequest("/"); - let data = await queryRoute(request, "root"); + let data = await queryRoute(request, { routeId: "root" }); expect(data instanceof Response).toBe(true); expect(await data.json()).toEqual({ key: "value" }); }); @@ -11801,7 +11863,7 @@ describe("a router", () => { }); let e; try { - let statePromise = queryRoute(request, "root"); + let statePromise = queryRoute(request, { routeId: "root" }); controller.abort(); // This should resolve even though we never resolved the loader await statePromise; @@ -11826,7 +11888,7 @@ describe("a router", () => { }); let e; try { - let statePromise = queryRoute(request, "root"); + let statePromise = queryRoute(request, { routeId: "root" }); controller.abort(); // This should resolve even though we never resolved the loader await statePromise; @@ -11841,7 +11903,7 @@ describe("a router", () => { let request = createRequest("/", { signal: undefined }); let e; try { - await queryRoute(request, "index"); + await queryRoute(request, { routeId: "index" }); } catch (_e) { e = _e; } @@ -11850,6 +11912,38 @@ describe("a router", () => { ); }); + it("should support a requestContext passed to loaders and actions", async () => { + let requestContext = { sessionId: "12345" }; + let childStub = jest.fn(() => "CHILD"); + let actionStub = jest.fn(() => "CHILD ACTION"); + let arg = (s) => s.mock.calls[0][0]; + let { queryRoute } = createStaticHandler([ + { + path: "/", + children: [ + { + id: "child", + path: "child", + action: actionStub, + loader: childStub, + }, + ], + }, + ]); + + await queryRoute(createRequest("/child"), { + routeId: "child", + requestContext, + }); + expect(arg(childStub).requestContext.sessionId).toBe("12345"); + + await queryRoute(createSubmitRequest("/child"), { + routeId: "child", + requestContext, + }); + expect(arg(actionStub).requestContext.sessionId).toBe("12345"); + }); + describe("Errors with Status Codes", () => { /* eslint-disable jest/no-conditional-expect */ let { queryRoute } = createStaticHandler([ @@ -11887,7 +11981,7 @@ describe("a router", () => { it("should handle not found routeIds with a 403 Response", async () => { try { - await queryRoute(createRequest("/"), "junk"); + await queryRoute(createRequest("/"), { routeId: "junk" }); expect(false).toBe(true); } catch (data) { expect(isRouteErrorResponse(data)).toBe(true); @@ -11899,7 +11993,7 @@ describe("a router", () => { } try { - await queryRoute(createSubmitRequest("/"), "junk"); + await queryRoute(createSubmitRequest("/"), { routeId: "junk" }); expect(false).toBe(true); } catch (data) { expect(isRouteErrorResponse(data)).toBe(true); @@ -11913,7 +12007,7 @@ describe("a router", () => { it("should handle missing loaders with a 400 Response", async () => { try { - await queryRoute(createRequest("/"), "root"); + await queryRoute(createRequest("/"), { routeId: "root" }); expect(false).toBe(true); } catch (data) { expect(isRouteErrorResponse(data)).toBe(true); @@ -11930,7 +12024,7 @@ describe("a router", () => { it("should handle missing actions with a 405 Response", async () => { try { - await queryRoute(createSubmitRequest("/"), "root"); + await queryRoute(createSubmitRequest("/"), { routeId: "root" }); expect(false).toBe(true); } catch (data) { expect(isRouteErrorResponse(data)).toBe(true); @@ -11947,7 +12041,9 @@ describe("a router", () => { it("should handle unsupported methods with a 405 Response", async () => { try { - await queryRoute(createRequest("/", { method: "OPTIONS" }), "root"); + await queryRoute(createRequest("/", { method: "OPTIONS" }), { + routeId: "root", + }); expect(false).toBe(true); } catch (data) { expect(isRouteErrorResponse(data)).toBe(true); diff --git a/packages/router/router.ts b/packages/router/router.ts index 3a1bf9f2e8..cdcdb61e6e 100644 --- a/packages/router/router.ts +++ b/packages/router/router.ts @@ -316,8 +316,14 @@ export interface StaticHandlerContext { */ export interface StaticHandler { dataRoutes: AgnosticDataRouteObject[]; - query(request: Request): Promise; - queryRoute(request: Request, routeId?: string): Promise; + query( + request: Request, + opts?: { requestContext?: any } + ): Promise; + queryRoute( + request: Request, + opts?: { routeId?: string; requestContext?: any } + ): Promise; } /** @@ -1943,7 +1949,8 @@ export function unstable_createStaticHandler( * return it directly. */ async function query( - request: Request + request: Request, + { requestContext }: { requestContext?: any } = {} ): Promise { let url = new URL(request.url); let method = request.method.toLowerCase(); @@ -1987,7 +1994,7 @@ export function unstable_createStaticHandler( }; } - let result = await queryImpl(request, location, matches); + let result = await queryImpl(request, location, matches, requestContext); if (isResponse(result)) { return result; } @@ -2018,7 +2025,10 @@ export function unstable_createStaticHandler( * code. Examples here are 404 and 405 errors that occur prior to reaching * any user-defined loaders. */ - async function queryRoute(request: Request, routeId?: string): Promise { + async function queryRoute( + request: Request, + { routeId, requestContext }: { requestContext?: any; routeId?: string } = {} + ): Promise { let url = new URL(request.url); let method = request.method.toLowerCase(); let location = createLocation("", createPath(url), null, "default"); @@ -2045,7 +2055,13 @@ export function unstable_createStaticHandler( throw getInternalRouterError(404, { pathname: location.pathname }); } - let result = await queryImpl(request, location, matches, match); + let result = await queryImpl( + request, + location, + matches, + requestContext, + match + ); if (isResponse(result)) { return result; } @@ -2068,6 +2084,7 @@ export function unstable_createStaticHandler( request: Request, location: Location, matches: AgnosticDataRouteMatch[], + requestContext: any, routeMatch?: AgnosticDataRouteMatch ): Promise | Response> { invariant( @@ -2081,12 +2098,18 @@ export function unstable_createStaticHandler( request, matches, routeMatch || getTargetMatch(matches, location), + requestContext, routeMatch != null ); return result; } - let result = await loadRouteData(request, matches, routeMatch); + let result = await loadRouteData( + request, + matches, + requestContext, + routeMatch + ); return isResponse(result) ? result : { @@ -2117,6 +2140,7 @@ export function unstable_createStaticHandler( request: Request, matches: AgnosticDataRouteMatch[], actionMatch: AgnosticDataRouteMatch, + requestContext: any, isRouteRequest: boolean ): Promise | Response> { let result: DataResult; @@ -2142,7 +2166,8 @@ export function unstable_createStaticHandler( matches, basename, true, - isRouteRequest + isRouteRequest, + requestContext ); if (request.signal.aborted) { @@ -2192,9 +2217,15 @@ export function unstable_createStaticHandler( // Store off the pending error - we use it to determine which loaders // to call and will commit it when we complete the navigation let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id); - let context = await loadRouteData(request, matches, undefined, { - [boundaryMatch.route.id]: result.error, - }); + let context = await loadRouteData( + request, + matches, + requestContext, + undefined, + { + [boundaryMatch.route.id]: result.error, + } + ); // action status codes take precedence over loader status codes return { @@ -2211,7 +2242,7 @@ export function unstable_createStaticHandler( // Create a GET request for the loaders let loaderRequest = new Request(request.url, { signal: request.signal }); - let context = await loadRouteData(loaderRequest, matches); + let context = await loadRouteData(loaderRequest, matches, requestContext); return { ...context, @@ -2229,6 +2260,7 @@ export function unstable_createStaticHandler( async function loadRouteData( request: Request, matches: AgnosticDataRouteMatch[], + requestContext: any, routeMatch?: AgnosticDataRouteMatch, pendingActionError?: RouteData ): Promise< @@ -2277,7 +2309,8 @@ export function unstable_createStaticHandler( matches, basename, true, - isRouteRequest + isRouteRequest, + requestContext ) ), ]); @@ -2580,7 +2613,8 @@ async function callLoaderOrAction( matches: AgnosticDataRouteMatch[], basename = "/", isStaticRequest: boolean = false, - isRouteRequest: boolean = false + isRouteRequest: boolean = false, + requestContext: any = undefined ): Promise { let resultType; let result; @@ -2599,7 +2633,7 @@ async function callLoaderOrAction( ); result = await Promise.race([ - handler({ request, params: match.params }), + handler({ request, params: match.params, requestContext }), abortPromise, ]); @@ -2968,12 +3002,10 @@ function getInternalRouterError( pathname, routeId, method, - message, }: { pathname?: string; routeId?: string; method?: string; - message?: string; } = {} ) { let statusText = "Unknown Server Error"; diff --git a/packages/router/utils.ts b/packages/router/utils.ts index 9cb192a747..26edd29dd9 100644 --- a/packages/router/utils.ts +++ b/packages/router/utils.ts @@ -88,6 +88,7 @@ export interface Submission { interface DataFunctionArgs { request: Request; params: Params; + requestContext?: any; } /** From 17153401c14cb6d483a47b4b7f31701642c2f7b8 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 6 Dec 2022 15:21:02 -0500 Subject: [PATCH 2/4] add changeset --- .changeset/afraid-snakes-cough.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/afraid-snakes-cough.md diff --git a/.changeset/afraid-snakes-cough.md b/.changeset/afraid-snakes-cough.md new file mode 100644 index 0000000000..168a92b373 --- /dev/null +++ b/.changeset/afraid-snakes-cough.md @@ -0,0 +1,5 @@ +--- +"@remix-run/router": patch +--- + +Add `requestContext` support to static handler `query`/`queryRoute` From 8efea0a815d915707e5af24a2524c74a9ec38f4e Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 6 Dec 2022 16:48:35 -0500 Subject: [PATCH 3/4] PR feedback --- packages/router/__tests__/router-test.ts | 14 +++++++------- packages/router/router.ts | 21 ++++++++++++--------- packages/router/utils.ts | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/router/__tests__/router-test.ts b/packages/router/__tests__/router-test.ts index 0ebf30e088..8d5d58eae8 100644 --- a/packages/router/__tests__/router-test.ts +++ b/packages/router/__tests__/router-test.ts @@ -10955,17 +10955,17 @@ describe("a router", () => { ]); await query(createRequest("/child"), { requestContext }); - expect(arg(rootStub).requestContext.sessionId).toBe("12345"); - expect(arg(childStub).requestContext.sessionId).toBe("12345"); + expect(arg(rootStub).context.sessionId).toBe("12345"); + expect(arg(childStub).context.sessionId).toBe("12345"); actionStub.mockClear(); rootStub.mockClear(); childStub.mockClear(); await query(createSubmitRequest("/child"), { requestContext }); - expect(arg(actionStub).requestContext.sessionId).toBe("12345"); - expect(arg(rootStub).requestContext.sessionId).toBe("12345"); - expect(arg(childStub).requestContext.sessionId).toBe("12345"); + expect(arg(actionStub).context.sessionId).toBe("12345"); + expect(arg(rootStub).context.sessionId).toBe("12345"); + expect(arg(childStub).context.sessionId).toBe("12345"); }); describe("statusCode", () => { @@ -11935,13 +11935,13 @@ describe("a router", () => { routeId: "child", requestContext, }); - expect(arg(childStub).requestContext.sessionId).toBe("12345"); + expect(arg(childStub).context.sessionId).toBe("12345"); await queryRoute(createSubmitRequest("/child"), { routeId: "child", requestContext, }); - expect(arg(actionStub).requestContext.sessionId).toBe("12345"); + expect(arg(actionStub).context.sessionId).toBe("12345"); }); describe("Errors with Status Codes", () => { diff --git a/packages/router/router.ts b/packages/router/router.ts index cdcdb61e6e..2b97dd2e49 100644 --- a/packages/router/router.ts +++ b/packages/router/router.ts @@ -318,11 +318,11 @@ export interface StaticHandler { dataRoutes: AgnosticDataRouteObject[]; query( request: Request, - opts?: { requestContext?: any } + opts?: { requestContext?: unknown } ): Promise; queryRoute( request: Request, - opts?: { routeId?: string; requestContext?: any } + opts?: { routeId?: string; requestContext?: unknown } ): Promise; } @@ -1950,7 +1950,7 @@ export function unstable_createStaticHandler( */ async function query( request: Request, - { requestContext }: { requestContext?: any } = {} + { requestContext }: { requestContext?: unknown } = {} ): Promise { let url = new URL(request.url); let method = request.method.toLowerCase(); @@ -2027,7 +2027,10 @@ export function unstable_createStaticHandler( */ async function queryRoute( request: Request, - { routeId, requestContext }: { requestContext?: any; routeId?: string } = {} + { + routeId, + requestContext, + }: { requestContext?: unknown; routeId?: string } = {} ): Promise { let url = new URL(request.url); let method = request.method.toLowerCase(); @@ -2084,7 +2087,7 @@ export function unstable_createStaticHandler( request: Request, location: Location, matches: AgnosticDataRouteMatch[], - requestContext: any, + requestContext: unknown, routeMatch?: AgnosticDataRouteMatch ): Promise | Response> { invariant( @@ -2140,7 +2143,7 @@ export function unstable_createStaticHandler( request: Request, matches: AgnosticDataRouteMatch[], actionMatch: AgnosticDataRouteMatch, - requestContext: any, + requestContext: unknown, isRouteRequest: boolean ): Promise | Response> { let result: DataResult; @@ -2260,7 +2263,7 @@ export function unstable_createStaticHandler( async function loadRouteData( request: Request, matches: AgnosticDataRouteMatch[], - requestContext: any, + requestContext: unknown, routeMatch?: AgnosticDataRouteMatch, pendingActionError?: RouteData ): Promise< @@ -2614,7 +2617,7 @@ async function callLoaderOrAction( basename = "/", isStaticRequest: boolean = false, isRouteRequest: boolean = false, - requestContext: any = undefined + requestContext?: unknown ): Promise { let resultType; let result; @@ -2633,7 +2636,7 @@ async function callLoaderOrAction( ); result = await Promise.race([ - handler({ request, params: match.params, requestContext }), + handler({ request, params: match.params, context: requestContext }), abortPromise, ]); diff --git a/packages/router/utils.ts b/packages/router/utils.ts index 26edd29dd9..7faeea8913 100644 --- a/packages/router/utils.ts +++ b/packages/router/utils.ts @@ -88,7 +88,7 @@ export interface Submission { interface DataFunctionArgs { request: Request; params: Params; - requestContext?: any; + context?: any; } /** From 7f2649022e97025368ae016905a45290014f35e0 Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Tue, 6 Dec 2022 16:53:05 -0500 Subject: [PATCH 4/4] Add note on queryRoute change --- .changeset/afraid-snakes-cough.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/afraid-snakes-cough.md b/.changeset/afraid-snakes-cough.md index 168a92b373..84b44fcf75 100644 --- a/.changeset/afraid-snakes-cough.md +++ b/.changeset/afraid-snakes-cough.md @@ -3,3 +3,5 @@ --- Add `requestContext` support to static handler `query`/`queryRoute` + +- Note that the unstable API of `queryRoute(path, routeId)` has been changed to `queryRoute(path, { routeId, requestContext })`