react-router-effect 2.0.0 → 2.2.0

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.
@@ -0,0 +1,549 @@
1
+ import { Context, Effect, ManagedRuntime } from "effect";
2
+ import { ActionFunctionArgs, LoaderFunctionArgs, MiddlewareFunction, RouterContext, RouterContextProvider, UNSAFE_DataWithResponseInit, useLoaderData } from "react-router";
3
+ import { HttpServerRespondable } from "effect/http";
4
+ /** The key that holds the metadata in action data. */
5
+ declare const actionMetaKey = "__meta";
6
+ /**
7
+ * Adds optional metadata to a record. Only records get metadata, because a
8
+ * structural type cannot tell a plain object from a class instance, and other
9
+ * values (strings, arrays, `Response`s) have nowhere to put it.
10
+ */
11
+ type RecordWithMeta<T, Meta> = T extends Record<string, unknown> ? Omit<T, typeof actionMetaKey> & {
12
+ [actionMetaKey]?: Meta;
13
+ } : T;
14
+ /**
15
+ * The type of action data after metadata is attached. A `data(...)` wrapper
16
+ * gets metadata on its body. With no configured metadata (`Meta` is `never`)
17
+ * the data is unchanged.
18
+ */
19
+ type WithActionMeta<T, Meta> = [Meta] extends [never] ? T : T extends UNSAFE_DataWithResponseInit<infer Body> ? UNSAFE_DataWithResponseInit<RecordWithMeta<Body, Meta>> : RecordWithMeta<T, Meta>;
20
+ declare const ReturnableDataError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
21
+ readonly _tag: "ReturnableRouteError";
22
+ } & Readonly<A>;
23
+ /** Recoverable: short-circuits the loader but returns `data(...)` the component reads. */
24
+ declare class ReturnableDataError<D> extends ReturnableDataError_base<{
25
+ data: D;
26
+ init?: number | ResponseInit;
27
+ }> {}
28
+ declare const ThrowableDataError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
29
+ readonly _tag: "ThrowableRouteError";
30
+ } & Readonly<A>;
31
+ /** Throwable: rejects with `data(...)` → boundary as an `ErrorResponse`. */
32
+ declare class ThrowableDataError<D> extends ThrowableDataError_base<{
33
+ data: D;
34
+ init?: number | ResponseInit;
35
+ }> {}
36
+ declare const ThrowableRedirectError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
37
+ readonly _tag: "ThrowableRedirectError";
38
+ } & Readonly<A>;
39
+ /** Throwable: rejects with a redirect `Response`. */
40
+ declare class ThrowableRedirectError extends ThrowableRedirectError_base<{
41
+ url: string;
42
+ init?: number | ResponseInit;
43
+ }> {}
44
+ /** Every library route error, as a single union. */
45
+ type AnyRouteError = ReturnableDataError<unknown> | ThrowableDataError<unknown> | ThrowableRedirectError;
46
+ /**
47
+ * The library's base helpers for raising route errors (`early` / `throw` /
48
+ * `redirect`). This is internal: it isn't exported from the package. The factory
49
+ * passes it to your config builder and returns it (merged with any `respond`
50
+ * extensions) so your app imports a single `Respond`:
51
+ *
52
+ * ```ts
53
+ * export const { makeLoader, Respond } = makeEffectRouteFactory<DomainErrors>()(
54
+ * (Respond) => ({
55
+ * respond: { formError: (reply) => new FormError({ reply }) },
56
+ * errorHandlers: { FormError: (e) => Respond.early({ reply: e.reply }) },
57
+ * }),
58
+ * );
59
+ * ```
60
+ */
61
+ declare const Respond: {
62
+ /** Recover: short-circuit and pass `value` to the component as loader/action data. */
63
+ early: <D>(value: D, init?: number | ResponseInit) => ReturnableDataError<D>;
64
+ /** Throw: short-circuit to the error boundary with `data(value, init)`. */
65
+ throw: <D>(value: D, init?: number | ResponseInit) => ThrowableDataError<D>;
66
+ /** Throw: short-circuit with a redirect `Response`. */
67
+ redirect: (url: string, init?: number | ResponseInit) => ThrowableRedirectError;
68
+ };
69
+ /** Narrows an unknown value to one of the library's route errors. */
70
+ declare const isRouteError: (value: unknown) => value is AnyRouteError;
71
+ /** The library's base `Respond` helpers (`early` / `throw` / `redirect`). */
72
+ type BaseRespond = typeof Respond;
73
+ type Tagged = {
74
+ readonly _tag: string;
75
+ };
76
+ /**
77
+ * Everything a failure branch rejects with. React Router routes a thrown
78
+ * `DataWithResponseInit` to the boundary as an `ErrorResponse`, and a thrown
79
+ * `Response` (including `redirect(...)`) as-is. Pinning every `Effect.fail` branch
80
+ * to this union keeps inference from fixing the error channel to the first one.
81
+ */
82
+ type FailureResponse = UNSAFE_DataWithResponseInit<unknown> | Response;
83
+ /**
84
+ * What a handler may return, to *remap* a domain error:
85
+ * - a **library route error** - `Respond.early(value)` (recover), `Respond.throw(data)`
86
+ * or `Respond.redirect(url)` (throw) - which the library then processes; or
87
+ * - an **`Effect`** - `Effect.succeed(value)` to recover with `value`, or
88
+ * `Effect.fail(response)` to throw `response`.
89
+ */
90
+ type ErrorHandler<Err> = (error: Err) => AnyRouteError | Effect.Effect<unknown, FailureResponse>;
91
+ /** The body of every `ReturnableDataError` raised directly in `E` (`Respond.early`). */
92
+ type ReturnableBodyOf<E> = E extends ReturnableDataError<infer Body> ? Body : never;
93
+ /**
94
+ * The recover contribution of a `Respond.early` raised directly in the loader.
95
+ * Guarded so a loader that never recovers directly doesn't leak
96
+ * `DataWithResponseInit<never>` into its return type.
97
+ */
98
+ type DirectRecover<E> = [ReturnableBodyOf<E>] extends [never] ? never : UNSAFE_DataWithResponseInit<ReturnableBodyOf<E>>;
99
+ /** What a handler may return: a library route error or an `Effect` failing with a response. */
100
+ type HandlerReturn = AnyRouteError | Effect.Effect<unknown, FailureResponse>;
101
+ /** A `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) - `(args, next) => ...`. */
102
+ type AnyMiddlewareFunction = (...args: never[]) => unknown;
103
+ /**
104
+ * The `args` a `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) is called
105
+ * with - inferred from the type argument so `makeMiddleware` can be pinned to a specific
106
+ * generated route's middleware type.
107
+ */
108
+ type MiddlewareArgsOf<Middleware extends AnyMiddlewareFunction> = Parameters<Middleware>[0];
109
+ /**
110
+ * The `next` a `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) is called
111
+ * with - react-router's own `() => Promise<Result>`, not exported under its own name.
112
+ */
113
+ type MiddlewareNextOf<Middleware extends AnyMiddlewareFunction> = Parameters<Middleware>[1];
114
+ /**
115
+ * The `Result` a `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) resolves
116
+ * with - i.e. what its `next()` resolves to, and what the middleware itself may return.
117
+ * Read through `Awaited` because react-router declares `next` as an *interface* with a
118
+ * call signature (`MiddlewareNextFunction`), and a route's own may resolve synchronously.
119
+ */
120
+ type MiddlewareResultOf<Middleware extends AnyMiddlewareFunction> = MiddlewareNextOf<Middleware> extends ((...args: never[]) => infer Result) ? Awaited<Result> : never;
121
+ /**
122
+ * The inferred map of handler RETURN types, keyed by domain-error tag. Making the
123
+ * factory generic over *this* (rather than the handler map as a whole) is what lets
124
+ * `errorHandlers` contextually type each handler's `error` param from its key -
125
+ * no annotation needed - while still capturing precise return types for recovery.
126
+ */
127
+ type HandlerReturns<DomainError extends Tagged> = Partial<Record<DomainError["_tag"], HandlerReturn>>;
128
+ /**
129
+ * The `errorHandlers` field shape for a given return map: one entry per registered
130
+ * tag, whose `error` is the matching domain error (so it autocompletes and types
131
+ * itself) and whose return is the precise, inferred `Returns[Tag]`.
132
+ */
133
+ type ErrorHandlers<DomainError extends Tagged, Returns> = { [Tag in keyof Returns]: (error: Extract<DomainError, {
134
+ readonly _tag: Tag;
135
+ }>) => Returns[Tag]; };
136
+ /**
137
+ * What the registered handlers recover into loader/action data - from each
138
+ * handler's return type, but only for handlers whose error can actually occur in
139
+ * `E`:
140
+ * - a remapped `ReturnableDataError<B>` recovers as `DataWithResponseInit<B>`;
141
+ * - an `Effect.succeed(value)` recovers as `value`;
142
+ * - throwables, redirects and `Effect.fail(...)` contribute nothing.
143
+ */
144
+ type RecoverOf<DomainError extends Tagged, Returns, E> = { [Tag in keyof Returns]: [Extract<E, Extract<DomainError, {
145
+ readonly _tag: Tag;
146
+ }>>] extends [never] ? never : (Returns[Tag] extends ReturnableDataError<infer B> ? UNSAFE_DataWithResponseInit<B> : never) | (Returns[Tag] extends Effect.Effect<infer SuccessValue, any, any> ? SuccessValue : never); }[keyof Returns];
147
+ /**
148
+ * Errors the library deals with on the handler's behalf - so the loader/action/
149
+ * middleware needn't handle them itself:
150
+ * - the app's **declared domain errors** (`DomainError`) - handled by a registered
151
+ * handler, or left to the 500 / auto-respond default;
152
+ * - **library route errors** raised directly via `Respond`;
153
+ * - anything that renders itself via **`HttpServerRespondable`**;
154
+ * - `AlsoHandled` - extra, call-site-specific already-handled errors. Only
155
+ * `makeMiddleware` uses this, to admit the `FailureResponse` its `next()`
156
+ * fails with (a downstream throw, already a finished response - the library
157
+ * re-throws it as-is, so the middleware needn't catch it just to re-raise it).
158
+ */
159
+ type LibraryHandled<DomainError, AlsoHandled = never> = DomainError | AnyRouteError | HttpServerRespondable.Respondable | AlsoHandled;
160
+ /**
161
+ * What remains in a handler's error channel that the library will NOT handle -
162
+ * i.e. service-specific errors the route consumes that aren't declared domain
163
+ * errors. The loader/action/middleware must handle these itself (catch or map them).
164
+ */
165
+ type Unhandled<E, DomainError, AlsoHandled = never> = Exclude<E, LibraryHandled<DomainError, AlsoHandled>>;
166
+ /**
167
+ * A readable compile-time diagnostic. It's intersected onto the *effect return type*
168
+ * of `fn` - so the effect's `A`/`E`/`R` still infer directly (naked), while a mismatch
169
+ * reports this message, and the offending types, *at the `makeLoader`/`makeAction`
170
+ * call* instead of the opaque "Expected 2 arguments". Resolves to `unknown` (a no-op
171
+ * intersection) when the effect is fully handled and all its requirements are provided:
172
+ *
173
+ * - **Unhandled error** - the effect can fail with something that isn't a declared
174
+ * domain error, a library route error, or respondable; `unhandledErrors` lists them.
175
+ * - **Missing requirement** - the effect requires a service that neither the
176
+ * `runtime` nor the `requestContext` provides; `missingRequirements` lists them.
177
+ *
178
+ * The message is in an *inline* object literal (no named alias) so tsc/tsgo print
179
+ * its text verbatim rather than collapsing it to an alias name.
180
+ */
181
+ /**
182
+ * Lists the unhandled errors for the diagnostic: a tagged error becomes its `_tag`
183
+ * (the string you'd pass to `Effect.catchTag`), anything untagged stays as its type.
184
+ */
185
+ type NameError<U> = U extends {
186
+ readonly _tag: infer Tag extends string;
187
+ } ? Tag : U;
188
+ /**
189
+ * True exactly when `T` is (or, for a union, includes) `any` - not `unknown`, not
190
+ * `never`, not any other type. An unrelated mistake elsewhere in the effect (a typo,
191
+ * a call with the wrong number of arguments, a genuine syntax error) commonly makes
192
+ * TypeScript infer `any` for `E`/`R` instead of reporting a clean type; running
193
+ * `Diagnose` on that `any` produces a confusing "unhandled errors: any" message that
194
+ * buries the actual mistake. Guarding on this and bailing to `unknown` lets tsc's own,
195
+ * on-site error appear instead. (`1 & T` is `any` when `T` is `any` - including when
196
+ * `any` only appears as a union member, since intersection distributes over unions -
197
+ * and `0` is assignable to `any` but not to any concrete type built from `1 & T`.)
198
+ */
199
+ type IsAny<T> = 0 extends 1 & T ? true : false;
200
+ type Diagnose<E, R, DomainError, Provided, AlsoHandled = never> = IsAny<E> extends true ? unknown : IsAny<R> extends true ? unknown : [Unhandled<E, DomainError, AlsoHandled>] extends [never] ? [Exclude<R, Provided>] extends [never] ? unknown : {
201
+ "react-router-effect": "This loader/action/middleware requires a service the factory does not provide. Add it to your runtime or requestContext, or handle it in the effect.";
202
+ missingRequirements: Exclude<R, Provided>;
203
+ } : {
204
+ "react-router-effect": "This loader/action/middleware can fail with an error the library does not handle. Catch it in the effect (e.g. Effect.catchTag), make it Respondable, or add it to your DomainError union.";
205
+ unhandledErrors: NameError<Unhandled<E, DomainError, AlsoHandled>>;
206
+ };
207
+ /**
208
+ * A React Router context key holding a per-request effect `Context.Context`. Set
209
+ * it in middleware and pass it to the factory's `requestContext` - the runner
210
+ * reads it on every request and provides its services to the loader/action.
211
+ *
212
+ * With the server adapter, do not set it to a new context. The adapter sets it
213
+ * to the request fiber's context before React Router runs, and a new context
214
+ * replaces those services, such as `HttpServerRequest` and the server span.
215
+ * Provide request services with a router middleware on `ReactRouter.routes`.
216
+ * The middleware below is for an app without the adapter.
217
+ *
218
+ * It's just a `RouterContext`; create one with React Router's `createContext`:
219
+ *
220
+ * ```ts
221
+ * import { createContext } from "react-router";
222
+ * import { Context } from "effect";
223
+ * import type { RequestContextKey } from "react-router-effect";
224
+ *
225
+ * class RequestContext extends Context.Service<RequestContext, {
226
+ * readonly userId: string;
227
+ * }>()("app/RequestContext") {}
228
+ *
229
+ * export const requestContext: RequestContextKey<RequestContext> = createContext();
230
+ *
231
+ * // middleware, in an app without the server adapter:
232
+ * export const middleware: Route.MiddlewareFunction[] = [
233
+ * ({ context, request }, next) => {
234
+ * context.set(requestContext, Context.make(RequestContext, { userId: readUser(request) }));
235
+ * return next();
236
+ * },
237
+ * ];
238
+ * ```
239
+ */
240
+ type RequestContextKey<ReqServices> = RouterContext<Context.Context<ReqServices>>;
241
+ /** What `actionMeta` reads: the request and the router context of the action. */
242
+ interface ActionMetaArgs {
243
+ readonly request: Request;
244
+ readonly context: Readonly<RouterContextProvider>;
245
+ }
246
+ /** The `next` an effect middleware gets: runs the rest of the chain as an effect. */
247
+ type EffectNext<Result> = () => Effect.Effect<Result, FailureResponse>;
248
+ /**
249
+ * The arguments of an effect middleware: React Router's own middleware args,
250
+ * plus `next` as an effect. The second parameter stays React Router's
251
+ * promise-returning `next`, so a plain middleware works unchanged.
252
+ */
253
+ type EffectMiddlewareArgs<Middleware extends AnyMiddlewareFunction = MiddlewareFunction> = MiddlewareArgsOf<Middleware> & {
254
+ readonly next: EffectNext<MiddlewareResultOf<Middleware>>;
255
+ };
256
+ /** The router context and signal every route function receives. */
257
+ interface RouteArgs {
258
+ readonly request: Request;
259
+ readonly context: Readonly<RouterContextProvider>;
260
+ }
261
+ /**
262
+ * What a route function resolves with: an Effect's success, or the awaited
263
+ * value. A failure that a handler recovers resolves with its data too.
264
+ */
265
+ type RouteResult<Out> = Out extends Effect.Effect<infer A, infer _E, infer _R> ? A : Awaited<Out>;
266
+ /**
267
+ * Turns route functions into the promise-returning functions React Router
268
+ * calls. An Effect result runs through the factory's error model with the
269
+ * factory's runtime and the request's effect context; any other result passes
270
+ * through. The server adapter applies these to every route module, and tests
271
+ * can call them directly.
272
+ *
273
+ * Each is generic over the route's own args, so a test can pass a route's
274
+ * `Route.LoaderArgs` without a cast. React Router always supplies `request`
275
+ * and `context`.
276
+ */
277
+ interface RouteHandlers {
278
+ readonly loader: <Args extends RouteArgs, Out>(fn: (args: Args) => Out) => (args: Args) => Promise<RouteResult<Out>>;
279
+ readonly action: <Args extends RouteArgs, Out>(fn: (args: Args) => Out) => (args: Args) => Promise<RouteResult<Out>>;
280
+ /**
281
+ * The wrapped middleware takes React Router's args, without `next`: the
282
+ * runner adds `next` as an effect.
283
+ */
284
+ readonly middleware: <Args extends RouteArgs, Next extends () => Promise<unknown>, Out>(fn: (args: Args, next: Next) => Out) => (args: Omit<Args, "next">, next: Next) => Promise<RouteResult<Out> | undefined>;
285
+ /** The router context key that holds the request's effect context. */
286
+ readonly requestContext: RouterContext<Context.Context<never>>;
287
+ }
288
+ /**
289
+ * The types a factory exposes for the server adapter's route check and for
290
+ * `Register`. Never present at runtime.
291
+ */
292
+ interface FactoryTypes<DomainError, Returns, Services, Meta> {
293
+ readonly DomainError: DomainError;
294
+ readonly Returns: Returns;
295
+ readonly Services: Services;
296
+ readonly Meta: Meta;
297
+ }
298
+ /**
299
+ * Build `makeLoader` / `makeAction` / `makeMiddleware` for an application, wired to
300
+ * its domain errors.
301
+ *
302
+ * Declare the app's **domain errors** as the type argument, then register an
303
+ * *optional* handler per domain error in `errorHandlers`. The handler keys
304
+ * autocomplete to your domain-error tags and each `error` parameter is typed from
305
+ * its key - no annotation needed. It's curried so you can pin the domain errors
306
+ * while the handler return types are still inferred:
307
+ *
308
+ * ```ts
309
+ * type DomainErrors = MyDomainError | DbError | NotAuthorizedError;
310
+ *
311
+ * export const { makeLoader, makeAction, Respond } =
312
+ * makeEffectRouteFactory<DomainErrors>()((Respond) => ({
313
+ * errorHandlers: {
314
+ * // `error` is typed as MyDomainError automatically. throw → error boundary
315
+ * MyDomainError: (error) => Respond.throw({ message: error.message }, 400),
316
+ * // DbError has no handler → falls through to the 500 default.
317
+ * },
318
+ * }));
319
+ * ```
320
+ *
321
+ * The inner config is a **builder** - it receives the library's `Respond` helpers so
322
+ * your handlers can recover/throw with them, and returns the config. `Respond` is
323
+ * also returned from the factory (extended with any `respond` helpers you add), so
324
+ * your app imports a *single* `Respond` - no auto-import ambiguity with a library one.
325
+ *
326
+ * A handler *remaps* the error by returning either a library route error
327
+ * (`Respond.early` to recover; `Respond.throw` / `Respond.redirect` to throw) or an
328
+ * `Effect` (`Effect.succeed(value)` to recover `value`; `Effect.fail(response)` to
329
+ * throw). Recovered values become loader/action data, typed precisely.
330
+ *
331
+ * **Declared domain errors** may be left unhandled: if one implements
332
+ * `HttpServerRespondable` it's rendered automatically, otherwise it falls through
333
+ * to a 500. **Any other error** - a service-specific error the route consumes that
334
+ * isn't a declared domain error - *must* be handled in the loader/action, or
335
+ * `makeLoader`/`makeAction` fails to type-check.
336
+ *
337
+ * Pass a `runtime` (a `ManagedRuntime`, e.g. from `ManagedRuntime.make(AppLayer)`)
338
+ * to provide your app's services once. Loader/action effects may then require those
339
+ * services directly - no per-call `Effect.provide`:
340
+ *
341
+ * ```ts
342
+ * const { makeLoader, makeAction } = makeEffectRouteFactory<DomainErrors>()(() => ({
343
+ * runtime: getAppRuntime(), // provides Database, MyService, ...
344
+ * errorHandlers: { ... },
345
+ * }));
346
+ *
347
+ * // `MyService` is satisfied by the runtime, not provided here:
348
+ * const loader = makeLoader((args: Route.LoaderArgs) =>
349
+ * Effect.gen(function* () {
350
+ * const svc = yield* MyService;
351
+ * return { data: yield* svc.load(args) };
352
+ * }),
353
+ * );
354
+ * ```
355
+ *
356
+ * `makeMiddleware` wraps a `Route.MiddlewareFunction` the same way: write it as an
357
+ * effect, and register the same domain errors/handlers. Pin it to the route's
358
+ * generated `Route.MiddlewareFunction` (a type argument, since middleware has no
359
+ * separate args-only type) to type `args` and `next`'s result from that route; it
360
+ * defaults to React Router's own `MiddlewareFunction`. The `next` handed to the
361
+ * effect is `() => Effect.Effect<Result, FailureResponse>` - call it with `yield*`
362
+ * to run the rest of the chain, and a downstream throw (e.g. a nested loader's
363
+ * redirect) becomes a typed failure instead of a rejected promise:
364
+ *
365
+ * ```ts
366
+ * const middleware = makeMiddleware<Route.MiddlewareFunction>((args, next) =>
367
+ * Effect.gen(function* () {
368
+ * const user = yield* getUser(args); // may fail with a declared domain error
369
+ * if (!user) yield* Respond.redirect("/login");
370
+ * return yield* next();
371
+ * }),
372
+ * );
373
+ * ```
374
+ */
375
+ declare function makeEffectRouteFactory<DomainError extends Tagged = never>(): <const Returns extends HandlerReturns<DomainError> = {}, RServices = never, ReqServices = never, const ExtraRespond extends Record<string, (...args: never[]) => unknown> = {}, Meta = never>(builder: (respond: BaseRespond) => {
376
+ /**
377
+ * An optional handler per declared domain error, keyed by its tag. The keys
378
+ * autocomplete to your domain-error tags and each handler's `error` parameter
379
+ * is typed automatically - no annotation needed. A handler *remaps* the error
380
+ * by returning a library route error (the builder's `Respond.early` / `throw`
381
+ * / `redirect`) or an `Effect` (`Effect.succeed`/`fail`). Omit to register none.
382
+ */
383
+ errorHandlers?: ErrorHandlers<DomainError, Returns>;
384
+ /**
385
+ * The app runtime that provides services to loader/action effects. When
386
+ * set, effects may require its services (`RServices`, inferred from here)
387
+ * without providing layers, and runs go through `runtime.runPromise`. When
388
+ * omitted, `RServices` is `never` and effects must require nothing.
389
+ */
390
+ runtime?: ManagedRuntime.ManagedRuntime<RServices, any>;
391
+ /**
392
+ * A React Router context key (a {@link RequestContextKey}) holding a
393
+ * per-request effect `Context.Context`. Middleware sets it for each request;
394
+ * the runner reads `args.context.get(requestContext)` and provides those
395
+ * services to the effect. Loader/action effects may then require
396
+ * `ReqServices` (inferred from here) in addition to the runtime's services.
397
+ */
398
+ requestContext?: RequestContextKey<ReqServices>;
399
+ /**
400
+ * App-specific `Respond` helpers - merged onto the base `Respond` that the
401
+ * factory returns, so your app imports one `Respond`. Typically opinionated
402
+ * error constructors; annotate their parameters, e.g.
403
+ * `{ formError: (reply: string) => new FormError({ reply }) }`. The base
404
+ * helpers (`early` / `throw` / `redirect`) always win the merge.
405
+ */
406
+ respond?: ExtraRespond;
407
+ /**
408
+ * Request-scoped metadata to attach to every action's data under `__meta`,
409
+ * read after the action resolves. The server adapter attaches it to every
410
+ * action it runs, including plain functions and `makeAction` wrappers. A
411
+ * thrown result gets none. Only plain records and `data(...)` bodies include
412
+ * it; other values are returned unchanged.
413
+ */
414
+ actionMeta?: (args: ActionMetaArgs) => Meta;
415
+ }) => {
416
+ makeLoader: <Args extends LoaderFunctionArgs | ActionFunctionArgs, A, E, R>(fn: (args: Args) => Effect.Effect<A, E, R> & Diagnose<E, R, DomainError, RServices | ReqServices>) => (args: Args) => Promise<A | DirectRecover<E> | RecoverOf<DomainError, Returns, E>>;
417
+ makeAction: <Args extends LoaderFunctionArgs | ActionFunctionArgs, A, E, R>(fn: (args: Args) => Effect.Effect<A, E, R> & Diagnose<E, R, DomainError, RServices | ReqServices>) => (args: Args) => Promise<WithActionMeta<A | DirectRecover<E> | RecoverOf<DomainError, Returns, E>, Meta>>;
418
+ makeMiddleware: <Middleware extends AnyMiddlewareFunction = MiddlewareFunction, E = LibraryHandled<DomainError, FailureResponse>, R = RServices | ReqServices>(fn: (args: MiddlewareArgsOf<Middleware>, next: () => Effect.Effect<MiddlewareResultOf<Middleware>, FailureResponse>) => Effect.Effect<MiddlewareResultOf<Middleware> | void, E, R> & Diagnose<E, R, DomainError, RServices | ReqServices, FailureResponse>) => (args: MiddlewareArgsOf<Middleware>, next: MiddlewareNextOf<Middleware>) => Promise<MiddlewareResultOf<Middleware> | undefined>;
419
+ Respond: {
420
+ early: <D>(value: D, init?: number | ResponseInit) => ReturnableDataError<D>;
421
+ throw: <D>(value: D, init?: number | ResponseInit) => ThrowableDataError<D>;
422
+ redirect: (url: string, init?: number | ResponseInit) => ThrowableRedirectError;
423
+ } & ExtraRespond;
424
+ /** The route runner the server adapter applies to every route module. */
425
+ handlers: RouteHandlers;
426
+ /** Type-only: what the adapter's route check and `Register` read. */
427
+ "~types": FactoryTypes<DomainError, Returns, RServices | ReqServices, Meta>;
428
+ };
429
+ /**
430
+ * Registers what the app's route factory turns failures into, so that a
431
+ * loader or action written as a plain `Effect.fn` types its data the way a
432
+ * `makeLoader` call would.
433
+ *
434
+ * A `makeLoader` call sees the factory's handlers as type arguments. A plain
435
+ * Effect loader does not, so without this an error that a handler recovers
436
+ * into data, such as a form error that becomes `{ reply }`, would be missing
437
+ * from `useLoaderData`, `useFetcher` and `Route.ComponentProps`.
438
+ *
439
+ * Declare the types directly rather than reading them from the factory's
440
+ * value: the factory's type depends on every Effect in the app, and so on
441
+ * this registration. The server adapter's `RouteEffectCheck` fails when the
442
+ * registration and the factory disagree.
443
+ *
444
+ * ```ts
445
+ * declare module "react-router-effect" {
446
+ * interface Register {
447
+ * // Each error tag a handler recovers, and the data it recovers into.
448
+ * recover: { FormValidationError: { reply: SubmissionResponse } };
449
+ * // What `actionMeta` returns.
450
+ * actionMeta: { realtimeEventIds: ReadonlyArray<string> };
451
+ * }
452
+ * }
453
+ * ```
454
+ */
455
+ interface Register$1 {}
456
+ /** The registered recover map: error tag to recovered data. */
457
+ type RegisteredRecover = Register$1 extends {
458
+ readonly recover: infer Recover;
459
+ } ? Recover : {};
460
+ /** The registered action metadata, or `never` when there is none. */
461
+ type RegisteredMeta = Register$1 extends {
462
+ readonly actionMeta: infer Meta;
463
+ } ? Meta : never;
464
+ /** What a failure of type `E` recovers into: `Respond.early` bodies and registered handlers. */
465
+ type Recovered<E> = (E extends ReturnableDataError<infer Body> ? Body : never) | (E extends {
466
+ readonly _tag: infer Tag extends keyof RegisteredRecover;
467
+ } ? RegisteredRecover[Tag] : never);
468
+ /**
469
+ * React Router's own serialization of loader data. React Router does not
470
+ * export it, but a zero-argument function is a server loader to its public
471
+ * `useLoaderData` type, so this gives the same type.
472
+ */
473
+ type Serialize<T> = ReturnType<typeof useLoaderData<() => Promise<T>>>;
474
+ /**
475
+ * The data an Effect that succeeds with `A` and fails with `E` gives the
476
+ * component: its success and what its failures recover into, serialized as
477
+ * React Router serializes them, with the registered action metadata.
478
+ */
479
+ type EffectData<A, E> = WithActionMeta<Serialize<A | Recovered<E>>, RegisteredMeta>;
480
+ /**
481
+ * The route data of a loader or action, for React Router's hooks:
482
+ * `useFetcher<RouteData<typeof action>>()`. A function that returns an Effect
483
+ * gives its `EffectData`. Any other function passes through, so the hook
484
+ * reads it as it always has.
485
+ */
486
+ type RouteData<F> = F extends ((...args: never[]) => Effect.Effect<infer A, infer E, infer _R>) ? EffectData<A, E> : F;
487
+ /**
488
+ * Loader data as React Router reads it before serializing: a `Response` adds
489
+ * nothing, and a `data(...)` wrapper gives its body.
490
+ */
491
+ type Unwrapped<A> = A extends Response ? never : A extends UNSAFE_DataWithResponseInit<infer Body> ? Body : A;
492
+ /**
493
+ * The data React Router reads from an Effect through `unstable_SerializesTo`.
494
+ *
495
+ * TypeScript compares this brand whenever it relates two Effects
496
+ * structurally, so it must keep the subtype order of `A` and `E`: when
497
+ * `Effect<A1, E1>` is assignable to `Effect<A2, E2>`, `BrandData<A1, E1>` must
498
+ * be assignable to `BrandData<A2, E2>`. React Router's `Serialize` does not
499
+ * (it turns `unknown` into `undefined`), so the brand holds the data before
500
+ * serialization; `RouteData` applies the actual serialization.
501
+ *
502
+ * - A `void` or `any` success gives `unknown`, which accepts every other brand.
503
+ * - A `never` success gives only what the failures recover into.
504
+ *
505
+ * `Effect` declares its parameters `out`, and TypeScript verifies that
506
+ * annotation against this member, and against it again in every Effect type
507
+ * that declares variance (`Exit`, `Config`, ...). It relates two conditional
508
+ * types branch by branch when their check types are related and their
509
+ * `extends` types are identical. So every conditional here, and in the types
510
+ * it uses, checks `A` or `E` (or a tuple of one) against a type that does not
511
+ * depend on them. A conditional with `A` or `E` on its `extends` side, such as
512
+ * `0 extends 1 & A` to detect `any`, fails the verification: a consumer that
513
+ * checks library types (`skipLibCheck: false`) then gets TS2636 in Effect's own
514
+ * declarations. `test/consumer` checks the built package for that.
515
+ */
516
+ type BrandData<A, E> = [A] extends [never] ? WithActionMeta<Recovered<E>, RegisteredMeta> : [A] extends [void] ? unknown : WithActionMeta<Unwrapped<A> | Recovered<E>, RegisteredMeta>;
517
+ declare module "effect/Effect" {
518
+ interface Effect<out A, out E, out R> {
519
+ readonly unstable__ReactRouter_SerializesTo: [BrandData<A, E>];
520
+ }
521
+ }
522
+ declare module "effect/Cause" {
523
+ interface YieldableError {
524
+ readonly unstable__ReactRouter_SerializesTo: [never];
525
+ }
526
+ }
527
+ /**
528
+ * Route args for a loader or action written as a plain `Effect.fn`, such as
529
+ * `EffectRouteArgs<Route.ActionArgs>`. It keeps hooks that take `typeof loader`
530
+ * or `typeof action` typed, such as `useFetcher<typeof action>()`.
531
+ *
532
+ * Those hooks read a function's data through React Router's `SerializeFrom`.
533
+ * It treats a function whose argument extends `ClientDataFunctionArgs` as a
534
+ * client function, and gives its raw return value without serialization.
535
+ * Typegen's `Route.LoaderArgs` and `Route.ActionArgs` have the same members as
536
+ * `ClientDataFunctionArgs`, so a server function typed with them matches too.
537
+ * For an Effect, the raw return value is the Effect itself: only serialization
538
+ * reads the `unstable_SerializesTo` brand.
539
+ *
540
+ * `pattern` is optional here, so the argument no longer matches and the hooks
541
+ * read the brand. `Route.ComponentProps` and `RouteData` don't go through
542
+ * `SerializeFrom`, so they are correct without it.
543
+ */
544
+ type EffectRouteArgs<Args extends {
545
+ readonly pattern: string;
546
+ }> = Omit<Args, "pattern"> & {
547
+ readonly pattern?: string;
548
+ };
549
+ export { ReturnableDataError as C, WithActionMeta as D, isRouteError as E, actionMetaKey as O, AnyRouteError as S, ThrowableRedirectError as T, NameError as _, RegisteredMeta as a, RouteResult as b, Serialize as c, EffectNext as d, ErrorHandler as f, LibraryHandled as g, IsAny as h, Register$1 as i, ActionMetaArgs as l, FailureResponse as m, EffectData as n, RegisteredRecover as o, FactoryTypes as p, EffectRouteArgs as r, RouteData as s, BrandData as t, EffectMiddlewareArgs as u, RequestContextKey as v, ThrowableDataError as w, makeEffectRouteFactory as x, RouteHandlers as y };