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 +2 -2
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +6 -6
- package/package.json +10 -9
- package/src/factory.ts +13 -6
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/
|
|
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/
|
|
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 {
|
|
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/
|
|
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": "
|
|
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.
|
|
40
|
-
"@changesets/cli": "^
|
|
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": "
|
|
46
|
-
"@vitest/browser-playwright": "
|
|
47
|
-
"effect": "4.0.
|
|
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
|
|
54
|
-
"
|
|
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
|
|
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/
|
|
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 {
|
|
550
|
-
|
|
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
|
};
|