Skip to content

Commit d76f76d

Browse files
authored
Simplify fetch-router action and middleware types
1 parent aee2ee0 commit d76f76d

20 files changed

Lines changed: 129 additions & 132 deletions

File tree

demos/bookstore/app/middleware/asset-entry.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type SetAssetEntryContextTransform = readonly [readonly [typeof assetsEntryKey,
2020
export function loadAssetEntry(
2121
scriptEntry = defaultScriptEntry,
2222
stylesheetEntry = defaultStylesheetEntry,
23-
): Middleware<any, any, SetAssetEntryContextTransform> {
23+
): Middleware<any, SetAssetEntryContextTransform> {
2424
return async (context, next) => {
2525
let [scriptSrc, scriptPreloads, stylesheetHref] = await Promise.all([
2626
assetServer.getHref(scriptEntry),

demos/bookstore/app/middleware/database.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { db } from '../data/setup.ts'
55

66
type SetDatabaseContextTransform = readonly [readonly [typeof Database, Database]]
77

8-
export function loadDatabase(): Middleware<'ANY', {}, SetDatabaseContextTransform> {
8+
export function loadDatabase(): Middleware<{}, SetDatabaseContextTransform> {
99
return async (context, next) => {
1010
context.set(Database, db)
1111
return next()

demos/frames/app/actions/root-reload-client-entries.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { BuildAction } from 'remix/fetch-router'
1+
import type { Action } from 'remix/fetch-router'
22
import type { Handle } from 'remix/ui'
33

44
import {
@@ -28,7 +28,7 @@ export const rootReloadClientEntriesAction = {
2828
{ request: context.request, router: context.router },
2929
)
3030
},
31-
} satisfies BuildAction<typeof routes.rootReloadClientEntries>
31+
} satisfies Action<typeof routes.rootReloadClientEntries>
3232

3333
type RootReloadClientEntriesPageProps = {
3434
includeRemoved: boolean

demos/social-auth/app/middleware/database.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { db } from '../data/setup.ts'
55

66
type SetDatabaseContextTransform = readonly [readonly [typeof Database, Database]]
77

8-
export function loadDatabase(): Middleware<'ANY', {}, SetDatabaseContextTransform> {
8+
export function loadDatabase(): Middleware<{}, SetDatabaseContextTransform> {
99
return async (context, next) => {
1010
context.set(Database, db)
1111
return next()

packages/auth-middleware/src/lib/auth-types.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, it } from '@remix-run/test'
22

33
import {
44
createRouter,
5-
type BuildAction,
5+
type Action,
66
type Controller,
77
type GetContextValue,
88
type MiddlewareContext,
@@ -109,7 +109,7 @@ const privateAction = {
109109

110110
return new Response('Private')
111111
},
112-
} satisfies BuildAction<typeof routes.private, ProtectedAppContext>
112+
} satisfies Action<typeof routes.private, ProtectedAppContext>
113113

114114
const adminController = {
115115
actions: {

packages/auth-middleware/src/lib/auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export interface AuthOptions<schemes extends readonly AuthScheme<any>[] = AuthSc
135135
*/
136136
export function auth<schemes extends readonly AuthScheme<any>[]>(
137137
options: AuthOptions<schemes>,
138-
): Middleware<any, any, SetAuthContextTransform<AuthForSchemes<schemes>>> {
138+
): Middleware<any, SetAuthContextTransform<AuthForSchemes<schemes>>> {
139139
if (options.schemes.length === 0) {
140140
throw new Error('auth() requires at least one authentication scheme')
141141
}

packages/auth-middleware/src/lib/require-auth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export interface RequireAuthOptions {
4646
*/
4747
export function requireAuth<identity = unknown>(
4848
options: RequireAuthOptions = {},
49-
): Middleware<any, any, RequireAuthContextTransform<identity>> {
49+
): Middleware<any, RequireAuthContextTransform<identity>> {
5050
return async (context, next) => {
5151
let auth = context.get(Auth)
5252
if (auth == null) {

packages/auth-middleware/src/lib/schemes/session.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ describe('createSessionAuthScheme scheme', () => {
109109

110110
it('fails and invalidates when verify() returns null', async () => {
111111
let invalidated = false
112-
let setSession: Middleware<any, any, SetSessionContextTransform> = (context, next) => {
112+
let setSession: Middleware<any, SetSessionContextTransform> = (context, next) => {
113113
let session = createSession()
114114
session.set('auth', { userId: 'u1' })
115115
context.set(Session, session)

packages/fetch-router/.changes/minor.action-route-entry-types.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
BREAKING CHANGE: Simplified route action helper types so `Action` and `BuildAction` no longer accept an unused request method type parameter. If you manually type route actions, pass only the route pattern or route object plus any request context type:
1+
BREAKING CHANGE: Simplified route action and middleware helper types so they no longer accept unused request method type parameters. If you manually type route actions, pass only the route pattern or route object plus any request context type:
22

33
```ts
44
// before
@@ -13,7 +13,7 @@ let action = {
1313
handler(context) {
1414
return new Response(context.params.id)
1515
},
16-
} satisfies BuildAction<typeof routes.account, AppContext>
16+
} satisfies Action<typeof routes.account, AppContext>
1717
```
1818

1919
Renamed the custom router matcher payload type from `MatchData` to `RouteEntry`:
@@ -25,3 +25,41 @@ let matcher = createMatcher<MatchData>()
2525
// after
2626
let matcher = createMatcher<RouteEntry>()
2727
```
28+
29+
If you manually annotate middleware, pass only the params type and context transform type:
30+
31+
```ts
32+
// before
33+
let middleware: Middleware<'ANY', {}, SetDatabaseContextTransform>
34+
35+
// after
36+
let middleware: Middleware<{}, SetDatabaseContextTransform>
37+
```
38+
39+
`Action` now accepts string patterns, `RoutePattern` objects, and `Route` objects directly. It also owns the optional action middleware tuple generic, so `BuildAction` is no longer exported.
40+
41+
For stored action objects with action-local middleware, the handler context can now be derived from the middleware tuple that actually runs. Previously, the action had to repeat the middleware's type effect with a manually refined context type. That was type-safe only as long as the manual context type and the runtime middleware stayed in sync:
42+
43+
```ts
44+
// before
45+
type AuthenticatedAppContext = WithRequiredAuth<AppContext, AuthIdentity>
46+
47+
let action = {
48+
middleware: [requireAuth<AuthIdentity>()],
49+
handler(context) {
50+
let auth = context.get(Auth)
51+
return Response.json(auth.identity)
52+
},
53+
} satisfies BuildAction<'GET', typeof routes.account, AuthenticatedAppContext>
54+
55+
// after
56+
let accountMiddleware = [requireAuth<AuthIdentity>()] as const
57+
58+
let action = {
59+
middleware: accountMiddleware,
60+
handler(context) {
61+
let auth = context.get(Auth)
62+
return Response.json(auth.identity)
63+
},
64+
} satisfies Action<typeof routes.account, AppContext, typeof accountMiddleware>
65+
```

packages/fetch-router/README.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -629,8 +629,8 @@ Route params are only half of a handler's type contract. In many apps, handlers
629629
`fetch-router` now lets you carry that context contract through the router, controller, and action types directly. A common pattern is to derive one app-local context type from your router middleware, then reuse it across stored controllers and actions.
630630

631631
```ts
632-
import { Auth, requireAuth, type WithRequiredAuth } from 'remix/auth-middleware'
633-
import { type BuildAction, type RequestContext, type WithParams } from 'remix/fetch-router'
632+
import { Auth, requireAuth } from 'remix/auth-middleware'
633+
import { type Action, type RequestContext, type WithParams } from 'remix/fetch-router'
634634
import { route } from 'remix/routes'
635635

636636
let routes = route({
@@ -641,21 +641,18 @@ type AppContext<params extends Record<string, string> = {}> = WithParams<Request
641641

642642
type AuthIdentity = { id: string }
643643

644-
type AuthenticatedAppContext<params extends Record<string, string> = {}> = WithRequiredAuth<
645-
AppContext<params>,
646-
AuthIdentity
647-
>
644+
let accountMiddleware = [requireAuth<AuthIdentity>()] as const
648645

649646
let accountAction = {
650-
middleware: [requireAuth<AuthIdentity>()],
647+
middleware: accountMiddleware,
651648
handler(context) {
652649
let auth = context.get(Auth)
653650
return Response.json({ id: auth.identity.id })
654651
},
655-
} satisfies BuildAction<typeof routes.account, AuthenticatedAppContext>
652+
} satisfies Action<typeof routes.account, AppContext, typeof accountMiddleware>
656653
```
657654

658-
In this example, the action declares the stronger context it requires, and the action-local middleware makes that contract true at runtime. In a larger app, you can still derive a shared base context from router middleware with `MiddlewareContext<typeof middleware>` and build on top of it the same way.
655+
In this example, the action-local middleware tuple gives the handler the stronger context it requires and makes that contract true at runtime. In a larger app, you can still derive a shared base context from router middleware with `MiddlewareContext<typeof middleware>` and build on top of it the same way.
659656

660657
#### Middleware Provider Guidance
661658

0 commit comments

Comments
 (0)