react-router-effect 1.0.2 → 2.1.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,543 @@
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
+ * It's just a `RouterContext`; create one with React Router's `createContext`:
213
+ *
214
+ * ```ts
215
+ * import { createContext } from "react-router";
216
+ * import { Context } from "effect";
217
+ * import type { RequestContextKey } from "react-router-effect";
218
+ *
219
+ * class RequestContext extends Context.Service<RequestContext, {
220
+ * readonly userId: string;
221
+ * }>()("app/RequestContext") {}
222
+ *
223
+ * export const requestContext: RequestContextKey<RequestContext> = createContext();
224
+ *
225
+ * // middleware:
226
+ * export const middleware: Route.MiddlewareFunction[] = [
227
+ * ({ context, request }, next) => {
228
+ * context.set(requestContext, Context.make(RequestContext, { userId: readUser(request) }));
229
+ * return next();
230
+ * },
231
+ * ];
232
+ * ```
233
+ */
234
+ type RequestContextKey<ReqServices> = RouterContext<Context.Context<ReqServices>>;
235
+ /** What `actionMeta` reads: the request and the router context of the action. */
236
+ interface ActionMetaArgs {
237
+ readonly request: Request;
238
+ readonly context: Readonly<RouterContextProvider>;
239
+ }
240
+ /** The `next` an effect middleware gets: runs the rest of the chain as an effect. */
241
+ type EffectNext<Result> = () => Effect.Effect<Result, FailureResponse>;
242
+ /**
243
+ * The arguments of an effect middleware: React Router's own middleware args,
244
+ * plus `next` as an effect. The second parameter stays React Router's
245
+ * promise-returning `next`, so a plain middleware works unchanged.
246
+ */
247
+ type EffectMiddlewareArgs<Middleware extends AnyMiddlewareFunction = MiddlewareFunction> = MiddlewareArgsOf<Middleware> & {
248
+ readonly next: EffectNext<MiddlewareResultOf<Middleware>>;
249
+ };
250
+ /** The router context and signal every route function receives. */
251
+ interface RouteArgs {
252
+ readonly request: Request;
253
+ readonly context: Readonly<RouterContextProvider>;
254
+ }
255
+ /**
256
+ * What a route function resolves with: an Effect's success, or the awaited
257
+ * value. A failure that a handler recovers resolves with its data too.
258
+ */
259
+ type RouteResult<Out> = Out extends Effect.Effect<infer A, infer _E, infer _R> ? A : Awaited<Out>;
260
+ /**
261
+ * Turns route functions into the promise-returning functions React Router
262
+ * calls. An Effect result runs through the factory's error model with the
263
+ * factory's runtime and the request's effect context; any other result passes
264
+ * through. The server adapter applies these to every route module, and tests
265
+ * can call them directly.
266
+ *
267
+ * Each is generic over the route's own args, so a test can pass a route's
268
+ * `Route.LoaderArgs` without a cast. React Router always supplies `request`
269
+ * and `context`.
270
+ */
271
+ interface RouteHandlers {
272
+ readonly loader: <Args extends RouteArgs, Out>(fn: (args: Args) => Out) => (args: Args) => Promise<RouteResult<Out>>;
273
+ readonly action: <Args extends RouteArgs, Out>(fn: (args: Args) => Out) => (args: Args) => Promise<RouteResult<Out>>;
274
+ /**
275
+ * The wrapped middleware takes React Router's args, without `next`: the
276
+ * runner adds `next` as an effect.
277
+ */
278
+ 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>;
279
+ /** The router context key that holds the request's effect context. */
280
+ readonly requestContext: RouterContext<Context.Context<never>>;
281
+ }
282
+ /**
283
+ * The types a factory exposes for the server adapter's route check and for
284
+ * `Register`. Never present at runtime.
285
+ */
286
+ interface FactoryTypes<DomainError, Returns, Services, Meta> {
287
+ readonly DomainError: DomainError;
288
+ readonly Returns: Returns;
289
+ readonly Services: Services;
290
+ readonly Meta: Meta;
291
+ }
292
+ /**
293
+ * Build `makeLoader` / `makeAction` / `makeMiddleware` for an application, wired to
294
+ * its domain errors.
295
+ *
296
+ * Declare the app's **domain errors** as the type argument, then register an
297
+ * *optional* handler per domain error in `errorHandlers`. The handler keys
298
+ * autocomplete to your domain-error tags and each `error` parameter is typed from
299
+ * its key - no annotation needed. It's curried so you can pin the domain errors
300
+ * while the handler return types are still inferred:
301
+ *
302
+ * ```ts
303
+ * type DomainErrors = MyDomainError | DbError | NotAuthorizedError;
304
+ *
305
+ * export const { makeLoader, makeAction, Respond } =
306
+ * makeEffectRouteFactory<DomainErrors>()((Respond) => ({
307
+ * errorHandlers: {
308
+ * // `error` is typed as MyDomainError automatically. throw → error boundary
309
+ * MyDomainError: (error) => Respond.throw({ message: error.message }, 400),
310
+ * // DbError has no handler → falls through to the 500 default.
311
+ * },
312
+ * }));
313
+ * ```
314
+ *
315
+ * The inner config is a **builder** - it receives the library's `Respond` helpers so
316
+ * your handlers can recover/throw with them, and returns the config. `Respond` is
317
+ * also returned from the factory (extended with any `respond` helpers you add), so
318
+ * your app imports a *single* `Respond` - no auto-import ambiguity with a library one.
319
+ *
320
+ * A handler *remaps* the error by returning either a library route error
321
+ * (`Respond.early` to recover; `Respond.throw` / `Respond.redirect` to throw) or an
322
+ * `Effect` (`Effect.succeed(value)` to recover `value`; `Effect.fail(response)` to
323
+ * throw). Recovered values become loader/action data, typed precisely.
324
+ *
325
+ * **Declared domain errors** may be left unhandled: if one implements
326
+ * `HttpServerRespondable` it's rendered automatically, otherwise it falls through
327
+ * to a 500. **Any other error** - a service-specific error the route consumes that
328
+ * isn't a declared domain error - *must* be handled in the loader/action, or
329
+ * `makeLoader`/`makeAction` fails to type-check.
330
+ *
331
+ * Pass a `runtime` (a `ManagedRuntime`, e.g. from `ManagedRuntime.make(AppLayer)`)
332
+ * to provide your app's services once. Loader/action effects may then require those
333
+ * services directly - no per-call `Effect.provide`:
334
+ *
335
+ * ```ts
336
+ * const { makeLoader, makeAction } = makeEffectRouteFactory<DomainErrors>()(() => ({
337
+ * runtime: getAppRuntime(), // provides Database, MyService, ...
338
+ * errorHandlers: { ... },
339
+ * }));
340
+ *
341
+ * // `MyService` is satisfied by the runtime, not provided here:
342
+ * const loader = makeLoader((args: Route.LoaderArgs) =>
343
+ * Effect.gen(function* () {
344
+ * const svc = yield* MyService;
345
+ * return { data: yield* svc.load(args) };
346
+ * }),
347
+ * );
348
+ * ```
349
+ *
350
+ * `makeMiddleware` wraps a `Route.MiddlewareFunction` the same way: write it as an
351
+ * effect, and register the same domain errors/handlers. Pin it to the route's
352
+ * generated `Route.MiddlewareFunction` (a type argument, since middleware has no
353
+ * separate args-only type) to type `args` and `next`'s result from that route; it
354
+ * defaults to React Router's own `MiddlewareFunction`. The `next` handed to the
355
+ * effect is `() => Effect.Effect<Result, FailureResponse>` - call it with `yield*`
356
+ * to run the rest of the chain, and a downstream throw (e.g. a nested loader's
357
+ * redirect) becomes a typed failure instead of a rejected promise:
358
+ *
359
+ * ```ts
360
+ * const middleware = makeMiddleware<Route.MiddlewareFunction>((args, next) =>
361
+ * Effect.gen(function* () {
362
+ * const user = yield* getUser(args); // may fail with a declared domain error
363
+ * if (!user) yield* Respond.redirect("/login");
364
+ * return yield* next();
365
+ * }),
366
+ * );
367
+ * ```
368
+ */
369
+ 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) => {
370
+ /**
371
+ * An optional handler per declared domain error, keyed by its tag. The keys
372
+ * autocomplete to your domain-error tags and each handler's `error` parameter
373
+ * is typed automatically - no annotation needed. A handler *remaps* the error
374
+ * by returning a library route error (the builder's `Respond.early` / `throw`
375
+ * / `redirect`) or an `Effect` (`Effect.succeed`/`fail`). Omit to register none.
376
+ */
377
+ errorHandlers?: ErrorHandlers<DomainError, Returns>;
378
+ /**
379
+ * The app runtime that provides services to loader/action effects. When
380
+ * set, effects may require its services (`RServices`, inferred from here)
381
+ * without providing layers, and runs go through `runtime.runPromise`. When
382
+ * omitted, `RServices` is `never` and effects must require nothing.
383
+ */
384
+ runtime?: ManagedRuntime.ManagedRuntime<RServices, any>;
385
+ /**
386
+ * A React Router context key (a {@link RequestContextKey}) holding a
387
+ * per-request effect `Context.Context`. Middleware sets it for each request;
388
+ * the runner reads `args.context.get(requestContext)` and provides those
389
+ * services to the effect. Loader/action effects may then require
390
+ * `ReqServices` (inferred from here) in addition to the runtime's services.
391
+ */
392
+ requestContext?: RequestContextKey<ReqServices>;
393
+ /**
394
+ * App-specific `Respond` helpers - merged onto the base `Respond` that the
395
+ * factory returns, so your app imports one `Respond`. Typically opinionated
396
+ * error constructors; annotate their parameters, e.g.
397
+ * `{ formError: (reply: string) => new FormError({ reply }) }`. The base
398
+ * helpers (`early` / `throw` / `redirect`) always win the merge.
399
+ */
400
+ respond?: ExtraRespond;
401
+ /**
402
+ * Request-scoped metadata to attach to every action's data under `__meta`,
403
+ * read after the action resolves. The server adapter attaches it to every
404
+ * action it runs, including plain functions and `makeAction` wrappers. A
405
+ * thrown result gets none. Only plain records and `data(...)` bodies include
406
+ * it; other values are returned unchanged.
407
+ */
408
+ actionMeta?: (args: ActionMetaArgs) => Meta;
409
+ }) => {
410
+ 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>>;
411
+ 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>>;
412
+ 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>;
413
+ Respond: {
414
+ early: <D>(value: D, init?: number | ResponseInit) => ReturnableDataError<D>;
415
+ throw: <D>(value: D, init?: number | ResponseInit) => ThrowableDataError<D>;
416
+ redirect: (url: string, init?: number | ResponseInit) => ThrowableRedirectError;
417
+ } & ExtraRespond;
418
+ /** The route runner the server adapter applies to every route module. */
419
+ handlers: RouteHandlers;
420
+ /** Type-only: what the adapter's route check and `Register` read. */
421
+ "~types": FactoryTypes<DomainError, Returns, RServices | ReqServices, Meta>;
422
+ };
423
+ /**
424
+ * Registers what the app's route factory turns failures into, so that a
425
+ * loader or action written as a plain `Effect.fn` types its data the way a
426
+ * `makeLoader` call would.
427
+ *
428
+ * A `makeLoader` call sees the factory's handlers as type arguments. A plain
429
+ * Effect loader does not, so without this an error that a handler recovers
430
+ * into data, such as a form error that becomes `{ reply }`, would be missing
431
+ * from `useLoaderData`, `useFetcher` and `Route.ComponentProps`.
432
+ *
433
+ * Declare the types directly rather than reading them from the factory's
434
+ * value: the factory's type depends on every Effect in the app, and so on
435
+ * this registration. The server adapter's `RouteEffectCheck` fails when the
436
+ * registration and the factory disagree.
437
+ *
438
+ * ```ts
439
+ * declare module "react-router-effect" {
440
+ * interface Register {
441
+ * // Each error tag a handler recovers, and the data it recovers into.
442
+ * recover: { FormValidationError: { reply: SubmissionResponse } };
443
+ * // What `actionMeta` returns.
444
+ * actionMeta: { realtimeEventIds: ReadonlyArray<string> };
445
+ * }
446
+ * }
447
+ * ```
448
+ */
449
+ interface Register$1 {}
450
+ /** The registered recover map: error tag to recovered data. */
451
+ type RegisteredRecover = Register$1 extends {
452
+ readonly recover: infer Recover;
453
+ } ? Recover : {};
454
+ /** The registered action metadata, or `never` when there is none. */
455
+ type RegisteredMeta = Register$1 extends {
456
+ readonly actionMeta: infer Meta;
457
+ } ? Meta : never;
458
+ /** What a failure of type `E` recovers into: `Respond.early` bodies and registered handlers. */
459
+ type Recovered<E> = (E extends ReturnableDataError<infer Body> ? Body : never) | (E extends {
460
+ readonly _tag: infer Tag extends keyof RegisteredRecover;
461
+ } ? RegisteredRecover[Tag] : never);
462
+ /**
463
+ * React Router's own serialization of loader data. React Router does not
464
+ * export it, but a zero-argument function is a server loader to its public
465
+ * `useLoaderData` type, so this gives the same type.
466
+ */
467
+ type Serialize<T> = ReturnType<typeof useLoaderData<() => Promise<T>>>;
468
+ /**
469
+ * The data an Effect that succeeds with `A` and fails with `E` gives the
470
+ * component: its success and what its failures recover into, serialized as
471
+ * React Router serializes them, with the registered action metadata.
472
+ */
473
+ type EffectData<A, E> = WithActionMeta<Serialize<A | Recovered<E>>, RegisteredMeta>;
474
+ /**
475
+ * The route data of a loader or action, for React Router's hooks:
476
+ * `useFetcher<RouteData<typeof action>>()`. A function that returns an Effect
477
+ * gives its `EffectData`. Any other function passes through, so the hook
478
+ * reads it as it always has.
479
+ */
480
+ type RouteData<F> = F extends ((...args: never[]) => Effect.Effect<infer A, infer E, infer _R>) ? EffectData<A, E> : F;
481
+ /**
482
+ * Loader data as React Router reads it before serializing: a `Response` adds
483
+ * nothing, and a `data(...)` wrapper gives its body.
484
+ */
485
+ type Unwrapped<A> = A extends Response ? never : A extends UNSAFE_DataWithResponseInit<infer Body> ? Body : A;
486
+ /**
487
+ * The data React Router reads from an Effect through `unstable_SerializesTo`.
488
+ *
489
+ * TypeScript compares this brand whenever it relates two Effects
490
+ * structurally, so it must keep the subtype order of `A` and `E`: when
491
+ * `Effect<A1, E1>` is assignable to `Effect<A2, E2>`, `BrandData<A1, E1>` must
492
+ * be assignable to `BrandData<A2, E2>`. React Router's `Serialize` does not
493
+ * (it turns `unknown` into `undefined`), so the brand holds the data before
494
+ * serialization; `RouteData` applies the actual serialization.
495
+ *
496
+ * - A `void` or `any` success gives `unknown`, which accepts every other brand.
497
+ * - A `never` success gives only what the failures recover into.
498
+ *
499
+ * `Effect` declares its parameters `out`, and TypeScript verifies that
500
+ * annotation against this member, and against it again in every Effect type
501
+ * that declares variance (`Exit`, `Config`, ...). It relates two conditional
502
+ * types branch by branch when their check types are related and their
503
+ * `extends` types are identical. So every conditional here, and in the types
504
+ * it uses, checks `A` or `E` (or a tuple of one) against a type that does not
505
+ * depend on them. A conditional with `A` or `E` on its `extends` side, such as
506
+ * `0 extends 1 & A` to detect `any`, fails the verification: a consumer that
507
+ * checks library types (`skipLibCheck: false`) then gets TS2636 in Effect's own
508
+ * declarations. `test/consumer` checks the built package for that.
509
+ */
510
+ type BrandData<A, E> = [A] extends [never] ? WithActionMeta<Recovered<E>, RegisteredMeta> : [A] extends [void] ? unknown : WithActionMeta<Unwrapped<A> | Recovered<E>, RegisteredMeta>;
511
+ declare module "effect/Effect" {
512
+ interface Effect<out A, out E, out R> {
513
+ readonly unstable__ReactRouter_SerializesTo: [BrandData<A, E>];
514
+ }
515
+ }
516
+ declare module "effect/Cause" {
517
+ interface YieldableError {
518
+ readonly unstable__ReactRouter_SerializesTo: [never];
519
+ }
520
+ }
521
+ /**
522
+ * Route args for a loader or action written as a plain `Effect.fn`, such as
523
+ * `EffectRouteArgs<Route.ActionArgs>`. It keeps hooks that take `typeof loader`
524
+ * or `typeof action` typed, such as `useFetcher<typeof action>()`.
525
+ *
526
+ * Those hooks read a function's data through React Router's `SerializeFrom`.
527
+ * It treats a function whose argument extends `ClientDataFunctionArgs` as a
528
+ * client function, and gives its raw return value without serialization.
529
+ * Typegen's `Route.LoaderArgs` and `Route.ActionArgs` have the same members as
530
+ * `ClientDataFunctionArgs`, so a server function typed with them matches too.
531
+ * For an Effect, the raw return value is the Effect itself: only serialization
532
+ * reads the `unstable_SerializesTo` brand.
533
+ *
534
+ * `pattern` is optional here, so the argument no longer matches and the hooks
535
+ * read the brand. `Route.ComponentProps` and `RouteData` don't go through
536
+ * `SerializeFrom`, so they are correct without it.
537
+ */
538
+ type EffectRouteArgs<Args extends {
539
+ readonly pattern: string;
540
+ }> = Omit<Args, "pattern"> & {
541
+ readonly pattern?: string;
542
+ };
543
+ 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 };