Skip to content

Commit ce60786

Browse files
committed
Add single method route helpers and BuildRoute type
1 parent d29cfe2 commit ce60786

7 files changed

Lines changed: 134 additions & 91 deletions

File tree

‎AGENTS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
- **Classes**: Use native fields (omit `public`), `#private` for private members (no TypeScript accessibility modifiers)
2626
- **Formatting**: Prettier (printWidth: 100, no semicolons, single quotes, spaces not tabs)
2727
- **TypeScript**: Strict mode, ESNext target, ES2022 modules, bundler resolution, verbatimModuleSyntax
28+
- **Generics**: Use descriptive lowercase names for type parameters (e.g., `source`, `method`, `pattern`) instead of single uppercase letters like `T`, `P`, or `K`
2829
- **Comments**: Only add non-JSDoc comments when the code is doing something surprising or non-obvious
2930

3031
## Test Structure

‎packages/fetch-router/CHANGELOG.md‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,24 @@ This is the changelog for [`fetch-router`](https://github.com/remix-run/remix/tr
7272

7373
The `FormActionOptions` type has also been renamed to `FormOptions`.
7474

75+
- Add functional aliases for creating routes that respond to a single request method
76+
77+
```tsx
78+
import { del, get, patch, post } from '@remix-run/fetch-router'
79+
80+
let routes = route({
81+
home: get('/'),
82+
login: post('/login'),
83+
logout: post('/logout'),
84+
profile: {
85+
show: get('/profile'),
86+
edit: get('/profile/edit'),
87+
update: patch('/profile'),
88+
destroy: del('/profile'),
89+
},
90+
})
91+
```
92+
7593
## v0.12.0 (2025-11-25)
7694

7795
- BREAKING CHANGE: Moved all response helpers to `@remix-run/response`. Update your imports:
@@ -319,7 +337,6 @@ This is the changelog for [`fetch-router`](https://github.com/remix-run/remix/tr
319337
- Add support for `request.signal` abort, which now short-circuits the middleware chain. `router.fetch()` will now throw `DOMException` with `error.name === 'AbortError'` when a request is aborted
320338
- Fix an issue where `Router`'s `fetch` wasn't spec-compliant
321339
- Provide empty `context.formData` to `POST`/`PUT`/etc handlers when `parseFormData: false`
322-
- Add functional aliases for routes that respond to a single HTTP verb
323340

324341
## v0.6.0 (2025-10-10)
325342

‎packages/fetch-router/src/index.ts‎

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,35 +12,35 @@ export type { RequestHandler, BuildRequestHandler } from './lib/request-handler.
1212

1313
export type { Controller, Action, BuildAction } from './lib/controller.ts'
1414

15-
export {
16-
createDestroy,
17-
createDestroy as destroy, // shorthand
18-
createGet,
19-
createGet as get, // shorthand
20-
createHead,
21-
createHead as head, // shorthand
22-
createOptions,
23-
createOptions as options, // shorthand
24-
createPatch,
25-
createPatch as patch, // shorthand
26-
createPost,
27-
createPost as post, // shorthand
28-
createPut,
29-
createPut as put, // shorthand
30-
} from './lib/route-helpers.ts'
31-
3215
export {
3316
Route,
3417
createRoutes,
3518
createRoutes as route, // shorthand
3619
} from './lib/route-map.ts'
37-
export type { RouteMap, RouteDefs, RouteDef } from './lib/route-map.ts'
20+
export type { BuildRoute, RouteMap, RouteDefs, RouteDef } from './lib/route-map.ts'
3821

3922
export { createRouter } from './lib/router.ts'
4023
export type { Router, RouterOptions } from './lib/router.ts'
4124

4225
// Route helpers
4326

27+
export {
28+
createDeleteRoute,
29+
createDeleteRoute as del, // shorthand
30+
createGetRoute,
31+
createGetRoute as get, // shorthand
32+
createHeadRoute,
33+
createHeadRoute as head, // shorthand
34+
createOptionsRoute,
35+
createOptionsRoute as options, // shorthand
36+
createPatchRoute,
37+
createPatchRoute as patch, // shorthand
38+
createPostRoute,
39+
createPostRoute as post, // shorthand
40+
createPutRoute,
41+
createPutRoute as put, // shorthand
42+
} from './lib/route-helpers/method.ts'
43+
4444
export {
4545
createFormRoutes,
4646
createFormRoutes as form, // shorthand

‎packages/fetch-router/src/lib/route-helpers.ts‎

Lines changed: 0 additions & 58 deletions
This file was deleted.

packages/fetch-router/src/lib/route-helpers.test.ts renamed to packages/fetch-router/src/lib/route-helpers/method.test.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import * as assert from 'node:assert/strict'
22
import { describe, it } from 'node:test'
3-
4-
import type { Assert, IsEqual } from './type-utils.ts'
5-
import { Route, createRoutes as route } from './route-map.ts'
6-
import { destroy, get, head, options, patch, post, put } from '../index.ts'
73
import { RoutePattern } from '@remix-run/route-pattern'
84

5+
import type { Assert, IsEqual } from '../type-utils.ts'
6+
import { Route, createRoutes as route } from '../route-map.ts'
7+
import {
8+
createDeleteRoute as del,
9+
createGetRoute as get,
10+
createHeadRoute as head,
11+
createOptionsRoute as options,
12+
createPatchRoute as patch,
13+
createPostRoute as post,
14+
createPutRoute as put,
15+
} from './method.ts'
16+
917
describe('route helpers composition', () => {
1018
it('composes route helpers in a route map', () => {
1119
let routes = route({
@@ -16,7 +24,7 @@ describe('route helpers composition', () => {
1624
show: get('/posts/:id'),
1725
update: put('/posts/:id'),
1826
patch: patch('/posts/:id'),
19-
destroy: destroy('/posts/:id'),
27+
destroy: del('/posts/:id'),
2028
},
2129
api: {
2230
health: head('/api/health'),
@@ -42,7 +50,7 @@ describe('route helpers composition', () => {
4250
create: post('/'),
4351
show: get('/:id'),
4452
update: put('/:id'),
45-
destroy: destroy('/:id'),
53+
destroy: del('/:id'),
4654
},
4755
})
4856

@@ -64,7 +72,7 @@ describe('route helpers composition', () => {
6472
about: '/about',
6573
contact: get('/contact'),
6674
login: post('/auth/login'),
67-
logout: destroy('/auth/logout'),
75+
logout: del('/auth/logout'),
6876
profile: {
6977
show: '/profile',
7078
edit: get('/profile/edit'),
@@ -88,7 +96,7 @@ describe('route helpers composition', () => {
8896
posts: get('/api/posts(/:lang)'),
8997
createPost: post('/api/posts'),
9098
updatePost: put('/api/posts/:id'),
91-
deletePost: destroy('/api/posts/:id'),
99+
deletePost: del('/api/posts/:id'),
92100
},
93101
healthCheck: head('/health'),
94102
})
@@ -108,7 +116,7 @@ let composedRoutes = route({
108116
createPost: post('/'),
109117
showPost: get(':id'),
110118
updatePost: put(':id'),
111-
deletePost: destroy(':id'),
119+
deletePost: del(':id'),
112120
}),
113121
api: {
114122
health: head('/api/health'),
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import type { RoutePattern } from '@remix-run/route-pattern'
2+
3+
import { Route } from '../route-map.ts'
4+
5+
/**
6+
* Shorthand for a DELETE route.
7+
*/
8+
export function createDeleteRoute<source extends string>(
9+
pattern: source | RoutePattern<source>,
10+
): Route<'DELETE', source> {
11+
return new Route('DELETE', pattern)
12+
}
13+
14+
/**
15+
* Shorthand for a GET route.
16+
*/
17+
export function createGetRoute<source extends string>(
18+
pattern: source | RoutePattern<source>,
19+
): Route<'GET', source> {
20+
return new Route('GET', pattern)
21+
}
22+
23+
/**
24+
* Shorthand for a HEAD route.
25+
*/
26+
export function createHeadRoute<source extends string>(
27+
pattern: source | RoutePattern<source>,
28+
): Route<'HEAD', source> {
29+
return new Route('HEAD', pattern)
30+
}
31+
32+
/**
33+
* Shorthand for a OPTIONS route.
34+
*/
35+
export function createOptionsRoute<source extends string>(
36+
pattern: source | RoutePattern<source>,
37+
): Route<'OPTIONS', source> {
38+
return new Route('OPTIONS', pattern)
39+
}
40+
41+
/**
42+
* Shorthand for a PATCH route.
43+
*/
44+
export function createPatchRoute<source extends string>(
45+
pattern: source | RoutePattern<source>,
46+
): Route<'PATCH', source> {
47+
return new Route('PATCH', pattern)
48+
}
49+
50+
/**
51+
* Shorthand for a POST route.
52+
*/
53+
export function createPostRoute<source extends string>(
54+
pattern: source | RoutePattern<source>,
55+
): Route<'POST', source> {
56+
return new Route('POST', pattern)
57+
}
58+
59+
/**
60+
* Shorthand for a PUT route.
61+
*/
62+
export function createPutRoute<source extends string>(
63+
pattern: source | RoutePattern<source>,
64+
): Route<'PUT', source> {
65+
return new Route('PUT', pattern)
66+
}

‎packages/fetch-router/src/lib/route-map.ts‎

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ export class Route<
5858
}
5959
}
6060

61+
/**
62+
* Build a `Route` type from a request method and pattern.
63+
*/
64+
// prettier-ignore
65+
export type BuildRoute<method extends RequestMethod | 'ANY', pattern extends string | RoutePattern> =
66+
pattern extends string ? Route<method, pattern> :
67+
pattern extends RoutePattern<infer source extends string> ? Route<method, source> :
68+
never
69+
6170
/**
6271
* Create a route map from a set of route definitions.
6372
*
@@ -112,17 +121,17 @@ function buildRouteMap<base extends string, defs extends RouteDefs>(
112121
export type BuildRouteMap<base extends string, defs extends RouteDefs> = Simplify<{
113122
-readonly [name in keyof defs]: (
114123
defs[name] extends Route<infer method extends RequestMethod | 'ANY', infer pattern extends string> ? Route<method, Join<base, pattern>> :
115-
defs[name] extends RouteDef ? BuildRoute<base, defs[name]> :
124+
defs[name] extends RouteDef ? BuildRouteWithBase<base, defs[name]> :
116125
defs[name] extends RouteDefs ? BuildRouteMap<base, defs[name]> :
117126
never
118127
)
119128
}>
120129

121130
// prettier-ignore
122-
type BuildRoute<base extends string, defs extends RouteDef> =
123-
defs extends string ? Route<'ANY', Join<base, defs>> :
124-
defs extends RoutePattern<infer pattern extends string> ? Route<'ANY', Join<base, pattern>> :
125-
defs extends { method: infer method extends RequestMethod | 'ANY', pattern: infer pattern } ? (
131+
type BuildRouteWithBase<base extends string, def extends RouteDef> =
132+
def extends string ? Route<'ANY', Join<base, def>> :
133+
def extends RoutePattern<infer pattern extends string> ? Route<'ANY', Join<base, pattern>> :
134+
def extends { method: infer method extends RequestMethod | 'ANY', pattern: infer pattern } ? (
126135
pattern extends string ? Route<method, Join<base, pattern>> :
127136
pattern extends RoutePattern<infer source extends string> ? Route<method, Join<base, source>> :
128137
never

0 commit comments

Comments
 (0)