react-router-effect 2.0.0 → 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.
package/dist/index.d.mts CHANGED
@@ -1,342 +1,2 @@
1
- import { Context, Effect, ManagedRuntime } from "effect";
2
- import { HttpServerRespondable } from "effect/http";
3
- import { ActionFunctionArgs, LoaderFunctionArgs, MiddlewareFunction, RouterContext, UNSAFE_DataWithResponseInit } from "react-router";
4
- //#region src/errors.d.ts
5
- 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 & {
6
- readonly _tag: "ReturnableRouteError";
7
- } & Readonly<A>;
8
- /** Recoverable: short-circuits the loader but returns `data(...)` the component reads. */
9
- export declare class ReturnableDataError<D> extends ReturnableDataError_base<{
10
- data: D;
11
- init?: number | ResponseInit;
12
- }> {}
13
- 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 & {
14
- readonly _tag: "ThrowableRouteError";
15
- } & Readonly<A>;
16
- /** Throwable: rejects with `data(...)` → boundary as an `ErrorResponse`. */
17
- export declare class ThrowableDataError<D> extends ThrowableDataError_base<{
18
- data: D;
19
- init?: number | ResponseInit;
20
- }> {}
21
- 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 & {
22
- readonly _tag: "ThrowableRedirectError";
23
- } & Readonly<A>;
24
- /** Throwable: rejects with a redirect `Response`. */
25
- export declare class ThrowableRedirectError extends ThrowableRedirectError_base<{
26
- url: string;
27
- init?: number | ResponseInit;
28
- }> {}
29
- /** Every library route error, as a single union. */
30
- type AnyRouteError = ReturnableDataError<unknown> | ThrowableDataError<unknown> | ThrowableRedirectError;
31
- /**
32
- * The library's base helpers for raising route errors (`early` / `throw` /
33
- * `redirect`). This is internal: it isn't exported from the package. The factory
34
- * hands it to your config builder and returns it (merged with any `respond`
35
- * extensions) so your app imports a single `Respond`:
36
- *
37
- * ```ts
38
- * export const { makeLoader, Respond } = makeEffectRouteFactory<DomainErrors>()(
39
- * (Respond) => ({
40
- * respond: { formError: (reply) => new FormError({ reply }) },
41
- * errorHandlers: { FormError: (e) => Respond.early({ reply: e.reply }) },
42
- * }),
43
- * );
44
- * ```
45
- */
46
- declare const Respond: {
47
- /** Recover: short-circuit and hand `value` to the component as loader/action data. */
48
- early: <D>(value: D, init?: number | ResponseInit) => ReturnableDataError<D>;
49
- /** Throw: short-circuit to the error boundary with `data(value, init)`. */
50
- throw: <D>(value: D, init?: number | ResponseInit) => ThrowableDataError<D>;
51
- /** Throw: short-circuit with a redirect `Response`. */
52
- redirect: (url: string, init?: number | ResponseInit) => ThrowableRedirectError;
53
- };
54
- /** Narrows an unknown value to one of the library's route errors. */
55
- export declare const isRouteError: (value: unknown) => value is AnyRouteError;
56
- //#endregion
57
- //#region src/factory.d.ts
58
- /** The library's base `Respond` helpers (`early` / `throw` / `redirect`). */
59
- type BaseRespond = typeof Respond;
60
- type Tagged = {
61
- readonly _tag: string;
62
- };
63
- /**
64
- * Everything a failure branch rejects with. React Router routes a thrown
65
- * `DataWithResponseInit` to the boundary as an `ErrorResponse`, and a thrown
66
- * `Response` (including `redirect(...)`) as-is. Pinning every `Effect.fail` branch
67
- * to this union keeps inference from fixing the error channel to the first one.
68
- */
69
- type FailureResponse = UNSAFE_DataWithResponseInit<unknown> | Response;
70
- /**
71
- * What a handler may return, to *remap* a domain error:
72
- * - a **library route error** — `Respond.early(value)` (recover), `Respond.throw(data)`
73
- * or `Respond.redirect(url)` (throw) — which the library then processes; or
74
- * - an **`Effect`** — `Effect.succeed(value)` to recover with `value`, or
75
- * `Effect.fail(response)` to throw `response`.
76
- */
77
- type ErrorHandler<Err> = (error: Err) => AnyRouteError | Effect.Effect<unknown, FailureResponse>;
78
- /** The body of every `ReturnableDataError` raised directly in `E` (`Respond.early`). */
79
- type ReturnableBodyOf<E> = E extends ReturnableDataError<infer Body> ? Body : never;
80
- /**
81
- * The recover contribution of a `Respond.early` raised directly in the loader.
82
- * Guarded so a loader that never recovers directly doesn't leak
83
- * `DataWithResponseInit<never>` into its return type.
84
- */
85
- type DirectRecover<E> = [ReturnableBodyOf<E>] extends [never] ? never : UNSAFE_DataWithResponseInit<ReturnableBodyOf<E>>;
86
- /** What a handler may return: a library route error or an `Effect` failing with a response. */
87
- type HandlerReturn = AnyRouteError | Effect.Effect<unknown, FailureResponse>;
88
- /** A `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) — `(args, next) => ...`. */
89
- type AnyMiddlewareFunction = (...args: never[]) => unknown;
90
- /**
91
- * The `args` a `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) is called
92
- * with — inferred from the type argument so `makeMiddleware` can be pinned to a specific
93
- * generated route's middleware type.
94
- */
95
- type MiddlewareArgsOf<Middleware extends AnyMiddlewareFunction> = Parameters<Middleware>[0];
96
- /**
97
- * The `next` a `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) is called
98
- * with — react-router's own `() => Promise<Result>`, not exported under its own name.
99
- */
100
- type MiddlewareNextOf<Middleware extends AnyMiddlewareFunction> = Parameters<Middleware>[1];
101
- /**
102
- * The `Result` a `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) resolves
103
- * with — i.e. what its `next()` resolves to, and what the middleware itself may return.
104
- * Read through `Awaited` because react-router declares `next` as an *interface* with a
105
- * call signature (`MiddlewareNextFunction`), and a route's own may resolve synchronously.
106
- */
107
- type MiddlewareResultOf<Middleware extends AnyMiddlewareFunction> = MiddlewareNextOf<Middleware> extends ((...args: never[]) => infer Result) ? Awaited<Result> : never;
108
- /**
109
- * The inferred map of handler RETURN types, keyed by domain-error tag. Making the
110
- * factory generic over *this* (rather than the whole handler map) is what lets
111
- * `errorHandlers` contextually type each handler's `error` param from its key —
112
- * no annotation needed — while still capturing precise return types for recovery.
113
- */
114
- type HandlerReturns<DomainError extends Tagged> = Partial<Record<DomainError["_tag"], HandlerReturn>>;
115
- /**
116
- * The `errorHandlers` field shape for a given return map: one entry per registered
117
- * tag, whose `error` is the matching domain error (so it autocompletes and types
118
- * itself) and whose return is the precise, inferred `Returns[Tag]`.
119
- */
120
- type ErrorHandlers<DomainError extends Tagged, Returns> = { [Tag in keyof Returns]: (error: Extract<DomainError, {
121
- readonly _tag: Tag;
122
- }>) => Returns[Tag]; };
123
- /**
124
- * What the registered handlers recover into loader/action data — from each
125
- * handler's return type, but only for handlers whose error can actually occur in
126
- * `E`:
127
- * - a remapped `ReturnableDataError<B>` recovers as `DataWithResponseInit<B>`;
128
- * - an `Effect.succeed(value)` recovers as `value`;
129
- * - throwables, redirects and `Effect.fail(...)` contribute nothing.
130
- */
131
- type RecoverOf<DomainError extends Tagged, Returns, E> = { [Tag in keyof Returns]: [Extract<E, Extract<DomainError, {
132
- readonly _tag: Tag;
133
- }>>] 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];
134
- /**
135
- * Errors the library deals with on the handler's behalf — so the loader/action/
136
- * middleware needn't handle them itself:
137
- * - the app's **declared domain errors** (`DomainError`) — handled by a registered
138
- * handler, or left to the 500 / auto-respond default;
139
- * - **library route errors** raised directly via `Respond`;
140
- * - anything that renders itself via **`HttpServerRespondable`**;
141
- * - `AlsoHandled` — extra, call-site-specific already-handled errors. Only
142
- * `makeMiddleware` uses this, to admit the `FailureResponse` its `next()`
143
- * fails with (a downstream throw, already a finished response — the library
144
- * re-throws it as-is, so the middleware needn't catch it just to re-raise it).
145
- */
146
- type LibraryHandled<DomainError, AlsoHandled = never> = DomainError | AnyRouteError | HttpServerRespondable.Respondable | AlsoHandled;
147
- /**
148
- * What remains in a handler's error channel that the library will NOT handle —
149
- * i.e. service-specific errors the route consumes that aren't declared domain
150
- * errors. The loader/action/middleware must handle these itself (catch or map them).
151
- */
152
- type Unhandled<E, DomainError, AlsoHandled = never> = Exclude<E, LibraryHandled<DomainError, AlsoHandled>>;
153
- /**
154
- * A readable compile-time diagnostic. It's intersected onto the *effect return type*
155
- * of `fn` — so the effect's `A`/`E`/`R` still infer directly (naked), while a mismatch
156
- * reports this message, and the offending types, *at the `makeLoader`/`makeAction`
157
- * call* instead of the opaque "Expected 2 arguments". Resolves to `unknown` (a no-op
158
- * intersection) when the effect is fully handled and all its requirements are provided:
159
- *
160
- * - **Unhandled error** — the effect can fail with something that isn't a declared
161
- * domain error, a library route error, or respondable; `unhandledErrors` names them.
162
- * - **Missing requirement** — the effect requires a service that neither the
163
- * `runtime` nor the `requestContext` provides; `missingRequirements` names them.
164
- *
165
- * The message lives in an *inline* object literal (no named alias) so tsc/tsgo print
166
- * its text verbatim rather than collapsing it to an alias name.
167
- */
168
- /**
169
- * Names the unhandled errors for the diagnostic: a tagged error becomes its `_tag`
170
- * (the string you'd pass to `Effect.catchTag`), anything untagged stays as its type.
171
- */
172
- type NameError<U> = U extends {
173
- readonly _tag: infer Tag extends string;
174
- } ? Tag : U;
175
- /**
176
- * True exactly when `T` is (or, for a union, includes) `any` — not `unknown`, not
177
- * `never`, not any other type. An unrelated mistake elsewhere in the effect (a typo,
178
- * a call with the wrong number of arguments, a genuine syntax error) commonly makes
179
- * TypeScript infer `any` for `E`/`R` instead of reporting a clean type; running
180
- * `Diagnose` on that `any` produces a confusing "unhandled errors: any" message that
181
- * buries the real mistake. Guarding on this and bailing to `unknown` lets tsc's own,
182
- * on-site error surface instead. (`1 & T` is `any` when `T` is `any` — including when
183
- * `any` only appears as a union member, since intersection distributes over unions —
184
- * and `0` is assignable to `any` but not to any concrete type built from `1 & T`.)
185
- */
186
- type IsAny<T> = 0 extends 1 & T ? true : false;
187
- 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 : {
188
- "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.";
189
- missingRequirements: Exclude<R, Provided>;
190
- } : {
191
- "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.";
192
- unhandledErrors: NameError<Unhandled<E, DomainError, AlsoHandled>>;
193
- };
194
- /**
195
- * A React Router context key holding a per-request effect `Context.Context`. Set
196
- * it in middleware and pass it to the factory's `requestContext` — the runner
197
- * reads it on every request and provides its services to the loader/action.
198
- *
199
- * It's just a `RouterContext`; create one with React Router's `createContext`:
200
- *
201
- * ```ts
202
- * import { createContext } from "react-router";
203
- * import { Context } from "effect";
204
- * import type { RequestContextKey } from "react-router-effect";
205
- *
206
- * class RequestContext extends Context.Service<RequestContext, {
207
- * readonly userId: string;
208
- * }>()("app/RequestContext") {}
209
- *
210
- * export const requestContext: RequestContextKey<RequestContext> = createContext();
211
- *
212
- * // middleware:
213
- * export const middleware: Route.MiddlewareFunction[] = [
214
- * ({ context, request }, next) => {
215
- * context.set(requestContext, Context.make(RequestContext, { userId: readUser(request) }));
216
- * return next();
217
- * },
218
- * ];
219
- * ```
220
- */
221
- type RequestContextKey<ReqServices> = RouterContext<Context.Context<ReqServices>>;
222
- /**
223
- * Build `makeLoader` / `makeAction` / `makeMiddleware` for an application, wired to
224
- * its domain errors.
225
- *
226
- * Declare the app's **domain errors** as the type argument, then register an
227
- * *optional* handler per domain error in `errorHandlers`. The handler keys
228
- * autocomplete to your domain-error tags and each `error` parameter is typed from
229
- * its key — no annotation needed. It's curried so you can pin the domain errors
230
- * while the handler return types are still inferred:
231
- *
232
- * ```ts
233
- * type DomainErrors = MyDomainError | DbError | NotAuthorizedError;
234
- *
235
- * export const { makeLoader, makeAction, Respond } =
236
- * makeEffectRouteFactory<DomainErrors>()((Respond) => ({
237
- * errorHandlers: {
238
- * // `error` is typed as MyDomainError automatically. throw → error boundary
239
- * MyDomainError: (error) => Respond.throw({ message: error.message }, 400),
240
- * // DbError has no handler → falls through to the 500 default.
241
- * },
242
- * }));
243
- * ```
244
- *
245
- * The inner config is a **builder** — it receives the library's `Respond` helpers so
246
- * your handlers can recover/throw with them, and returns the config. `Respond` is
247
- * also returned from the factory (extended with any `respond` helpers you add), so
248
- * your app imports a *single* `Respond` — no auto-import ambiguity with a library one.
249
- *
250
- * A handler *remaps* the error by returning either a library route error
251
- * (`Respond.early` to recover; `Respond.throw` / `Respond.redirect` to throw) or an
252
- * `Effect` (`Effect.succeed(value)` to recover `value`; `Effect.fail(response)` to
253
- * throw). Recovered values become loader/action data, typed precisely.
254
- *
255
- * **Declared domain errors** may be left unhandled: if one implements
256
- * `HttpServerRespondable` it's rendered automatically, otherwise it falls through
257
- * to a 500. **Any other error** — a service-specific error the route consumes that
258
- * isn't a declared domain error — *must* be handled in the loader/action, or
259
- * `makeLoader`/`makeAction` fails to type-check.
260
- *
261
- * Pass a `runtime` (a `ManagedRuntime`, e.g. from `ManagedRuntime.make(AppLayer)`)
262
- * to provide your app's services once. Loader/action effects may then require those
263
- * services directly — no per-call `Effect.provide`:
264
- *
265
- * ```ts
266
- * const { makeLoader, makeAction } = makeEffectRouteFactory<DomainErrors>()(() => ({
267
- * runtime: getAppRuntime(), // provides Database, MyService, ...
268
- * errorHandlers: { ... },
269
- * }));
270
- *
271
- * // `MyService` is satisfied by the runtime, not provided here:
272
- * const loader = makeLoader((args: Route.LoaderArgs) =>
273
- * Effect.gen(function* () {
274
- * const svc = yield* MyService;
275
- * return { data: yield* svc.load(args) };
276
- * }),
277
- * );
278
- * ```
279
- *
280
- * `makeMiddleware` wraps a `Route.MiddlewareFunction` the same way: write it as an
281
- * effect, and register the same domain errors/handlers. Pin it to the route's
282
- * generated `Route.MiddlewareFunction` (a type argument, since middleware has no
283
- * separate args-only type) to type `args` and `next`'s result from that route; it
284
- * defaults to React Router's own `MiddlewareFunction`. The `next` handed to the
285
- * effect is `() => Effect.Effect<Result, FailureResponse>` — call it with `yield*`
286
- * to run the rest of the chain, and a downstream throw (e.g. a nested loader's
287
- * redirect) surfaces as a typed failure instead of a rejected promise:
288
- *
289
- * ```ts
290
- * const middleware = makeMiddleware<Route.MiddlewareFunction>((args, next) =>
291
- * Effect.gen(function* () {
292
- * const user = yield* getUser(args); // may fail with a declared domain error
293
- * if (!user) yield* Respond.redirect("/login");
294
- * return yield* next();
295
- * }),
296
- * );
297
- * ```
298
- */
299
- export declare function makeEffectRouteFactory<DomainError extends Tagged = never>(): <const Returns extends HandlerReturns<DomainError> = {}, RServices = never, ReqServices = never, const ExtraRespond extends Record<string, (...args: never[]) => unknown> = {}>(builder: (respond: BaseRespond) => {
300
- /**
301
- * An optional handler per declared domain error, keyed by its tag. The keys
302
- * autocomplete to your domain-error tags and each handler's `error` parameter
303
- * is typed automatically — no annotation needed. A handler *remaps* the error
304
- * by returning a library route error (the builder's `Respond.early` / `throw`
305
- * / `redirect`) or an `Effect` (`Effect.succeed`/`fail`). Omit to register none.
306
- */
307
- errorHandlers?: ErrorHandlers<DomainError, Returns>;
308
- /**
309
- * The app runtime that provides services to loader/action effects. When
310
- * set, effects may require its services (`RServices`, inferred from here)
311
- * without providing layers, and runs go through `runtime.runPromise`. When
312
- * omitted, `RServices` is `never` and effects must require nothing.
313
- */
314
- runtime?: ManagedRuntime.ManagedRuntime<RServices, any>;
315
- /**
316
- * A React Router context key (a {@link RequestContextKey}) holding a
317
- * per-request effect `Context.Context`. Middleware sets it for each request;
318
- * the runner reads `args.context.get(requestContext)` and provides those
319
- * services to the effect. Loader/action effects may then require
320
- * `ReqServices` (inferred from here) in addition to the runtime's services.
321
- */
322
- requestContext?: RequestContextKey<ReqServices>;
323
- /**
324
- * App-specific `Respond` helpers — merged onto the base `Respond` that the
325
- * factory returns, so your app imports one `Respond`. Typically opinionated
326
- * error constructors; annotate their parameters, e.g.
327
- * `{ formError: (reply: string) => new FormError({ reply }) }`. The base
328
- * helpers (`early` / `throw` / `redirect`) always win the merge.
329
- */
330
- respond?: ExtraRespond;
331
- }) => {
332
- 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>>;
333
- 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<A | DirectRecover<E> | RecoverOf<DomainError, Returns, E>>;
334
- 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>;
335
- Respond: {
336
- early: <D>(value: D, init?: number | ResponseInit) => ReturnableDataError<D>;
337
- throw: <D>(value: D, init?: number | ResponseInit) => ThrowableDataError<D>;
338
- redirect: (url: string, init?: number | ResponseInit) => ThrowableRedirectError;
339
- } & ExtraRespond;
340
- };
341
- //#endregion
342
- export type { AnyRouteError, ErrorHandler, RequestContextKey };
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 };
package/dist/index.mjs CHANGED
@@ -1,7 +1,37 @@
1
- import { Data, Effect } from "effect";
1
+ import { Context, Data, Effect, Predicate } from "effect";
2
+ import { createContext, data, redirect } from "react-router";
2
3
  import { HttpServerRespondable, HttpServerResponse } from "effect/http";
