react-router-effect 1.0.2 → 2.0.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/README.md CHANGED
@@ -27,7 +27,7 @@ const loader = makeLoader((args: Route.LoaderArgs) =>
27
27
  vp add react-router-effect effect react-router
28
28
  ```
29
29
 
30
- `effect` and `react-router` are peer dependencies.
30
+ `effect` and `react-router` are peer dependencies. The library requires Effect 4 (`effect@^4.0.0`).
31
31
 
32
32
  ## Concepts
33
33
 
@@ -166,7 +166,7 @@ If a domain error implements `HttpServerRespondable`, you don't need to register
166
166
  it renders its own response:
167
167
 
168
168
  ```ts
169
- import { HttpServerRespondable, HttpServerResponse } from "effect/unstable/http";
169
+ import { HttpServerRespondable, HttpServerResponse } from "effect/http";
170
170
 
171
171
  class NotAuthorizedError extends Data.TaggedError("NotAuthorizedError")<{}> {
172
172
  [HttpServerRespondable.symbol]() {
package/dist/index.d.mts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { Context, Effect, ManagedRuntime } from "effect";
2
- import { HttpServerRespondable } from "effect/unstable/http";
2
+ import { HttpServerRespondable } from "effect/http";
3
3
  import { ActionFunctionArgs, LoaderFunctionArgs, MiddlewareFunction, RouterContext, UNSAFE_DataWithResponseInit } from "react-router";
4
4
  //#region src/errors.d.ts
5
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
6
  readonly _tag: "ReturnableRouteError";
7
7
  } & Readonly<A>;
8
8
  /** Recoverable: short-circuits the loader but returns `data(...)` the component reads. */
9
- declare class ReturnableDataError<D> extends ReturnableDataError_base<{
9
+ export declare class ReturnableDataError<D> extends ReturnableDataError_base<{
10
10
  data: D;
11
11
  init?: number | ResponseInit;
12
12
  }> {}
@@ -14,7 +14,7 @@ declare const ThrowableDataError_base: new <A extends Record<string, any> = {}>(
14
14
  readonly _tag: "ThrowableRouteError";
15
15
  } & Readonly<A>;
16
16
  /** Throwable: rejects with `data(...)` → boundary as an `ErrorResponse`. */
17
- declare class ThrowableDataError<D> extends ThrowableDataError_base<{
17
+ export declare class ThrowableDataError<D> extends ThrowableDataError_base<{
18
18
  data: D;
19
19
  init?: number | ResponseInit;
20
20
  }> {}
@@ -22,7 +22,7 @@ declare const ThrowableRedirectError_base: new <A extends Record<string, any> =
22
22
  readonly _tag: "ThrowableRedirectError";
23
23
  } & Readonly<A>;
24
24
  /** Throwable: rejects with a redirect `Response`. */
25
- declare class ThrowableRedirectError extends ThrowableRedirectError_base<{
25
+ export declare class ThrowableRedirectError extends ThrowableRedirectError_base<{
26
26
  url: string;
27
27
  init?: number | ResponseInit;
28
28
  }> {}
@@ -52,7 +52,7 @@ declare const Respond: {
52
52
  redirect: (url: string, init?: number | ResponseInit) => ThrowableRedirectError;
53
53
  };
54
54
  /** Narrows an unknown value to one of the library's route errors. */
55
- declare const isRouteError: (value: unknown) => value is AnyRouteError;
55
+ export declare const isRouteError: (value: unknown) => value is AnyRouteError;
56
56
  //#endregion
57
57
  //#region src/factory.d.ts
58
58
  /** The library's base `Respond` helpers (`early` / `throw` / `redirect`). */
@@ -296,7 +296,7 @@ type RequestContextKey<ReqServices> = RouterContext<Context.Context<ReqServices>
296
296
  * );
297
297
  * ```
298
298
  */
299
- 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) => {
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
300
  /**
301
301
  * An optional handler per declared domain error, keyed by its tag. The keys
302
302
  * autocomplete to your domain-error tags and each handler's `error` parameter
@@ -339,4 +339,4 @@ declare function makeEffectRouteFactory<DomainError extends Tagged = never>(): <
339
339
  } & ExtraRespond;
340
340
  };
341
341
  //#endregion
342
- export { type AnyRouteError, type ErrorHandler, type RequestContextKey, ReturnableDataError, ThrowableDataError, ThrowableRedirectError, isRouteError, makeEffectRouteFactory };
342
+ export type { AnyRouteError, ErrorHandler, RequestContextKey };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Data, Effect } from "effect";
2
- import { HttpServerRespondable, HttpServerResponse } from "effect/unstable/http";
2
+ import { HttpServerRespondable, HttpServerResponse } from "effect/http";
3
3
  import { data, redirect } from "react-router";
4
4
  //#region src/errors.ts
5
5
  /** Recoverable: short-circuits the loader but returns `data(...)` the component reads. */
@@ -152,7 +152,7 @@ function makeEffectRouteFactory() {
152
152
  * configured runtime. Shared by `makeLoader`/`makeAction` and `makeMiddleware`
153
153
  * — they differ only in how they build the effect and what `context` they read.
154
154
  */
155
- function runProgram(effect, context) {
155
+ function runProgram(effect, context, signal) {
156
156
  const program = effect.pipe(Effect.catchIf((_e) => true, (e) => {
157
157
  if (isUserError(e)) {
158
158
  const out = userHandlers[e._tag](e);
@@ -164,11 +164,11 @@ function makeEffectRouteFactory() {
164
164
  return internalServerError();
165
165
  }));
166
166
  const provided = requestContextKey ? Effect.provideContext(program, context.get(requestContextKey)) : program;
167
- return runtime ? runtime.runPromise(provided) : Effect.runPromise(provided);
167
+ return runtime ? runtime.runPromise(provided, { signal }) : Effect.runPromise(provided, { signal });
168
168
  }
169
169
  function makeLoaderOrAction(fn) {
170
170
  const run = fn;
171
- return (args) => runProgram(run(args), args.context);
171
+ return (args) => runProgram(run(args), args.context, args.request.signal);
172
172
  }
173
173
  /**
174
174
  * Wraps a middleware function written as an effect. Pin it to the route's generated
@@ -194,8 +194,8 @@ function makeEffectRouteFactory() {
194
194
  try: next,
195
195
  catch: (e) => e
196
196
  });
197
- const { context } = args;
198
- return runProgram(run(args, effectNext), context);
197
+ const { context, request } = args;
198
+ return runProgram(run(args, effectNext), context, request.signal);
199
199
  };
200
200
  }
201
201
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://schemastore.org/package.json",
3
3
  "name": "react-router-effect",
4
- "version": "1.0.2",
4
+ "version": "2.0.0",
5
5
  "description": "Wrap React Router framework-mode route modules with Effect, with typed recover/throw/redirect error handling.",
6
6
  "keywords": [
7
7
  "action",
@@ -36,26 +36,27 @@
36
36
  "access": "public"
37
37
  },
38
38
  "devDependencies": {
39
- "@changesets/changelog-github": "^0.7.0",
40
- "@changesets/cli": "^2.31.1",
39
+ "@changesets/changelog-github": "^1.0.1",
40
+ "@changesets/cli": "^3.0.3",
41
41
  "@types/node": "^25.6.2",
42
42
  "@types/react": "^19.2.17",
43
43
  "@types/react-dom": "^19.2.3",
44
44
  "@vitejs/plugin-react": "^6.0.4",
45
- "@vitest/browser": "4.1.10",
46
- "@vitest/browser-playwright": "4.1.10",
47
- "effect": "4.0.0-beta.107",
45
+ "@vitest/browser": "5.0.4",
46
+ "@vitest/browser-playwright": "5.0.4",
47
+ "effect": "4.0.5",
48
48
  "playwright": "1.60.0",
49
49
  "react": "^19.2.8",
50
50
  "react-dom": "^19.2.8",
51
51
  "react-router": "^8.3.0",
52
52
  "typescript": "^7.0.2",
53
- "vite-plus": "latest",
54
- "vitest": "4.1.10",
53
+ "vite": "npm:@voidzero-dev/vite-plus-core@latest",
54
+ "vite-plus": "^1.3.0",
55
+ "vitest": "5.0.4",
55
56
  "vitest-browser-react": "^2.2.0"
56
57
  },
57
58
  "peerDependencies": {
58
- "effect": "^4.0.0-beta.107",
59
+ "effect": "^4.0.0",
59
60
  "react-router": "^7.16.0 || ^8.3.0"
60
61
  },
61
62
  "devEngines": {
package/src/factory.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { type Context, Effect, type ManagedRuntime } from "effect";
2
- import { HttpServerRespondable, HttpServerResponse } from "effect/unstable/http";
2
+ import { HttpServerRespondable, HttpServerResponse } from "effect/http";
3
3
  import {
4
4
  data,
5
5
  redirect,
@@ -420,6 +420,7 @@ export function makeEffectRouteFactory<DomainError extends Tagged = never>() {
420
420
  function runProgram<E>(
421
421
  effect: Effect.Effect<unknown, E, RServices | ReqServices>,
422
422
  context: Readonly<RouterContextProvider>,
423
+ signal: AbortSignal,
423
424
  ): Promise<unknown> {
424
425
  // The internal channel is deliberately loose (`unknown` success); the caller
425
426
  // restores the precise resolved type (computed from `E` and the handler map).
@@ -464,9 +465,12 @@ export function makeEffectRouteFactory<DomainError extends Tagged = never>() {
464
465
  ) as Effect.Effect<unknown, FailureResponse, RServices>;
465
466
  // Run against the configured runtime so its services satisfy the effect's
466
467
  // `R`; with no runtime, the effect requires nothing and runs standalone.
468
+ // The request's abort signal interrupts the fiber, so finalizers run and
469
+ // in-flight work is cancelled. React Router races the handler against the same
470
+ // signal and discards its result, so the interrupt rejection is never rendered.
467
471
  return runtime
468
- ? runtime.runPromise(provided)
469
- : Effect.runPromise(provided as Effect.Effect<unknown, FailureResponse>);
472
+ ? runtime.runPromise(provided, { signal })
473
+ : Effect.runPromise(provided as Effect.Effect<unknown, FailureResponse>, { signal });
470
474
  }
471
475
 
472
476
  function makeLoaderOrAction<Args extends LoaderFunctionArgs | ActionFunctionArgs, A, E, R>(
@@ -486,7 +490,7 @@ export function makeEffectRouteFactory<DomainError extends Tagged = never>() {
486
490
  args: Args,
487
491
  ) => Effect.Effect<unknown, E, RServices | ReqServices>;
488
492
  return (args: Args) =>
489
- runProgram(run(args), args.context) as Promise<
493
+ runProgram(run(args), args.context, args.request.signal) as Promise<
490
494
  A | DirectRecover<E> | RecoverOf<DomainError, Returns, E>
491
495
  >;
492
496
  }
@@ -546,8 +550,11 @@ export function makeEffectRouteFactory<DomainError extends Tagged = never>() {
546
550
  try: next as unknown as () => Promise<unknown>,
547
551
  catch: (e) => e as FailureResponse,
548
552
  });
549
- const { context } = args as unknown as { context: Readonly<RouterContextProvider> };
550
- return runProgram(run(args, effectNext), context) as Promise<
553
+ const { context, request } = args as unknown as {
554
+ context: Readonly<RouterContextProvider>;
555
+ request: Request;
556
+ };
557
+ return runProgram(run(args, effectNext), context, request.signal) as Promise<
551
558
  MiddlewareResultOf<Middleware> | undefined
552
559
  >;
553
560
  };