react-router-effect 1.0.0 → 1.0.1
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 +6 -4
- package/dist/index.mjs +19 -1
- package/package.json +3 -3
- package/src/factory.ts +45 -14
package/dist/index.d.mts
CHANGED
|
@@ -101,8 +101,10 @@ type MiddlewareNextOf<Middleware extends AnyMiddlewareFunction> = Parameters<Mid
|
|
|
101
101
|
/**
|
|
102
102
|
* The `Result` a `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) resolves
|
|
103
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.
|
|
104
106
|
*/
|
|
105
|
-
type MiddlewareResultOf<Middleware extends AnyMiddlewareFunction> = MiddlewareNextOf<Middleware> extends (() =>
|
|
107
|
+
type MiddlewareResultOf<Middleware extends AnyMiddlewareFunction> = MiddlewareNextOf<Middleware> extends ((...args: never[]) => infer Result) ? Awaited<Result> : never;
|
|
106
108
|
/**
|
|
107
109
|
* The inferred map of handler RETURN types, keyed by domain-error tag. Making the
|
|
108
110
|
* factory generic over *this* (rather than the whole handler map) is what lets
|
|
@@ -183,10 +185,10 @@ type NameError<U> = U extends {
|
|
|
183
185
|
*/
|
|
184
186
|
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
185
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 : {
|
|
186
|
-
"react-router-effect": "This loader/action requires a service the factory does not provide. Add it to your runtime or requestContext, or handle it in the effect.";
|
|
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.";
|
|
187
189
|
missingRequirements: Exclude<R, Provided>;
|
|
188
190
|
} : {
|
|
189
|
-
"react-router-effect": "This loader/action 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.";
|
|
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.";
|
|
190
192
|
unhandledErrors: NameError<Unhandled<E, DomainError, AlsoHandled>>;
|
|
191
193
|
};
|
|
192
194
|
/**
|
|
@@ -329,7 +331,7 @@ declare function makeEffectRouteFactory<DomainError extends Tagged = never>(): <
|
|
|
329
331
|
}) => {
|
|
330
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>>;
|
|
331
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>>;
|
|
332
|
-
makeMiddleware: <Middleware extends
|
|
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>;
|
|
333
335
|
Respond: {
|
|
334
336
|
early: <D>(value: D, init?: number | ResponseInit) => ReturnableDataError<D>;
|
|
335
337
|
throw: <D>(value: D, init?: number | ResponseInit) => ThrowableDataError<D>;
|
package/dist/index.mjs
CHANGED
|
@@ -170,6 +170,23 @@ function makeEffectRouteFactory() {
|
|
|
170
170
|
const run = fn;
|
|
171
171
|
return (args) => runProgram(run(args), args.context);
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Wraps a middleware function written as an effect. Pin it to the route's generated
|
|
175
|
+
* `Route.MiddlewareFunction` (a type argument, since middleware has no separate
|
|
176
|
+
* args-only type) to type `args`, `next` and the result from that route; it defaults
|
|
177
|
+
* to React Router's own `MiddlewareFunction`.
|
|
178
|
+
*
|
|
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
|
|
181
|
+
* `FailureResponse` `next()` fails with. Anything else must be handled in the effect,
|
|
182
|
+
* as must any service neither the `runtime` nor the `requestContext` provides.
|
|
183
|
+
*
|
|
184
|
+
* The wrapper is typed as the route's own middleware — `(args, next) =>
|
|
185
|
+
* Promise<Result | undefined>` — so it drops straight into a
|
|
186
|
+
* `Route.MiddlewareFunction[]`. A handler that recovers (`Respond.early`) resolves
|
|
187
|
+
* with `data(...)`, which react-router's server middleware pipeline turns into the
|
|
188
|
+
* short-circuit `Response`.
|
|
189
|
+
*/
|
|
173
190
|
function makeMiddleware(fn) {
|
|
174
191
|
const run = fn;
|
|
175
192
|
return (args, next) => {
|
|
@@ -177,7 +194,8 @@ function makeEffectRouteFactory() {
|
|
|
177
194
|
try: next,
|
|
178
195
|
catch: (e) => e
|
|
179
196
|
});
|
|
180
|
-
|
|
197
|
+
const { context } = args;
|
|
198
|
+
return runProgram(run(args, effectNext), context);
|
|
181
199
|
};
|
|
182
200
|
}
|
|
183
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.
|
|
4
|
+
"version": "1.0.1",
|
|
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",
|
|
@@ -44,14 +44,14 @@
|
|
|
44
44
|
"@vitejs/plugin-react": "^6.0.4",
|
|
45
45
|
"@vitest/browser": "4.1.10",
|
|
46
46
|
"@vitest/browser-playwright": "4.1.10",
|
|
47
|
-
"effect": "4.0.0-beta.
|
|
47
|
+
"effect": "4.0.0-beta.102",
|
|
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
53
|
"vite-plus": "latest",
|
|
54
|
-
"vitest": "4.1.
|
|
54
|
+
"vitest": "4.1.10",
|
|
55
55
|
"vitest-browser-react": "^2.2.0"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
package/src/factory.ts
CHANGED
|
@@ -81,9 +81,11 @@ type MiddlewareNextOf<Middleware extends AnyMiddlewareFunction> = Parameters<Mid
|
|
|
81
81
|
/**
|
|
82
82
|
* The `Result` a `Route.MiddlewareFunction` (or the base `MiddlewareFunction`) resolves
|
|
83
83
|
* with — i.e. what its `next()` resolves to, and what the middleware itself may return.
|
|
84
|
+
* Read through `Awaited` because react-router declares `next` as an *interface* with a
|
|
85
|
+
* call signature (`MiddlewareNextFunction`), and a route's own may resolve synchronously.
|
|
84
86
|
*/
|
|
85
87
|
type MiddlewareResultOf<Middleware extends AnyMiddlewareFunction> =
|
|
86
|
-
MiddlewareNextOf<Middleware> extends () =>
|
|
88
|
+
MiddlewareNextOf<Middleware> extends (...args: never[]) => infer Result ? Awaited<Result> : never;
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
91
|
* The inferred map of handler RETURN types, keyed by domain-error tag. Making the
|
|
@@ -195,11 +197,11 @@ type Diagnose<E, R, DomainError, Provided, AlsoHandled = never> =
|
|
|
195
197
|
? [Exclude<R, Provided>] extends [never]
|
|
196
198
|
? unknown
|
|
197
199
|
: {
|
|
198
|
-
"react-router-effect": "This loader/action requires a service the factory does not provide. Add it to your runtime or requestContext, or handle it in the effect.";
|
|
200
|
+
"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.";
|
|
199
201
|
missingRequirements: Exclude<R, Provided>;
|
|
200
202
|
}
|
|
201
203
|
: {
|
|
202
|
-
"react-router-effect": "This loader/action 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.";
|
|
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.";
|
|
203
205
|
unhandledErrors: NameError<Unhandled<E, DomainError, AlsoHandled>>;
|
|
204
206
|
};
|
|
205
207
|
|
|
@@ -489,35 +491,64 @@ export function makeEffectRouteFactory<DomainError extends Tagged = never>() {
|
|
|
489
491
|
>;
|
|
490
492
|
}
|
|
491
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Wraps a middleware function written as an effect. Pin it to the route's generated
|
|
496
|
+
* `Route.MiddlewareFunction` (a type argument, since middleware has no separate
|
|
497
|
+
* args-only type) to type `args`, `next` and the result from that route; it defaults
|
|
498
|
+
* to React Router's own `MiddlewareFunction`.
|
|
499
|
+
*
|
|
500
|
+
* The errors the library deals with for a loader/action it deals with here too —
|
|
501
|
+
* declared domain errors, library route errors and respondables — plus the
|
|
502
|
+
* `FailureResponse` `next()` fails with. Anything else must be handled in the effect,
|
|
503
|
+
* as must any service neither the `runtime` nor the `requestContext` provides.
|
|
504
|
+
*
|
|
505
|
+
* The wrapper is typed as the route's own middleware — `(args, next) =>
|
|
506
|
+
* Promise<Result | undefined>` — so it drops straight into a
|
|
507
|
+
* `Route.MiddlewareFunction[]`. A handler that recovers (`Respond.early`) resolves
|
|
508
|
+
* with `data(...)`, which react-router's server middleware pipeline turns into the
|
|
509
|
+
* short-circuit `Response`.
|
|
510
|
+
*/
|
|
492
511
|
function makeMiddleware<
|
|
493
|
-
Middleware extends
|
|
494
|
-
A
|
|
495
|
-
|
|
496
|
-
|
|
512
|
+
Middleware extends AnyMiddlewareFunction = MiddlewareFunction,
|
|
513
|
+
// A pinned call passes an explicit type argument, which makes the whole call
|
|
514
|
+
// explicit — TypeScript has no partial inference — so these defaults stand in
|
|
515
|
+
// for what would otherwise be inferred: exactly the errors the library handles
|
|
516
|
+
// and the services the factory provides.
|
|
517
|
+
E = LibraryHandled<DomainError, FailureResponse>,
|
|
518
|
+
R = RServices | ReqServices,
|
|
497
519
|
>(
|
|
498
520
|
// Same shape as `makeLoaderOrAction`'s `fn`, but middleware also receives
|
|
499
521
|
// `next` — wrapped as an effect (rather than react-router's raw
|
|
500
522
|
// `() => Promise<Result>`) so downstream failures (e.g. a nested loader's
|
|
501
523
|
// redirect) surface as a typed `FailureResponse`, consistent with the rest
|
|
502
|
-
// of the library's error model.
|
|
524
|
+
// of the library's error model. The success type is fixed to the route's own
|
|
525
|
+
// `Result` (plus `void`), which is what react-router accepts back.
|
|
503
526
|
fn: (
|
|
504
527
|
args: MiddlewareArgsOf<Middleware>,
|
|
505
528
|
next: () => Effect.Effect<MiddlewareResultOf<Middleware>, FailureResponse>,
|
|
506
|
-
) => Effect.Effect<
|
|
529
|
+
) => Effect.Effect<MiddlewareResultOf<Middleware> | void, E, R> &
|
|
507
530
|
Diagnose<E, R, DomainError, RServices | ReqServices, FailureResponse>,
|
|
508
531
|
): (
|
|
509
532
|
args: MiddlewareArgsOf<Middleware>,
|
|
510
533
|
next: MiddlewareNextOf<Middleware>,
|
|
511
|
-
) => Promise<
|
|
534
|
+
) => Promise<MiddlewareResultOf<Middleware> | undefined> {
|
|
535
|
+
// Inside the body the route's own arg/result types are opaque (they're
|
|
536
|
+
// `Parameters<Middleware>[...]` of an unresolved type parameter), so dispatch
|
|
537
|
+
// through the loose shape — as `makeLoaderOrAction` does with its diagnostic.
|
|
538
|
+
// Sound at runtime: the values produced are exactly the declared types.
|
|
512
539
|
const run = fn as unknown as (
|
|
513
540
|
args: MiddlewareArgsOf<Middleware>,
|
|
514
|
-
next: () => Effect.Effect<
|
|
541
|
+
next: () => Effect.Effect<unknown, FailureResponse>,
|
|
515
542
|
) => Effect.Effect<unknown, E, RServices | ReqServices>;
|
|
516
543
|
return (args, next) => {
|
|
517
544
|
const effectNext = () =>
|
|
518
|
-
Effect.tryPromise({
|
|
519
|
-
|
|
520
|
-
|
|
545
|
+
Effect.tryPromise({
|
|
546
|
+
try: next as unknown as () => Promise<unknown>,
|
|
547
|
+
catch: (e) => e as FailureResponse,
|
|
548
|
+
});
|
|
549
|
+
const { context } = args as unknown as { context: Readonly<RouterContextProvider> };
|
|
550
|
+
return runProgram(run(args, effectNext), context) as Promise<
|
|
551
|
+
MiddlewareResultOf<Middleware> | undefined
|
|
521
552
|
>;
|
|
522
553
|
};
|
|
523
554
|
}
|