react-router-effect 2.1.0 → 2.3.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.
package/dist/index.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { C as ReturnableDataError, D as WithActionMeta, E as isRouteError, O as actionMetaKey, S as AnyRouteError, T as ThrowableRedirectError, a as RegisteredMeta, b as RouteResult, c as Serialize, d as EffectNext, f as ErrorHandler, i as Register, l as ActionMetaArgs, n as EffectData, o as RegisteredRecover, p as FactoryTypes, r as EffectRouteArgs, s as RouteData, t as BrandData, u as EffectMiddlewareArgs, v as RequestContextKey, w as ThrowableDataError, x as makeEffectRouteFactory, y as RouteHandlers } from "./route-data-MgSnnR33.mjs";
2
- export { type ActionMetaArgs, type AnyRouteError, type BrandData, type EffectData, type EffectMiddlewareArgs, type EffectNext, type EffectRouteArgs, type ErrorHandler, type FactoryTypes, type Register, type RegisteredMeta, type RegisteredRecover, type RequestContextKey, ReturnableDataError, type RouteData, type RouteHandlers, type RouteResult, type Serialize, ThrowableDataError, ThrowableRedirectError, type WithActionMeta, actionMetaKey, isRouteError, makeEffectRouteFactory };
1
+ import { A as ThrowableRedirectError, C as RouteHandlers, D as AnyRouteError, E as makeEffectRouteFactory, M as WithRouteMeta, N as routeMetaKey, O as ReturnableDataError, S as RequestContextKey, T as RouteResult, _ as FactoryTypes, a as RegisteredMeta, c as Serialize, d as deferredFailureMessage, f as DeferHandled, g as ErrorHandler, h as EffectNext, i as Register, j as isRouteError, k as ThrowableDataError, l as DeferDiagnose, m as EffectMiddlewareArgs, n as EffectData, o as RegisteredRecover, p as DeferRecoverOf, r as EffectRouteArgs, s as RouteData, t as BrandData, u as DeferredPromise, w as RouteMetaArgs } from "./route-data-ByiuG-Yb.mjs";
2
+ export { type AnyRouteError, type BrandData, type DeferDiagnose, type DeferHandled, type DeferRecoverOf, type DeferredPromise, type EffectData, type EffectMiddlewareArgs, type EffectNext, type EffectRouteArgs, type ErrorHandler, type FactoryTypes, type Register, type RegisteredMeta, type RegisteredRecover, type RequestContextKey, ReturnableDataError, type RouteData, type RouteHandlers, type RouteMetaArgs, type RouteResult, type Serialize, ThrowableDataError, ThrowableRedirectError, type WithRouteMeta, deferredFailureMessage, isRouteError, makeEffectRouteFactory, routeMetaKey };
package/dist/index.mjs CHANGED
@@ -1,12 +1,15 @@
1
- import { Context, Data, Effect, Predicate } from "effect";
2
- import { createContext, data, redirect } from "react-router";
1
+ import { Cause, Context, Data, Effect, Exit, Option, Predicate, Scope } from "effect";
2
+ import { UNSAFE_ErrorResponseImpl, createContext, data, redirect } from "react-router";
3
3
  import { HttpServerRespondable, HttpServerResponse } from "effect/http";
4
- // src/action-meta.ts
5
- /** The key that holds the metadata in action data. */
6
- const actionMetaKey = "__meta";
7
- const DataWithResponseInitClass = data(void 0).constructor;
8
- /** True for a `data(...)` wrapper, never for a business field that looks like one. */
9
- const isDataWithResponseInit = (value) => value instanceof DataWithResponseInitClass;
4
+ // src/route-meta.ts
5
+ /** The key that holds the metadata in loader and action data. */
6
+ const routeMetaKey = "__meta";
7
+ /**
8
+ * True for a `data(...)` wrapper, by the same test React Router applies. A
9
+ * class test fails when the app and the adapter load different copies of
10
+ * React Router, as under the Vite dev server.
11
+ */
12
+ const isDataWithResponseInit = (value) => Predicate.hasProperty(value, "type") && Predicate.hasProperty(value, "data") && Predicate.hasProperty(value, "init") && value.type === "DataWithResponseInit";
10
13
  /** True for a plain object, which can be copied without losing behavior. */