3
- import { data, redirect } from "react-router";
4
- //#region src/errors.ts
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;
10
+ /** True for a plain object, which can be copied without losing behavior. */
11
+ const isPlainRecord = (value) => {
12
+ if (!Predicate.isObject(value) || Array.isArray(value)) return false;
13
+ const prototype = Object.getPrototypeOf(value);
14
+ return prototype === Object.prototype || prototype === null;
15
+ };
16
+ /**
17
+ * Attaches `meta` to the action result. A plain record gets a copy with the
18
+ * metadata key; a `data(...)` wrapper gets it on its body and keeps its init.
19
+ * Any other value is returned unchanged.
20
+ */
21
+ const attachActionMeta = (value, meta) => {
22
+ if (isDataWithResponseInit(value)) {
23
+ if (!isPlainRecord(value.data)) return value;
24
+ return data({
25
+ ...value.data,
26
+ [actionMetaKey]: meta
27
+ }, value.init ?? void 0);
28
+ }
29
+ return isPlainRecord(value) ? {
30
+ ...value,
31
+ [actionMetaKey]: meta
32
+ } : value;
33
+ };
34
+ // src/errors.ts
5
35
  /** Recoverable: short-circuits the loader but returns `data(...)` the component reads. */
6
36
  var ReturnableDataError = class extends Data.TaggedError("ReturnableRouteError") {};
