remix 3.0.0-beta.2 → 3.0.0-beta.4

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.
Files changed (81) hide show
  1. package/dist/headers/accept-encoding.d.ts +2 -0
  2. package/dist/headers/accept-encoding.d.ts.map +1 -0
  3. package/dist/headers/accept-encoding.js +2 -0
  4. package/dist/headers/accept-language.d.ts +2 -0
  5. package/dist/headers/accept-language.d.ts.map +1 -0
  6. package/dist/headers/accept-language.js +2 -0
  7. package/dist/headers/accept.d.ts +2 -0
  8. package/dist/headers/accept.d.ts.map +1 -0
  9. package/dist/headers/accept.js +2 -0
  10. package/dist/headers/cache-control.d.ts +2 -0
  11. package/dist/headers/cache-control.d.ts.map +1 -0
  12. package/dist/headers/cache-control.js +2 -0
  13. package/dist/headers/content-disposition.d.ts +2 -0
  14. package/dist/headers/content-disposition.d.ts.map +1 -0
  15. package/dist/headers/content-disposition.js +2 -0
  16. package/dist/headers/content-range.d.ts +2 -0
  17. package/dist/headers/content-range.d.ts.map +1 -0
  18. package/dist/headers/content-range.js +2 -0
  19. package/dist/headers/content-type.d.ts +2 -0
  20. package/dist/headers/content-type.d.ts.map +1 -0
  21. package/dist/headers/content-type.js +2 -0
  22. package/dist/headers/cookie.d.ts +2 -0
  23. package/dist/headers/cookie.d.ts.map +1 -0
  24. package/dist/headers/cookie.js +2 -0
  25. package/dist/headers/if-match.d.ts +2 -0
  26. package/dist/headers/if-match.d.ts.map +1 -0
  27. package/dist/headers/if-match.js +2 -0
  28. package/dist/headers/if-none-match.d.ts +2 -0
  29. package/dist/headers/if-none-match.d.ts.map +1 -0
  30. package/dist/headers/if-none-match.js +2 -0
  31. package/dist/headers/if-range.d.ts +2 -0
  32. package/dist/headers/if-range.d.ts.map +1 -0
  33. package/dist/headers/if-range.js +2 -0
  34. package/dist/headers/range.d.ts +2 -0
  35. package/dist/headers/range.d.ts.map +1 -0
  36. package/dist/headers/range.js +2 -0
  37. package/dist/headers/raw-headers.d.ts +2 -0
  38. package/dist/headers/raw-headers.d.ts.map +1 -0
  39. package/dist/headers/raw-headers.js +2 -0
  40. package/dist/headers/set-cookie.d.ts +2 -0
  41. package/dist/headers/set-cookie.d.ts.map +1 -0
  42. package/dist/headers/set-cookie.js +2 -0
  43. package/dist/headers/vary.d.ts +2 -0
  44. package/dist/headers/vary.d.ts.map +1 -0
  45. package/dist/headers/vary.js +2 -0
  46. package/package.json +96 -35
  47. package/src/assert/README.md +11 -4
  48. package/src/csrf-middleware/README.md +5 -12
  49. package/src/data-schema/README.md +3 -9
  50. package/src/data-table/README.md +6 -14
  51. package/src/data-table-mysql/README.md +5 -11
  52. package/src/data-table-postgres/README.md +2 -4
  53. package/src/data-table-sqlite/README.md +2 -4
  54. package/src/fetch-proxy/README.md +1 -2
  55. package/src/fetch-router/README.md +115 -35
  56. package/src/file-storage-s3/README.md +1 -2
  57. package/src/form-data-middleware/README.md +1 -2
  58. package/src/form-data-parser/README.md +2 -4
  59. package/src/headers/README.md +10 -0
  60. package/src/headers/accept-encoding.ts +2 -0
  61. package/src/headers/accept-language.ts +2 -0
  62. package/src/headers/accept.ts +2 -0
  63. package/src/headers/cache-control.ts +2 -0
  64. package/src/headers/content-disposition.ts +2 -0
  65. package/src/headers/content-range.ts +2 -0
  66. package/src/headers/content-type.ts +2 -0
  67. package/src/headers/cookie.ts +2 -0
  68. package/src/headers/if-match.ts +2 -0
  69. package/src/headers/if-none-match.ts +2 -0
  70. package/src/headers/if-range.ts +2 -0
  71. package/src/headers/range.ts +2 -0
  72. package/src/headers/raw-headers.ts +2 -0
  73. package/src/headers/set-cookie.ts +2 -0
  74. package/src/headers/vary.ts +2 -0
  75. package/src/node-fetch-server/README.md +1 -2
  76. package/src/node-tsx/README.md +3 -8
  77. package/src/route-pattern/README.md +18 -13
  78. package/src/session-storage-redis/README.md +1 -2
  79. package/src/test/README.md +29 -5
  80. package/src/ui/anchor/README.md +11 -3
  81. package/src/ui/menu/README.md +26 -2