11
14
  const isPlainRecord = (value) => {
12
15
  if (!Predicate.isObject(value) || Array.isArray(value)) return false;
@@ -14,21 +17,21 @@ const isPlainRecord = (value) => {
14
17
  return prototype === Object.prototype || prototype === null;
15
18
  };
16
19
  /**
17
- * Attaches `meta` to the action result. A plain record gets a copy with the
20
+ * Attaches `meta` to a loader or action result. A plain record gets a copy with the
18
21
  * metadata key; a `data(...)` wrapper gets it on its body and keeps its init.
19
22
  * Any other value is returned unchanged.
20
23
  */
21
- const attachActionMeta = (value, meta) => {
24
+ const attachRouteMeta = (value, meta) => {
22
25
  if (isDataWithResponseInit(value)) {
23
26
  if (!isPlainRecord(value.data)) return value;
24
27
  return data({
25
28
  ...value.data,
26
- [actionMetaKey]: meta
29
+ [routeMetaKey]: meta
27
30
  }, value.init ?? void 0);
28
31
  }
29
32
  return isPlainRecord(value) ? {
30
33
  ...value,
31
- [actionMetaKey]: meta
34
+ [routeMetaKey]: meta
32
35
  } : value;
33
36
  };
34
37
  // src/errors.ts
@@ -72,6 +75,84 @@ const Respond = {
72
75
  };
73
76
  /** Narrows an unknown value to one of the library's route errors. */
74
77
  const isRouteError = (value) => value instanceof ReturnableDataError || value instanceof ThrowableDataError || value instanceof ThrowableRedirectError;
78
+ // src/defer.ts
79
+ /**
80
+ * The scope that deferred work forks into. The route runner sets it for each
81
+ * loader or action run, and closes it when the request aborts or the route
82
+ * Effect fails. Outside a route runner it is unset, and deferred work runs
83
+ * detached.
84
+ */
85
+ const DeferScope = Context.Reference("react-router-effect/DeferScope", { defaultValue: () => void 0 });
86
+ /**
87
+ * Gives a route Effect a scope for its deferred work. The scope closes, and
88
+ * interrupts the deferred fibers, when `signal` aborts or the Effect fails: a
89
+ * failed loader sends no data, so no `<Await>` waits for the work.
90
+ */
91
+ const withDeferScope = (effect, signal) => Effect.suspend(() => {
92
+ const scope = Scope.makeUnsafe("parallel");
93
+ const close = (exit) => Scope.close(scope, exit);
94
+ signal.addEventListener("abort", () => Effect.runFork(close(Exit.interrupt())), { once: true });
95
+ return effect.pipe(Effect.provideService(DeferScope, scope), Effect.onError((cause) => close(Exit.failCause(cause))));
96
+ });
97
+ /**
98
+ * The message every deferred rejection has. The server logs the cause.
99
+ *
100
+ * @stability unstable
101
+ */
102
+ const deferredFailureMessage = "Deferred route data failed";
103
+ /**
104
+ * The rejection the browser gets for a defect or an interrupt: a constant
105
+ * message and no stack, in every server mode. React Router removes server
106
+ * details only in production.
107
+ */
108
+ const deferredFailure = () => {
109
+ const error = /* @__PURE__ */ new Error(deferredFailureMessage);
110
+ error.stack = void 0;
111
+ return error;
112
+ };
113
+ /**
114
+ * Forks `self` into the run's defer scope and gives a promise of its result.
115
+ * A failure rejects the promise with its `ErrorResponse`, which React Router
116
+ * streams to `useAsyncError`. A defect or interrupt rejects it with
117
+ * `deferredFailure`, and the server logs a defect's full cause.
118
+ */
119
+ const forkDeferred = (self) => Effect.gen(function* () {
120
+ const scope = yield* DeferScope;
121
+ const work = self.pipe(Effect.tapCause((cause) => Cause.hasDies(cause) ? Effect.logError(deferredFailureMessage, cause) : Effect.void));
122
+ const fiber = yield* scope === void 0 ? Effect.forkDetach(work) : Effect.forkIn(work, scope);
123
+ const promise = new Promise((resolve, reject) => {
124
+ fiber.addObserver((exit) => {
125
+ if (Exit.isSuccess(exit)) return resolve(exit.value);
126
+ reject(Cause.hasDies(exit.cause) ? deferredFailure() : Option.getOrElse(Cause.findErrorOption(exit.cause), deferredFailure));
127
+ });
128
+ });
129
+ promise.catch(() => void 0);
130
+ return promise;
131
+ });
132
+ /** A thrown `data(...)` as an `ErrorResponse`, with React Router's defaults. */
133
+ const toErrorResponse = (thrown) => new UNSAFE_ErrorResponseImpl(thrown.init?.status ?? 500, thrown.init?.statusText ?? "Internal Server Error", thrown.data);
134
+ /**
135
+ * A library route error raised in deferred work: `Respond.early` resolves the
136
+ * promise with its value, and `Respond.throw` rejects it with an
137
+ * `ErrorResponse`. A redirect cannot happen after the response started, so it
138
+ * is a defect.
139
+ */
140
+ const deferRouteError = (e) => {
141
+ if (e instanceof ReturnableDataError) return Effect.succeed(e.data);
142
+ if (e instanceof ThrowableDataError) return Effect.fail(toErrorResponse(data(e.data, e.init)));
143
+ return Effect.die(e);
144
+ };
145
+ /**
146
+ * A handler's `Effect.fail(response)` in deferred work, as an `ErrorResponse`:
147
+ * a `data(...)` as React Router converts a thrown one, and a `Response` with
148
+ * its body read as React Router reads it, JSON or text. A redirect is a defect.
149
+ */
150
+ const deferFailureResponse = (failure) => {
151
+ if (!(failure instanceof Response)) return Effect.fail(toErrorResponse(failure));
152
+ if (failure.status >= 300 && failure.status < 400) return Effect.die(failure);
153
+ const isJson = /\bapplication\/json\b/.test(failure.headers.get("Content-Type") ?? "");
154
+ return Effect.promise(() => isJson && failure.body !== null ? failure.json() : failure.text()).pipe(Effect.flatMap((body) => Effect.fail(new UNSAFE_ErrorResponseImpl(failure.status, failure.statusText, body))));
155
+ };
75
156
  // src/factory.ts
76
157
  const internalServerError = () => Effect.fail(new Response("Internal Server Error", { status: 500 }));
77
158
  /**
@@ -187,7 +268,8 @@ function makeEffectRouteFactory() {
187
268
  ...Respond
188
269
  };
189
270
  const userHandlers = config.errorHandlers ?? {};
190
- const isUserError = (e) => typeof e === "object" && e !== null && "_tag" in e && e._tag in userHandlers;
271
+ /** The tags of the registered domain errors. */
272
+ const userTags = Object.keys(userHandlers);
191
273
  /**
192
274
  * Runs a handler's effect to completion: dispatches its whole error channel
193
275
  * (registered domain errors, library route errors, respondables, or the 500
@@ -196,9 +278,9 @@ function makeEffectRouteFactory() {
196
278
  * - they differ only in how they build the effect and what `context` they read.
197
279
  */
198
280
  function runProgram(effect, context, signal) {
199
- const program = effect.pipe(Effect.catchIf((_e) => true, (e) => {
200
- if (isUserError(e)) {
201
- const out = userHandlers[e._tag](e);
281
+ const program = withDeferScope(effect, signal).pipe(Effect.catchIf((_e) => true, (e) => {
282
+ for (const tag of userTags) if (Predicate.isTagged(e, tag)) {
283
+ const out = userHandlers[tag](e);
202
284
  return isRouteError(out) ? processRouteError(out) : out;
203
285
  }
204
286
  if (isRouteError(e)) return processRouteError(e);
@@ -268,19 +350,74 @@ function makeEffectRouteFactory() {
268
350
  function runResult(result, args) {
269
351
  return Effect.isEffect(result) ? runProgram(result, args.context, args.request.signal) : result;
270
352
  }
271
- const actionMeta = config.actionMeta;
353
+ /**
354
+ * A failure of deferred work, through the error handlers: a recovered
355
+ * value resolves the promise, a thrown response rejects it with an
356
+ * `ErrorResponse`. Anything else, such as a domain error with no handler,
357
+ * is a defect: the promise rejects with no server details.
358
+ */
359
+ function dispatchDeferred(e) {
360
+ for (const tag of userTags) if (Predicate.isTagged(e, tag)) {
361
+ const out = userHandlers[tag](e);
362
+ return isRouteError(out) ? deferRouteError(out) : out.pipe(Effect.catch(deferFailureResponse));
363
+ }
364
+ return isRouteError(e) ? deferRouteError(e) : Effect.die(e);
365
+ }
366
+ /**
367
+ * Starts an Effect without waiting for it, and gives a promise of its
368
+ * result for loader data. React Router streams the promise to `<Await>`.
369
+ *
370
+ * The work gets the services and the span of the Effect that defers it.
371
+ * It keeps running after the loader returns, and is interrupted when the
372
+ * request aborts or the loader fails. A client-side navigation away does
373
+ * not abort the request, so the work then runs until it ends or the stream
374
+ * times out.
375
+ *
376
+ * The work may fail only with the factory's domain errors,
377
+ * `Respond.early` and `Respond.throw`. The error handlers turn them into
378
+ * the promise's value, or an `ErrorResponse` for `useAsyncError`, as they
379
+ * do for a loader. A handler that redirects, a domain error with no
380
+ * handler, a defect or an interrupt rejects the promise with an error that
381
+ * holds no server details, and the server logs the full cause.
382
+ *
383
+ * ```ts
384
+ * export const { Respond, defer } = routeFactory;
385
+ *
386
+ * export const loader = Effect.fn("loader")(function* () {
387
+ * const user = yield* Users.current;
388
+ * return {
389
+ * user,
390
+ * reviews: yield* defer(Reviews.list(user.id)),
391
+ * stats: yield* Stats.load.pipe(
392
+ * Effect.catchTag("StatsUnavailable", () => Effect.succeed(null)),
393
+ * defer,
394
+ * ),
395
+ * };
396
+ * });
397
+ * ```
398
+ *
399
+ * @stability unstable
400
+ */
401
+ function defer(self) {
402
+ const work = self.pipe(Effect.catch(dispatchDeferred));
403
+ return forkDeferred(work);
404
+ }
405
+ const routeMeta = config.routeMeta;
406
+ /** Runs a loader or action's result and attaches the route metadata to its data. */
407
+ async function withRouteMeta(result, args) {
408
+ const resolved = await runResult(result, args);
409
+ return routeMeta === void 0 ? resolved : attachRouteMeta(resolved, routeMeta(args));
410
+ }
272
411
  return {
273
412
  makeLoader: makeLoaderOrAction,
274
413
  makeAction: makeLoaderOrAction,
275
414
  makeMiddleware,
276
415
  Respond: Respond$1,
416
+ defer,
277
417
  /** The route runner the server adapter applies to every route module. */
278
418
  handlers: {
279
- loader: (fn) => async (args) => runResult(fn(args), args),
280
- action: (fn) => async (args) => {
281
- const result = await runResult(fn(args), args);
282
- return actionMeta === void 0 ? result : attachActionMeta(result, actionMeta(args));
283
- },
419
+ loader: (fn) => async (args) => await withRouteMeta(fn(args), args),
420
+ action: (fn) => async (args) => await withRouteMeta(fn(args), args),
284
421
  middleware: (fn) => async (args, next) => {
285
422
  const routeArgs = args;
286
423
  return runResult(fn({
@@ -295,4 +432,4 @@ function makeEffectRouteFactory() {
295
432
  };
296
433
  };
297
434
  }
298
- export { ReturnableDataError, ThrowableDataError, ThrowableRedirectError, actionMetaKey, isRouteError, makeEffectRouteFactory };
435
+ export { ReturnableDataError, ThrowableDataError, ThrowableRedirectError, deferredFailureMessage, isRouteError, makeEffectRouteFactory, routeMetaKey };
@@ -1,22 +1,22 @@
1
- import { Context, Effect, ManagedRuntime } from "effect";
1
+ import { Context, Effect, ManagedRuntime, Scope } from "effect";
2
2
  import { ActionFunctionArgs, LoaderFunctionArgs, MiddlewareFunction, RouterContext, RouterContextProvider, UNSAFE_DataWithResponseInit, useLoaderData } from "react-router";
3
3
  import { HttpServerRespondable } from "effect/http";
4
- /** The key that holds the metadata in action data. */
5
- declare const actionMetaKey = "__meta";
4
+ /** The key that holds the metadata in loader and action data. */
5
+ declare const routeMetaKey = "__meta";
6
6
  /**
7
7
  * Adds optional metadata to a record. Only records get metadata, because a
8
8
  * structural type cannot tell a plain object from a class instance, and other
9
9
  * values (strings, arrays, `Response`s) have nowhere to put it.
10
10
  */
11
- type RecordWithMeta<T, Meta> = T extends Record<string, unknown> ? Omit<T, typeof actionMetaKey> & {
12
- [actionMetaKey]?: Meta;
11
+ type RecordWithMeta<T, Meta> = T extends Record<string, unknown> ? Omit<T, typeof routeMetaKey> & {
12
+ [routeMetaKey]?: Meta;
13
13
  } : T;
14
14
  /**
15
- * The type of action data after metadata is attached. A `data(...)` wrapper
15
+ * The type of loader or action data after metadata is attached. A `data(...)` wrapper
16
16
  * gets metadata on its body. With no configured metadata (`Meta` is `never`)
17
17
  * the data is unchanged.
18
18
  */
19
- type WithActionMeta<T, Meta> = [Meta] extends [never] ? T : T extends UNSAFE_DataWithResponseInit<infer Body> ? UNSAFE_DataWithResponseInit<RecordWithMeta<Body, Meta>> : RecordWithMeta<T, Meta>;
19
+ type WithRouteMeta<T, Meta> = [Meta] extends [never] ? T : T extends UNSAFE_DataWithResponseInit<infer Body> ? UNSAFE_DataWithResponseInit<RecordWithMeta<Body, Meta>> : RecordWithMeta<T, Meta>;
20
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
21
  readonly _tag: "ReturnableRouteError";
22
22
  } & Readonly<A>;
@@ -144,6 +144,25 @@ type ErrorHandlers<DomainError extends Tagged, Returns> = { [Tag in keyof Return
144
144
  type RecoverOf<DomainError extends Tagged, Returns, E> = { [Tag in keyof Returns]: [Extract<E, Extract<DomainError, {
145
145
  readonly _tag: Tag;
146
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
+ * The errors the factory's `defer` handles in deferred work: the declared
149
+ * domain errors, and `Respond.early` / `Respond.throw`. A redirect is not one
150
+ * of them: it cannot happen after the response started.
151
+ *
152
+ * @stability unstable
153
+ */
154
+ type DeferHandled<DomainError> = DomainError | ReturnableDataError<unknown> | ThrowableDataError<unknown>;
155
+ /**
156
+ * What a deferred promise resolves with when the work fails with `E`: the
157
+ * value of a `Respond.early` raised directly, and what the registered
158
+ * handlers recover. Unlike loader data, a recovered `Respond.early` gives its
159
+ * value without the `data(...)` wrapper: a promise has no response to set.
160
+ *
161
+ * @stability unstable
162
+ */
163
+ type DeferRecoverOf<DomainError extends Tagged, Returns, E> = ReturnableBodyOf<E> | { [Tag in keyof Returns]: [Extract<E, Extract<DomainError, {
164
+ readonly _tag: Tag;
165
+ }>>] extends [never] ? never : (Returns[Tag] extends ReturnableDataError<infer B> ? B : never) | (Returns[Tag] extends Effect.Effect<infer SuccessValue, any, any> ? SuccessValue : never); }[keyof Returns];
147
166
  /**
148
167
  * Errors the library deals with on the handler's behalf - so the loader/action/
149
168
  * middleware needn't handle them itself:
@@ -209,6 +228,12 @@ type Diagnose<E, R, DomainError, Provided, AlsoHandled = never> = IsAny<E> exten
209
228
  * it in middleware and pass it to the factory's `requestContext` - the runner
210
229
  * reads it on every request and provides its services to the loader/action.
211
230
  *
231
+ * With the server adapter, do not set it to a new context. The adapter sets it
232
+ * to the request fiber's context before React Router runs, and a new context
233
+ * replaces those services, such as `HttpServerRequest` and the server span.
234
+ * Provide request services with a router middleware on `ReactRouter.routes`.
235
+ * The middleware below is for an app without the adapter.
236
+ *
212
237
  * It's just a `RouterContext`; create one with React Router's `createContext`:
213
238
  *
214
239
  * ```ts
@@ -222,7 +247,7 @@ type Diagnose<E, R, DomainError, Provided, AlsoHandled = never> = IsAny<E> exten
222
247
  *
223
248
  * export const requestContext: RequestContextKey<RequestContext> = createContext();
224
249
  *
225
- * // middleware:
250
+ * // middleware, in an app without the server adapter:
226
251
  * export const middleware: Route.MiddlewareFunction[] = [
227
252
  * ({ context, request }, next) => {
228
253
  * context.set(requestContext, Context.make(RequestContext, { userId: readUser(request) }));
@@ -232,8 +257,8 @@ type Diagnose<E, R, DomainError, Provided, AlsoHandled = never> = IsAny<E> exten
232
257
  * ```
233
258
  */
234
259
  type RequestContextKey<ReqServices> = RouterContext<Context.Context<ReqServices>>;
235
- /** What `actionMeta` reads: the request and the router context of the action. */
236
- interface ActionMetaArgs {
260
+ /** What `routeMeta` reads: the request and the router context of the loader or action. */
261
+ interface RouteMetaArgs {
237
262
  readonly request: Request;
238
263
  readonly context: Readonly<RouterContextProvider>;
239
264
  }
@@ -399,27 +424,61 @@ declare function makeEffectRouteFactory<DomainError extends Tagged = never>(): <
399
424
  */
400
425
  respond?: ExtraRespond;
401
426
  /**
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.
427
+ * Request-scoped metadata to attach to the data of every loader and action
428
+ * under `__meta`, read after the function resolves. The server adapter
429
+ * attaches it to every loader and action it runs, including plain functions
430
+ * and `makeLoader` / `makeAction` wrappers. A thrown result gets none. Only
431
+ * plain records and `data(...)` bodies include it; other values are returned
432
+ * unchanged.
407
433
  */
408
- actionMeta?: (args: ActionMetaArgs) => Meta;
434
+ routeMeta?: (args: RouteMetaArgs) => Meta;
409
435
  }) => {
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>>;
436
+ 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<WithRouteMeta<A | DirectRecover<E> | RecoverOf<DomainError, Returns, E>, Meta>>;
437
+ 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<WithRouteMeta<A | DirectRecover<E> | RecoverOf<DomainError, Returns, E>, Meta>>;
412
438
  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
439
  Respond: {
414
440
  early: <D>(value: D, init?: number | ResponseInit) => ReturnableDataError<D>;
415
441
  throw: <D>(value: D, init?: number | ResponseInit) => ThrowableDataError<D>;
416
442
  redirect: (url: string, init?: number | ResponseInit) => ThrowableRedirectError;
417
443
  } & ExtraRespond;
444
+ defer: <A, E, R>(self: Effect.Effect<A, E, R> & DeferDiagnose<E, DeferHandled<DomainError>>) => Effect.Effect<DeferredPromise<A | DeferRecoverOf<DomainError, Returns, E>>, never, R>;
418
445
  /** The route runner the server adapter applies to every route module. */
419
446
  handlers: RouteHandlers;
420
447
  /** Type-only: what the adapter's route check and `Register` read. */
421
448
  "~types": FactoryTypes<DomainError, Returns, RServices | ReqServices, Meta>;
422
449
  };
450
+ /**
451
+ * The compile-time diagnostic of the factory's `defer`. A deferred Effect
452
+ * runs after the response started, so a failure cannot change the response.
453
+ * The Effect must handle every error it can fail with before it is deferred,
454
+ * except the `Handled` ones, which the error handlers turn into a value or an
455
+ * `ErrorResponse`. Resolves to `unknown` (a no-op intersection) when every
456
+ * error is handled.
457
+ *
458
+ * @stability unstable
459
+ */
460
+ type DeferDiagnose<E, Handled> = IsAny<E> extends true ? unknown : [Exclude<E, Handled>] extends [never] ? unknown : {
461
+ "react-router-effect": "A deferred effect runs after the response started, so it can fail only with the factory's domain errors, Respond.early and Respond.throw. Handle the other errors before defer: recover them into data (e.g. Effect.catchTag), or make them defects with Effect.orDie.";
462
+ unhandledErrors: NameError<Exclude<E, Handled>>;
463
+ };
464
+ /**
465
+ * The promise that the factory's `defer` gives: a `Promise` of the deferred Effect's
466
+ * success, which React Router streams to `<Await>`. The work started when the
467
+ * Effect was deferred.
468
+ *
469
+ * It is a type of its own so that an Effect that succeeds with it states
470
+ * that the promise is not awaited on purpose. The language service's
471
+ * `promiseInEffectSuccess` rule reports only the global `Promise` type.
472
+ *
473
+ * @stability unstable
474
+ */
475
+ interface DeferredPromise<A> extends Promise<A> {}
476
+ /**
477
+ * The message every deferred rejection has. The server logs the cause.
478
+ *
479
+ * @stability unstable
480
+ */
481
+ declare const deferredFailureMessage = "Deferred route data failed";
423
482
  /**
424
483
  * Registers what the app's route factory turns failures into, so that a
425
484
  * loader or action written as a plain `Effect.fn` types its data the way a
@@ -440,8 +499,8 @@ declare function makeEffectRouteFactory<DomainError extends Tagged = never>(): <
440
499
  * interface Register {
441
500
  * // Each error tag a handler recovers, and the data it recovers into.
442
501
  * recover: { FormValidationError: { reply: SubmissionResponse } };
443
- * // What `actionMeta` returns.
444
- * actionMeta: { realtimeEventIds: ReadonlyArray<string> };
502
+ * // What `routeMeta` returns.
503
+ * routeMeta: { realtimeEventIds: ReadonlyArray<string> };
445
504
  * }
446
505
  * }
447
506
  * ```
@@ -451,9 +510,9 @@ interface Register$1 {}
451
510
  type RegisteredRecover = Register$1 extends {
452
511
  readonly recover: infer Recover;
453
512
  } ? Recover : {};
454
- /** The registered action metadata, or `never` when there is none. */
513
+ /** The registered route metadata, or `never` when there is none. */
455
514
  type RegisteredMeta = Register$1 extends {
456
- readonly actionMeta: infer Meta;
515
+ readonly routeMeta: infer Meta;
457
516
  } ? Meta : never;
458
517
  /** What a failure of type `E` recovers into: `Respond.early` bodies and registered handlers. */
459
518
  type Recovered<E> = (E extends ReturnableDataError<infer Body> ? Body : never) | (E extends {
@@ -468,9 +527,9 @@ type Serialize<T> = ReturnType<typeof useLoaderData<() => Promise<T>>>;
468
527
  /**
469
528
  * The data an Effect that succeeds with `A` and fails with `E` gives the
470
529
  * component: its success and what its failures recover into, serialized as
471
- * React Router serializes them, with the registered action metadata.
530
+ * React Router serializes them, with the registered route metadata.
472
531
  */
473
- type EffectData<A, E> = WithActionMeta<Serialize<A | Recovered<E>>, RegisteredMeta>;
532
+ type EffectData<A, E> = WithRouteMeta<Serialize<A | Recovered<E>>, RegisteredMeta>;
474
533
  /**
475
534
  * The route data of a loader or action, for React Router's hooks:
476
535
  * `useFetcher<RouteData<typeof action>>()`. A function that returns an Effect
@@ -507,7 +566,7 @@ type Unwrapped<A> = A extends Response ? never : A extends UNSAFE_DataWithRespon
507
566
  * checks library types (`skipLibCheck: false`) then gets TS2636 in Effect's own
508
567
  * declarations. `test/consumer` checks the built package for that.
509
568
  */
510
- type BrandData<A, E> = [A] extends [never] ? WithActionMeta<Recovered<E>, RegisteredMeta> : [A] extends [void] ? unknown : WithActionMeta<Unwrapped<A> | Recovered<E>, RegisteredMeta>;
569
+ type BrandData<A, E> = [A] extends [never] ? WithRouteMeta<Recovered<E>, RegisteredMeta> : [A] extends [void] ? unknown : WithRouteMeta<Unwrapped<A> | Recovered<E>, RegisteredMeta>;
511
570
  declare module "effect/Effect" {
512
571
  interface Effect<out A, out E, out R> {
513
572
  readonly unstable__ReactRouter_SerializesTo: [BrandData<A, E>];
@@ -540,4 +599,4 @@ type EffectRouteArgs<Args extends {
540
599
  }> = Omit<Args, "pattern"> & {
541
600
  readonly pattern?: string;
542
601
  };
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 };
602
+ export { ThrowableRedirectError as A, RouteHandlers as C, AnyRouteError as D, makeEffectRouteFactory as E, WithRouteMeta as M, routeMetaKey as N, ReturnableDataError as O, RequestContextKey as S, RouteResult as T, FactoryTypes as _, RegisteredMeta as a, LibraryHandled as b, Serialize as c, deferredFailureMessage as d, DeferHandled as f, ErrorHandler as g, EffectNext as h, Register$1 as i, isRouteError as j, ThrowableDataError as k, DeferDiagnose as l, EffectMiddlewareArgs as m, EffectData as n, RegisteredRecover as o, DeferRecoverOf as p, EffectRouteArgs as r, RouteData as s, BrandData as t, DeferredPromise as u, FailureResponse as v, RouteMetaArgs as w, NameError as x, IsAny as y };