7
37
  /** Throwable: rejects with `data(...)` → boundary as an `ErrorResponse`. */
@@ -11,7 +41,7 @@ var ThrowableRedirectError = class extends Data.TaggedError("ThrowableRedirectEr
11
41
  /**
12
42
  * The library's base helpers for raising route errors (`early` / `throw` /
13
43
  * `redirect`). This is internal: it isn't exported from the package. The factory
14
- * hands it to your config builder and returns it (merged with any `respond`
44
+ * passes it to your config builder and returns it (merged with any `respond`
15
45
  * extensions) so your app imports a single `Respond`:
16
46
  *
17
47
  * ```ts
@@ -24,7 +54,7 @@ var ThrowableRedirectError = class extends Data.TaggedError("ThrowableRedirectEr
24
54
  * ```
25
55
  */
26
56
  const Respond = {
27
- /** Recover: short-circuit and hand `value` to the component as loader/action data. */
57
+ /** Recover: short-circuit and pass `value` to the component as loader/action data. */
28
58
  early: (value, init) => new ReturnableDataError({
29
59
  data: value,
30
60
  init
@@ -42,11 +72,10 @@ const Respond = {
42
72
  };
43
73
  /** Narrows an unknown value to one of the library's route errors. */
44
74
  const isRouteError = (value) => value instanceof ReturnableDataError || value instanceof ThrowableDataError || value instanceof ThrowableRedirectError;
45
- //#endregion
46
- //#region src/factory.ts
75
+ // src/factory.ts
47
76
  const internalServerError = () => Effect.fail(new Response("Internal Server Error", { status: 500 }));
48
77
  /**
49
- * A raw `FailureResponse` — a downstream `next()`'s rejection, in `makeMiddleware` —
78
+ * A raw `FailureResponse` - a downstream `next()`'s rejection, in `makeMiddleware` -
50
79
  * is already a finished response; re-throwing it as-is (rather than 500ing it) lets
51
80
  * a nested loader's redirect/thrown response pass back up through the middleware chain.
52
81
  */
@@ -58,13 +87,27 @@ const processRouteError = (e) => {
58
87
  return Effect.fail(redirect(e.url, e.init));
59
88
  };
60
89
  /**
90
+ * The request store of a factory that configures no `requestContext`. It holds
91
+ * whatever the server adapter and effect middleware put there, and starts empty.
92
+ */
93
+ const defaultRequestContext = createContext(Context.empty());
94
+ /**
95
+ * The entries of `current` that `started` lacks or holds with another value:
96
+ * what a fiber that began with `started` has provided since.
97
+ */
98
+ const providedSince = (started, current) => {
99
+ const provided = /* @__PURE__ */ new Map();
100
+ for (const [key, value] of current.mapUnsafe) if (started.mapUnsafe.get(key) !== value) provided.set(key, value);
101
+ return Context.makeUnsafe(provided);
102
+ };
103
+ /**
61
104
  * Build `makeLoader` / `makeAction` / `makeMiddleware` for an application, wired to
62
105
  * its domain errors.
63
106
  *
64
107
  * Declare the app's **domain errors** as the type argument, then register an
65
108
  * *optional* handler per domain error in `errorHandlers`. The handler keys
66
109
  * autocomplete to your domain-error tags and each `error` parameter is typed from
67
- * its key — no annotation needed. It's curried so you can pin the domain errors
110
+ * its key - no annotation needed. It's curried so you can pin the domain errors
68
111
  * while the handler return types are still inferred:
69
112
  *
70
113
  * ```ts
@@ -80,10 +123,10 @@ const processRouteError = (e) => {
80
123
  * }));
81
124
  * ```
82
125
  *
83
- * The inner config is a **builder** — it receives the library's `Respond` helpers so
126
+ * The inner config is a **builder** - it receives the library's `Respond` helpers so
84
127
  * your handlers can recover/throw with them, and returns the config. `Respond` is
85
128
  * also returned from the factory (extended with any `respond` helpers you add), so
86
- * your app imports a *single* `Respond` — no auto-import ambiguity with a library one.
129
+ * your app imports a *single* `Respond` - no auto-import ambiguity with a library one.
87
130
  *
88
131
  * A handler *remaps* the error by returning either a library route error
89
132
  * (`Respond.early` to recover; `Respond.throw` / `Respond.redirect` to throw) or an
@@ -92,13 +135,13 @@ const processRouteError = (e) => {
92
135
  *
93
136
  * **Declared domain errors** may be left unhandled: if one implements
94
137
  * `HttpServerRespondable` it's rendered automatically, otherwise it falls through
95
- * to a 500. **Any other error** — a service-specific error the route consumes that
96
- * isn't a declared domain error — *must* be handled in the loader/action, or
138
+ * to a 500. **Any other error** - a service-specific error the route consumes that
139
+ * isn't a declared domain error - *must* be handled in the loader/action, or
97
140
  * `makeLoader`/`makeAction` fails to type-check.
98
141
  *
99
142
  * Pass a `runtime` (a `ManagedRuntime`, e.g. from `ManagedRuntime.make(AppLayer)`)
100
143
  * to provide your app's services once. Loader/action effects may then require those
101
- * services directly — no per-call `Effect.provide`:
144
+ * services directly - no per-call `Effect.provide`:
102
145
  *
103
146
  * ```ts
104
147
  * const { makeLoader, makeAction } = makeEffectRouteFactory<DomainErrors>()(() => ({
@@ -120,9 +163,9 @@ const processRouteError = (e) => {
120
163
  * generated `Route.MiddlewareFunction` (a type argument, since middleware has no
121
164
  * separate args-only type) to type `args` and `next`'s result from that route; it
122
165
  * defaults to React Router's own `MiddlewareFunction`. The `next` handed to the
123
- * effect is `() => Effect.Effect<Result, FailureResponse>` — call it with `yield*`
166
+ * effect is `() => Effect.Effect<Result, FailureResponse>` - call it with `yield*`
124
167
  * to run the rest of the chain, and a downstream throw (e.g. a nested loader's
125
- * redirect) surfaces as a typed failure instead of a rejected promise:
168
+ * redirect) becomes a typed failure instead of a rejected promise:
126
169
  *
127
170
  * ```ts
128
171
  * const middleware = makeMiddleware<Route.MiddlewareFunction>((args, next) =>
@@ -138,7 +181,7 @@ function makeEffectRouteFactory() {
138
181
  return function defineErrorHandlers(builder) {
139
182
  const config = builder(Respond);
140
183
  const runtime = config.runtime;
141
- const requestContextKey = config.requestContext;
184
+ const storeKey = config.requestContext ?? defaultRequestContext;
142
185
  const Respond$1 = {
143
186
  ...config.respond,
144
187
  ...Respond
@@ -150,7 +193,7 @@ function makeEffectRouteFactory() {
150
193
  * (registered domain errors, library route errors, respondables, or the 500
151
194
  * default), provides the per-request context, and executes against the
152
195
  * configured runtime. Shared by `makeLoader`/`makeAction` and `makeMiddleware`
153
- * — they differ only in how they build the effect and what `context` they read.
196
+ * - they differ only in how they build the effect and what `context` they read.
154
197
  */
155
198
  function runProgram(effect, context, signal) {
156
199
  const program = effect.pipe(Effect.catchIf((_e) => true, (e) => {
@@ -163,7 +206,8 @@ function makeEffectRouteFactory() {
163
206
  if (isFailureResponse(e)) return Effect.fail(e);
164
207
  return internalServerError();
165
208
  }));
166
- const provided = requestContextKey ? Effect.provideContext(program, context.get(requestContextKey)) : program;
209
+ const store = readRequestStore(context);
210
+ const provided = store === void 0 ? program : Effect.provideContext(program, store);
167
211
  return runtime ? runtime.runPromise(provided, { signal }) : Effect.runPromise(provided, { signal });
168
212
  }
169
213
  function makeLoaderOrAction(fn) {
@@ -176,13 +220,13 @@ function makeEffectRouteFactory() {
176
220
  * args-only type) to type `args`, `next` and the result from that route; it defaults
177
221
  * to React Router's own `MiddlewareFunction`.
178
222
  *
179
- * The errors the library deals with for a loader/action it deals with here too —
180
- * declared domain errors, library route errors and respondables — plus the
223
+ * The errors the library deals with for a loader/action it deals with here too -
224
+ * declared domain errors, library route errors and respondables - plus the
181
225
  * `FailureResponse` `next()` fails with. Anything else must be handled in the effect,
182
226
  * as must any service neither the `runtime` nor the `requestContext` provides.
183
227
  *
184
- * The wrapper is typed as the route's own middleware — `(args, next) =>
185
- * Promise<Result | undefined>` — so it drops straight into a
228
+ * The wrapper is typed as the route's own middleware - `(args, next) =>
229
+ * Promise<Result | undefined>` - so it drops straight into a
186
230
  * `Route.MiddlewareFunction[]`. A handler that recovers (`Respond.early`) resolves
187
231
  * with `data(...)`, which react-router's server middleware pipeline turns into the
188
232
  * short-circuit `Response`.
@@ -190,21 +234,65 @@ function makeEffectRouteFactory() {
190
234
  function makeMiddleware(fn) {
191
235
  const run = fn;
192
236
  return (args, next) => {
193
- const effectNext = () => Effect.tryPromise({
194
- try: next,
195
- catch: (e) => e
196
- });
197
237
  const { context, request } = args;
238
+ const effectNext = makeEffectNext(context, next);
198
239
  return runProgram(run(args, effectNext), context, request.signal);
199
240
  };
200
241
  }
242
+ /**
243
+ * Reads the request's effect context from the router context. A test that
244
+ * passes args without a router context gets none, as before.
245
+ */
246
+ function readRequestStore(context) {
247
+ return typeof context?.get === "function" ? context.get(storeKey) : void 0;
248
+ }
249
+ /**
250
+ * Wraps React Router's `next` as an effect. Before it runs the rest of the
251
+ * chain, it adds to the request's effect context what the calling fiber
252
+ * provides beyond the context the middleware started with: services it
253
+ * provides around `next()`, log annotations, and its current span. Updates
254
+ * the middleware made with `context.set` stay. A downstream rejection is
255
+ * already a finished response, so it fails with that response as-is.
256
+ */
257
+ function makeEffectNext(context, next) {
258
+ const started = readRequestStore(context);
259
+ return () => Effect.withFiber((fiber) => {
260
+ if (started !== void 0) context.set(storeKey, Context.merge(context.get(storeKey), providedSince(started, fiber.context)));
261
+ return Effect.tryPromise({
262
+ try: next,
263
+ catch: (e) => e
264
+ });
265
+ });
266
+ }
267
+ /** Runs a loader or action's result: an Effect through the error model, anything else as-is. */
268
+ function runResult(result, args) {
269
+ return Effect.isEffect(result) ? runProgram(result, args.context, args.request.signal) : result;
270
+ }
271
+ const actionMeta = config.actionMeta;
201
272
  return {
202
273
  makeLoader: makeLoaderOrAction,
203
274
  makeAction: makeLoaderOrAction,
204
275
  makeMiddleware,
205
- Respond: Respond$1
276
+ Respond: Respond$1,
277
+ /** The route runner the server adapter applies to every route module. */
278
+ 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
+ },
284
+ middleware: (fn) => async (args, next) => {
285
+ const routeArgs = args;
286
+ return runResult(fn({
287
+ ...args,
288
+ next: makeEffectNext(routeArgs.context, next)
289
+ }, next), routeArgs);
290
+ },
291
+ requestContext: storeKey
292
+ },
293
+ /** Type-only: what the adapter's route check and `Register` read. */
294
+ "~types": void 0
206
295
  };
207
296
  };
208
297
  }
209
- //#endregion
210
- export { ReturnableDataError, ThrowableDataError, ThrowableRedirectError, isRouteError, makeEffectRouteFactory };
298
+ export { ReturnableDataError, ThrowableDataError, ThrowableRedirectError, actionMetaKey, isRouteError, makeEffectRouteFactory };
@@ -0,0 +1,12 @@
1
+ // \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ export { __exportAll as t };