@@ -8,7 +8,7 @@ A minimal, composable router built on the [web Fetch API](https://developer.mozi
8
8
  - **Type-Safe Routing**: Leverage TypeScript for compile-time route validation and parameter inference
9
9
  - **Typed Request Context**: Carry request-scoped context through routers, controllers, and actions
10
10
  - **Declarative Route Maps**: Define your route structure upfront with type-safe route names and request methods
11
- - **Flexible Middleware**: Apply middleware globally, per-route, or to controllers
11
+ - **Flexible Middleware**: Use router, controller, and action middleware for each request boundary
12
12
  - **Easy Testing**: Use standard `fetch()` to test your routes - no special test harness required
13
13
 
14
14
  ## Installation
@@ -237,6 +237,85 @@ router.map(routes.contact, {
237
237
  })
238
238
  ```
239
239
 
240
+ ### Composing Route Groups
241
+
242
+ As applications grow, it is useful to let one file own the routes for a specific area of the app while the top-level router decides where that area is mounted. Use `router.mount()` with a route installer to register a group of routes under a route pattern prefix.
243
+
244
+ A route installer receives a `RouteBuilder`, not a full `Router`, so it can register routes but cannot dispatch requests. The parent router remains the only router that owns request dispatch, the matcher, and the default handler.
245
+
246
+ ```ts
247
+ import { createRouter, type RouteBuilder } from 'remix/router'
248
+ import { get, route } from 'remix/routes'
249
+
250
+ const adminRouteDefs = {
251
+ index: get('/'),
252
+ users: {
253
+ show: get('/users/:id'),
254
+ },
255
+ }
256
+
257
+ // Use relative route groups inside installers. These routes are registered below
258
+ // the mount prefix when `installAdminRoutes()` runs.
259
+ const adminRouteGroup = route(adminRouteDefs)
260
+
261
+ // Use full app routes for links and redirects.
262
+ const routes = {
263
+ admin: route('/admin', adminRouteDefs),
264
+ }
265
+
266
+ function installAdminRoutes<context extends AppContext>(router: RouteBuilder<context>) {
267
+ router.map(adminRouteGroup, {
268
+ actions: {
269
+ index() {
270
+ return new Response('Admin')
271
+ },
272
+ },
273
+ })
274
+
275
+ router.map(adminRouteGroup.users, {
276
+ actions: {
277
+ show({ params, currentUser }) {
278
+ return new Response(`User ${params.id} for ${currentUser.id}`)
279
+ },
280
+ },
281
+ })
282
+ }
283
+
284
+ let router = createRouter<AppContext>({ middleware })
285
+
286
+ router.mount('/admin', installAdminRoutes)
287
+ ```
288
+
289
+ Mount prefixes are route patterns. They compose with routes registered inside the installer using `joinPatterns()`, so params from the mount prefix are available to mounted handlers:
290
+
291
+ ```ts
292
+ router.mount('/orgs/:orgId', (org) => {
293
+ org.get('/users/:userId', ({ params }) => {
294
+ // params is { orgId: string, userId: string }
295
+ return new Response(`${params.orgId}:${params.userId}`)
296
+ })
297
+ })
298
+ ```
299
+
300
+ When a mount prefix and child route use the same param name, the right-most route param wins, matching `route-pattern` behavior.
301
+
302
+ Middleware stays on routers, controllers, and actions. If an entire route group needs auth or another boundary, put that middleware on the controllers or actions owned by the installer:
303
+
304
+ ```ts
305
+ function installAdminRoutes<context extends AppContext>(router: RouteBuilder<context>) {
306
+ router.map(adminRouteGroup, {
307
+ middleware: [requireAdmin()],
308
+ actions: {
309
+ index({ admin }) {
310
+ return new Response(admin.id)
311
+ },
312
+ },
313
+ })
314
+ }
315
+ ```
316
+
317
+ Unknown paths below a mounted prefix fall through to the parent router's default handler. If a route group needs its own catch-all response, register one explicitly inside the installer.
318
+
240
319
  ### Declaring Routes
241
320
 
242
321
  In addition to the `{ method, pattern }` syntax shown above, the router provides a few shorthand methods that help eliminate some of the boilerplate when building complex route maps:
@@ -504,10 +583,12 @@ type Routes = typeof routes
504
583
 
505
584
  Middleware functions run code before and/or after actions. They are a powerful way to add functionality to your app.
506
585
 
586
+ Every middleware must either return a `Response`, return the response from `next()`, or call `await next()` before it returns. Middleware that returns without calling `next()` throws at runtime.
587
+
507
588
  A basic logging middleware might look like this:
508
589
 
509
590
  ```ts
510
- import type { Middleware } from 'remix/router'
591
+ import { type Middleware } from 'remix/router'
511
592
 
512
593
  // You can use the `Middleware` type to type middleware functions.
513
594
  function logger(): Middleware {
@@ -567,8 +648,9 @@ function loadDatabase(): Middleware<{
567
648
  value: Database
568
649
  property: 'db'
569
650
  }> {
570
- return async (context) => {
651
+ return async (context, next) => {
571
652
  context.set(Database, await connectDatabase(), { property: 'db' })
653
+ return next()
572
654
  }
573
655
  }
574
656
 
@@ -580,13 +662,13 @@ router.get('/books', async (context) => {
580
662
 
581
663
  Use `context.db` (or `context.get(Database)`). If two values use the same property name, the router throws.
582
664
 
583
- Middleware may be used at three levels: globally on the router, on a controller, or inline on an individual action.
665
+ Middleware has three API-owned forms: router middleware, controller middleware, and action middleware.
584
666
 
585
- Global middleware is added to the router when it is created using the `createRouter({ middleware })` option. This middleware runs before any routes are matched and is useful for doing things like logging, serving static files, profiling, and a variety of other things. Global middleware runs on every request, so it's important to keep them lightweight and fast.
667
+ Router middleware is added to the router when it is created using the `createRouter({ middleware })` option. This middleware runs before any routes are matched and is useful for doing things like logging, serving static files, profiling, and a variety of other things. Router middleware runs on every request, so it's important to keep it lightweight and fast.
586
668
 
587
- Controller middleware runs for every direct action in a controller. Action middleware runs only for one action, whether that action is registered in a controller or directly with `router.map()` or one of the method-specific helpers like `router.get()`, `router.post()`, `router.put()`, `router.delete()`, etc. The object form for actions is `{ handler, middleware? }`, so you can omit `middleware` entirely when you do not need it.
669
+ Controller middleware runs for every direct action in a controller. Action middleware runs only for one action, whether that action is created with `createAction()`, registered in a controller, or registered directly with `router.map()` or one of the method-specific helpers like `router.get()`, `router.post()`, `router.put()`, `router.delete()`, etc. The object form for actions is `{ handler, middleware? }`, so you can omit `middleware` entirely when you do not need it.
588
670
 
589
- A controller's `middleware` applies only to the direct route actions in that controller, and its `actions` object may not include nested route-map keys. Map nested route maps explicitly so each controller owns the direct route actions for one route map.
671
+ A controller's `middleware` applies only to the direct route actions in that controller, and its `actions` object may not include nested route-map keys. This is the router's scoped middleware model: map nested route maps explicitly so each controller owns the direct route actions for one route map, and share middleware arrays between controllers that need the same boundary.
590
672
 
591
673
  ```tsx
592
674
  let routes = route({
@@ -598,21 +680,21 @@ let routes = route({
598
680
  })
599
681
 
600
682
  let router = createRouter({
601
- // This middleware runs on all requests.
683
+ // Router middleware runs on all requests.
602
684
  middleware: [staticFiles('./public')],
603
685
  })
604
686
 
605
687
  router.map(routes.home, () => new Response('Home'))
606
688
 
607
689
  router.map(routes.admin, {
608
- // This middleware applies to all actions in this controller.
690
+ // Controller middleware applies to all direct actions in this controller.
609
691
  middleware: [auth({ token: 'secret' })],
610
692
  actions: {
611
693
  dashboard() {
612
694
  return new Response('Dashboard')
613
695
  },
614
696
  settings: {
615
- // This middleware applies only to this action.
697
+ // Action middleware applies only to this action.
616
698
  middleware: [requireAdmin()],
617
699
  handler() {
618
700
  return new Response('Settings')
@@ -655,17 +737,11 @@ router.get('/posts/:id', (context) => {
655
737
 
656
738
  Route params are only half of a handler's type contract. In many apps, handlers also depend on values that middleware loads into request context, like sessions, database connections, or authenticated users.
657
739
 
658
- `fetch-router` lets you carry that context contract through the router and into stored controllers and actions. A common pattern is to derive one app-local context type from your router middleware, augment `RouterTypes.context` with it, then use `createAction()` and `createController()` to type stored handlers.
740
+ `fetch-router` lets you carry that context contract through the router and into direct route registration, stored controllers, and stored actions. A common pattern is to derive one application context type from your middleware, augment `RouterTypes.context` with it, then use `createAction()` and `createController()` to type stored handlers.
659
741
 
660
742
  ```ts
661
743
  import { Auth, requireAuth } from 'remix/middleware/auth'
662
- import {
663
- createAction,
664
- createController,
665
- type AnyParams,
666
- type ContextWithParams,
667
- type MiddlewareContext,
668
- } from 'remix/router'
744
+ import { createAction, createController, createRouter, type RouterContext } from 'remix/router'
669
745
  import { route } from 'remix/routes'
670
746
  import { loadDatabase } from './middleware/database.ts'
671
747
  import { loadSession } from './middleware/session.ts'
@@ -675,12 +751,12 @@ let routes = route({
675
751
  })
676
752
 
677
753
  type AuthIdentity = { id: string }
678
- type RootMiddleware = [ReturnType<typeof loadSession>, ReturnType<typeof loadDatabase>]
679
754
 
680
- type AppContext<params extends AnyParams = {}> = ContextWithParams<
681
- MiddlewareContext<RootMiddleware>,
682
- params
683
- >
755
+ export const router = createRouter({
756
+ middleware: [loadSession(), loadDatabase()],
757
+ })
758
+
759
+ type AppContext = RouterContext<typeof router>
684
760
 
685
761
  declare module 'remix/router' {
686
762
  interface RouterTypes {
@@ -688,19 +764,16 @@ declare module 'remix/router' {
688
764
  }
689
765
  }
690
766
 
691
- let accountMiddleware = [requireAuth<AuthIdentity>()] as const
692
- type AccountContext = MiddlewareContext<typeof accountMiddleware, AppContext>
693
-
694
- let accountAction = createAction<typeof routes.account, AccountContext>(routes.account, {
695
- middleware: accountMiddleware,
767
+ let accountAction = createAction(routes.account, {
768
+ middleware: [requireAuth<AuthIdentity>()],
696
769
  handler(context) {
697
770
  let auth = context.get(Auth)
698
771
  return Response.json({ id: auth.identity.id })
699
772
  },
700
773
  })
701
774
 
702
- let accountController = createController<typeof routes, AccountContext>(routes, {
703
- middleware: accountMiddleware,
775
+ let accountController = createController(routes, {
776
+ middleware: [requireAuth<AuthIdentity>()],
704
777
  actions: {
705
778
  account(context) {
706
779
  let auth = context.get(Auth)
@@ -710,11 +783,13 @@ let accountController = createController<typeof routes, AccountContext>(routes,
710
783
  })
711
784
  ```
712
785
 
713
- In this example, `RootMiddleware` is the middleware tuple that defines the app context contract. It should include middleware instances. When a middleware is created by a factory function like `loadSession()`, use `ReturnType<typeof loadSession>` so the type describes the middleware value that actually runs. `AccountContext` applies local account middleware on top of that base context before the handler runs.
786
+ In this example, the router's inline middleware array defines the app context contract. `RouterContext<typeof router>` extracts the request context that the router provides, so `RouterTypes.context` can use that context without storing the middleware chain separately.
787
+
788
+ Prefer plain inline arrays for `middleware` options on routers, controllers, actions, and route helpers. Inline arrays already give TypeScript enough information to infer middleware-provided context for downstream handlers, so `createAction()` and direct action objects see action middleware context, and `createController()` sees controller middleware context without `createMiddleware()`.
714
789
 
715
- For small apps with a stable tuple-typed runtime array, `MiddlewareContext<typeof middleware>` is a fine shortcut. For larger apps, prefer the named `RootMiddleware` and `AppContext` pattern so runtime middleware assembly and context typing can evolve independently.
790
+ Use `createMiddleware()` only when a middleware chain is stored somewhere and its exact tuple type needs to survive that boundary. The common cases are deriving `MiddlewareContext<typeof rootMiddleware>` without a router value, exporting a reusable chain, or returning a chain from a factory. A standalone array like `let middleware = [loadSession(), loadDatabase()]` widens to a normal array, so `MiddlewareContext<typeof middleware>` cannot derive the ordered middleware context.
716
791
 
717
- When manually annotating stored handlers, use `Action<typeof route, Context>` for values that may be either a plain handler function or an action object with optional middleware.
792
+ When manually annotating stored handlers with `Action<typeof route, Context>` or `Controller<typeof routes, Context>`, compose any action or controller middleware chain into `Context` with `MiddlewareContext<typeof actionOrControllerMiddleware, AppContext>`.
718
793
 
719
794
  #### Middleware Provider Guidance
720
795
 
@@ -729,7 +804,12 @@ If you're authoring a middleware package that stores values in request context,
729
804
  Apps can derive request context from the middleware tuple with `MiddlewareContext`. If they need to describe a context shape without a middleware tuple, they can use the core `ContextWithEntry` and `ContextWithEntries` helpers directly.
730
805
 
731
806
  ```ts
732
- import { createContextKey, type Middleware, type MiddlewareContext } from 'remix/router'
807
+ import {
808
+ createContextKey,
809
+ createMiddleware,
810
+ type Middleware,
811
+ type MiddlewareContext,
812
+ } from 'remix/router'
733
813
 
734
814
  // The context key that consumers will need to read from `context.get(...)`
735
815
  export const CurrentUser = createContextKey<User | null>()
@@ -747,7 +827,7 @@ export function loadCurrentUser(): Middleware<{
747
827
  }
748
828
  }
749
829
 
750
- let middleware = [loadCurrentUser()] as const
830
+ let middleware = createMiddleware(loadCurrentUser())
751
831
  type AppContext = MiddlewareContext<typeof middleware>
752
832
 
753
833
  // Use context.currentUser (or context.get(CurrentUser)).
@@ -1,7 +1,6 @@
1
1
  # file-storage-s3
2
2
 
3
- S3 backend for [`remix/file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage).
4
- Use this package when you want the `FileStorage` API backed by AWS S3 or an S3-compatible provider.
3
+ S3 backend for [`remix/file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage). Use this package when you want the `FileStorage` API backed by AWS S3 or an S3-compatible provider.
5
4
 
6
5
  ## Features
7
6
 
@@ -69,8 +69,7 @@ let router = createRouter({
69
69
 
70
70
  ### Limit Multipart Growth
71
71
 
72
- `formData()` forwards multipart limit options to `parseFormData()`, so you can cap uploads with
73
- `maxHeaderSize`, `maxFiles`, `maxFileSize`, `maxParts`, and `maxTotalSize`.
72
+ `formData()` forwards multipart limit options to `parseFormData()`, so you can cap uploads with `maxHeaderSize`, `maxFiles`, `maxFileSize`, `maxParts`, and `maxTotalSize`.
74
73
 
75
74
  ```ts
76
75
  let router = createRouter({
@@ -72,8 +72,7 @@ async function requestHandler(request: Request) {
72
72
  }
73
73
  ```
74
74
 
75
- To validate the resulting `FormData` object with `remix/data-schema`, use the
76
- `remix/data-schema/form-data` helpers.
75
+ To validate the resulting `FormData` object with `remix/data-schema`, use the `remix/data-schema/form-data` helpers.
77
76
 
78
77
  To limit the overall shape of multipart requests, use the `maxHeaderSize`, `maxFileSize`, `maxFiles`, `maxParts`, and `maxTotalSize` options. By default, `parseFormData()` uses `maxFiles = 20`, `maxParts = 1000`, and `maxTotalSize = maxFiles * maxFileSize + 1 MiB`.
79
78
 
@@ -150,8 +149,7 @@ The [`demos` directory](https://github.com/remix-run/remix/tree/main/packages/fo
150
149
 
151
150
  ## Related Packages
152
151
 
153
- - [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Tiny,
154
- standards-aligned validation with a `form-data` export for `FormData` and `URLSearchParams`
152
+ - [`data-schema`](https://github.com/remix-run/remix/tree/main/packages/data-schema) - Tiny, standards-aligned validation with a `form-data` export for `FormData` and `URLSearchParams`
155
153
  - [`file-storage`](https://github.com/remix-run/remix/tree/main/packages/file-storage) - A simple key/value interface for storing `FileUpload` objects you get from the parser
156
154
  - [`multipart-parser`](https://github.com/remix-run/remix/tree/main/packages/multipart-parser) - The parser used internally for parsing `multipart/form-data` HTTP messages
157
155
 
@@ -94,6 +94,16 @@ The following headers are currently supported:
94
94
  - [Set-Cookie](./README.md#set-cookie)
95
95
  - [Vary](./README.md#vary)
96
96
 
97
+ If you only need a specific header parser (for example, just `Content-Type`), import that parser directly from its subpath. This avoids pulling the package barrel and `SuperHeaders`:
98
+
99
+ ```ts
100
+ import { ContentType } from 'remix/headers/content-type'
101
+ import { SetCookie } from 'remix/headers/set-cookie'
102
+
103
+ let contentType = ContentType.from('text/plain; charset=utf-8')
104
+ let setCookie = new SetCookie('session=abc; Path=/')
105
+ ```
106
+
97
107
  ### Accept
98
108
 
99
109
  Parse, manipulate and stringify [`Accept` headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept).
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/accept-encoding'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/accept-language'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/accept'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/cache-control'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/content-disposition'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/content-range'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/content-type'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/cookie'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/if-match'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/if-none-match'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/if-range'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/range'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/raw-headers'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/set-cookie'
@@ -0,0 +1,2 @@
1
+ // IMPORTANT: This file is auto-generated, please do not edit manually.
2
+ export * from '@remix-run/headers/vary'
@@ -124,8 +124,7 @@ async function handler(request: Request) {
124
124
 
125
125
  ### Custom Hostname Configuration
126
126
 
127
- Configure custom hostnames for deployment on VPS or custom environments. `node-fetch-server` uses
128
- the `host` option when constructing `request.url`.
127
+ Configure custom hostnames for deployment on VPS or custom environments. `node-fetch-server` uses the `host` option when constructing `request.url`.
129
128
 
130
129
  ```ts
131
130
  import * as http from 'node:http'
@@ -2,13 +2,9 @@
2
2
 
3
3
  Run Node.js with TypeScript and JSX syntax support in `.ts`, `.tsx`, and `.jsx` files.
4
4
 
5
- `node-tsx` transforms supported source files before Node.js executes them, including
6
- TypeScript syntax that requires JavaScript code generation such as enums, runtime
7
- namespaces, and parameter properties. JSX compiler options are read from the nearest
8
- `tsconfig.json` for each loaded file.
5
+ `node-tsx` transforms supported source files before Node.js executes them, including TypeScript syntax that requires JavaScript code generation such as enums, runtime namespaces, and parameter properties. JSX compiler options are read from the nearest `tsconfig.json` for each loaded file.
9
6
 
10
- The loader does not type check, change Node.js module resolution, apply TypeScript
11
- path aliases, or downlevel JavaScript syntax for older runtimes.
7
+ The loader does not type check, change Node.js module resolution, apply TypeScript path aliases, or downlevel JavaScript syntax for older runtimes.
12
8
 
13
9
  ## Installation
14
10
 
@@ -47,8 +43,7 @@ Since import resolution still follows Node.js, configure type checking to match
47
43
  - [`verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig/#verbatimModuleSyntax) requires type-only imports and exports to be marked so runtime imports are unambiguous.
48
44
  - [`rewriteRelativeImportExtensions`](https://www.typescriptlang.org/tsconfig/#rewriteRelativeImportExtensions) preserves a `tsc` emit path by rewriting relative `.ts` and `.tsx` imports to JavaScript extensions.
49
45
 
50
- Do not enable `erasableSyntaxOnly` if you want TypeScript to accept the same
51
- transform-only syntax that `node-tsx` can execute.
46
+ Do not enable `erasableSyntaxOnly` if you want TypeScript to accept the same transform-only syntax that `node-tsx` can execute.
52
47
 
53
48
  ### Programmatic usage
54
49
 
@@ -55,15 +55,13 @@ createHref('http(s)://:region.cdn.com/assets/*file.:ext', {
55
55
 
56
56
  ## API at a glance
57
57
 
58
- **remix/route-pattern** - Parse and stringify patterns.
59
-
60
- **remix/route-pattern/href** - Generate hrefs for patterns with type safe params.
61
-
62
- **remix/route-pattern/match** - Match against one pattern with type inference for params. Or match against many patterns with deterministic ranking and attached data.
63
-
64
- **remix/route-pattern/join** - Combine two patterns into one. Override protocol, hostname, port. Join pathnames. Merge search constraints.
65
-
66
- **remix/route-pattern/specificity** - Rank matches by [specificity](#ranking-matches-by-specificity).
58
+ | Import | Description |
59
+ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
60
+ | `remix/route-pattern` | Parse and stringify patterns. |
61
+ | `remix/route-pattern/href` | Generate hrefs for patterns with type safe params. |
62
+ | `remix/route-pattern/match` | Match against one pattern with type inference for params, or match against many patterns with deterministic ranking and attached data. |
63
+ | `remix/route-pattern/join` | Combine two patterns into one. Override protocol, hostname, port. Join pathnames. Merge search constraints. |
64
+ | `remix/route-pattern/specificity` | Rank matches by [specificity](#ranking-matches-by-specificity). |
67
65
 
68
66
  For in-depth reference, visit the [`route-pattern` API docs](https://api.remix.run/api/remix/route-pattern)
69
67
 
@@ -113,6 +111,15 @@ While variables, wilcards, and optionals are most prevalent in pathnames, you ca
113
111
  '(:locale.)example.com/docs(/:section)' // matches en.example.com/docs, en.example.com/docs/guides
114
112
  ```
115
113
 
114
+ **Escape characters** with `\`:
115
+
116
+ ```ts
117
+ 'time/12\\:30' // matches /time/12:30
118
+ 'calculator/2\\*3' // matches /calculator/2*3
119
+ 'wiki/Mercury_\\(planet\\)' // matches /wiki/Mercury_(planet)
120
+ 'wiki/AC\\/DC' // matches /wiki/AC%2FDC
121
+ ```
122
+
116
123
  ### Search
117
124
 
118
125
  **Search constraints** narrow matches using `?key` or `?key=value`:
@@ -175,8 +182,7 @@ When multiple patterns match the same URL, `route-pattern` chooses the most spec
175
182
 
176
183
  This is the same ranking used by `createMultiMatcher`.
177
184
 
178
- For advanced use cases, `/specificity` provides comparison utilities: `lessThan`, `greaterThan`, `equal`, `descending`, `ascending`, `compare`.
179
- For example:
185
+ For advanced use cases, `/specificity` provides comparison utilities: `lessThan`, `greaterThan`, `equal`, `descending`, `ascending`, `compare`. For example:
180
186
 
181
187
  ```ts
182
188
  import { createMultiMatcher } from 'remix/route-pattern/match'
@@ -195,8 +201,7 @@ matches.sort(descending).map((match) => match.pattern.toString())
195
201
 
196
202
  ## Generate hrefs
197
203
 
198
- `createHref` turns a pattern and params into a URL string.
199
- Required variables and wildcards must be provided, while params inside optional groups may be omitted.
204
+ `createHref` turns a pattern and params into a URL string. Required variables and wildcards must be provided, while params inside optional groups may be omitted.
200
205
 
201
206
  ```ts
202
207
  import { createHref } from 'remix/route-pattern/href'
@@ -1,7 +1,6 @@
1
1
  # session-storage-redis
2
2
 
3
- Redis-backed session storage for [`remix/session`](https://github.com/remix-run/remix/tree/main/packages/session).
4
- Use this package when app servers need to share session state through Redis.
3
+ Redis-backed session storage for [`remix/session`](https://github.com/remix-run/remix/tree/main/packages/session). Use this package when app servers need to share session state through Redis.
5
4
 
6
5
  ## Installation
7
6
 
@@ -9,6 +9,7 @@ A test framework for JavaScript and TypeScript projects.
9
9
  - Playwright E2E testing via `t.serve`
10
10
  - In-browser component testing (pair with `render` from `remix/ui/test`)
11
11
  - Mock functions and method spies via `t.mock.fn` / `t.mock.method`
12
+ - Per-test and hook timeouts with `t.signal` abort support
12
13
  - Unified code coverage reporting across unit and E2E tests
13
14
  - Watch mode
14
15
  - Config file support (`remix-test.config.ts`)
@@ -193,6 +194,11 @@ describe('My Test Suite', () => {
193
194
  afterEach(() => {})
194
195
 
195
196
  it('tests something', () => {})
197
+ it('skips with a reason', { skip: 'requires API credentials' }, () => {})
198
+ it('tracks planned work', { todo: 'add retry coverage' }, () => {})
199
+ it('fails if it takes too long', { timeout: 5_000 }, async (t) => {
200
+ await fetchSomething({ signal: t.signal })
201
+ })
196
202
  it('tests something else', () => {})
197
203
  })
198
204
  ```
@@ -209,8 +215,7 @@ suite('My Test Suite', () => {
209
215
 
210
216
  ### Programmatic runner
211
217
 
212
- `remix/test/cli` exports `runRemixTest()` for tools that want to run the test runner without
213
- exiting the current process:
218
+ `remix/test/cli` exports `runRemixTest()` for tools that want to run the test runner without exiting the current process:
214
219
 
215
220
  ```ts
216
221
  import { runRemixTest } from 'remix/test/cli'
@@ -221,9 +226,7 @@ let exitCode = await runRemixTest({
221
226
  })
222
227
  ```
223
228
 
224
- `runRemixTest()` returns the runner exit code. The `remix test` bin wrapper calls
225
- `process.exit()` with that code when the run finishes so open workers, browsers, or project handles
226
- cannot keep the CLI alive.
229
+ `runRemixTest()` returns the runner exit code. The `remix test` bin wrapper calls `process.exit()` with that code when the run finishes so open workers, browsers, or project handles cannot keep the CLI alive.
227
230
 
228
231
  ### Test Context
229
232
 
@@ -232,6 +235,9 @@ Each test callback receives a `TestContext` (`t`) as its first argument with hel
232
235
  ```ts
233
236
  // from 'remix/test'
234
237
  interface TestContext {
238
+ // Aborts when the test times out or when the user-provided test signal aborts
239
+ signal: AbortSignal
240
+
235
241
  // Register a cleanup function to run after the test completes
236
242
  after(fn: () => void): void
237
243
 
@@ -287,6 +293,24 @@ it('cleanup', (t) => {
287
293
  })
288
294
  ```
289
295
 
296
+ #### Timeouts and Signals
297
+
298
+ Pass `{ timeout: ms }` to `it()` or after any lifecycle hook callback to fail that work if it takes too long. Timed-out tests abort `t.signal`, so async code that accepts an `AbortSignal` can cancel promptly.
299
+
300
+ ```ts
301
+ it('loads data', { timeout: 5_000 }, async (t) => {
302
+ let response = await fetch('/api/data', { signal: t.signal })
303
+ assert.equal(response.status, 200)
304
+ })
305
+
306
+ beforeEach(
307
+ async () => {
308
+ await resetDatabase()
309
+ },
310
+ { timeout: 1_000 },
311
+ )
312
+ ```
313
+
290
314
  #### Fake Timers
291
315
 
292
316
  `t.useFakeTimers()` replaces the global timer functions (`setTimeout`, `setInterval`, etc.) with controllable fakes that are automatically restored after the test. It works in any test environment — server unit tests, browser tests, or E2E setup code.
@@ -1,6 +1,6 @@
1
1
  # anchor
2
2
 
3
- `anchor` positions a floating element against an anchor element and keeps it constrained to the viewport. Use it for custom floating surfaces that need placement, flipping, offsets, and optional relative alignment.
3
+ `anchor` positions a floating element against an anchor element or viewport coordinates and keeps it constrained to the viewport. Use it for custom floating surfaces that need placement, flipping, offsets, and optional relative alignment.
4
4
 
5
5
  ## Usage
6
6
 
@@ -49,11 +49,19 @@ popover.addEventListener('beforetoggle', (event) => {
49
49
  })
50
50
  ```
51
51
 
52
+ Anchor to coordinates when the surface should open at a pointer location.
53
+
54
+ ```tsx
55
+ let cleanup = anchor(popover, { x: event.clientX, y: event.clientY }, { placement: 'bottom-start' })
56
+ ```
57
+
52
58
  ## `anchor.*`
53
59
 
54
- - `anchor(floatingElement, anchorElement, options)`: positions `floatingElement` against `anchorElement`, starts animation-frame polling for geometry changes, and returns a cleanup function.
60
+ - `anchor(floatingElement, anchorTarget, options)`: positions `floatingElement` against an element or coordinate target, starts animation-frame polling for geometry changes, and returns a cleanup function.
55
61
  - `AnchorOptions`: placement, inset, relative alignment, and offset options.
62
+ - `AnchorPoint`: viewport coordinate target with `x`, `y`, and optional `width`/`height`.
56
63
  - `AnchorPlacement`: exported placement names for the main sides and top/bottom start/end alignment.
64
+ - `AnchorTarget`: an `HTMLElement` or `AnchorPoint`.
57
65
 
58
66
  ## Placements
59
67
 
@@ -149,5 +157,5 @@ anchor(listbox, trigger, {
149
157
  - Oversized inset surfaces with `relativeTo` preserve alignment by scrolling the nearest scrollable descendant when possible.
150
158
  - `offset`, `offsetX`, and `offsetY` may be numbers or functions that receive the floating element.
151
159
  - `relativeTo` lets a surface align to an inner element, which is useful for selected options inside popovers.
152
- - `anchor` polls on animation frames for anchor or floating geometry changes and repositions when either changes.
160
+ - `anchor` polls on animation frames for anchor target or floating geometry changes and repositions when either changes.
153
161
  - The returned cleanup function cancels animation-frame polling.