@typed/router 1.0.0-beta.1 → 1.0.0-beta.3
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/Matcher.d.ts +18 -13
- package/dist/Matcher.d.ts.map +1 -1
- package/dist/Matcher.js +102 -97
- package/dist/MatcherV2.d.ts +3 -0
- package/dist/MatcherV2.d.ts.map +1 -0
- package/dist/MatcherV2.js +1 -0
- package/dist/Route.d.ts +5 -0
- package/dist/Route.d.ts.map +1 -1
- package/dist/test-utils/matcherBrowserHarness.d.ts +10 -0
- package/dist/test-utils/matcherBrowserHarness.d.ts.map +1 -0
- package/dist/test-utils/matcherBrowserHarness.js +13 -0
- package/package.json +21 -18
- package/src/Matcher.browser.test.ts +771 -0
- package/src/Matcher.test.ts +344 -67
- package/src/Matcher.ts +165 -132
- package/src/Route.ts +6 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--back---restores-previous-match-1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--catchTag-RouteNotFound-can-navigate-and-re-match--browser-history--1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--decodes-query-params-from-pathname-search-1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--function-Matcher-catch--and-composition--browser--instance-catchTag-does-not-catch-RouteGuardError-from-guards-1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--history-back-restores-previous-match--popstate-sync--1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--path-and-query-params-both-decode--distinct-names--1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--prefix-scopes-routes-under-a-path-segment-1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--provideService-supplies-a-service-to-handlers-1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--query-param-wins-over-path-param-when-names-collide-1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--redirectTo-navigates-away-from-unmatched-path--side-effect--1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--redirectTo-navigates-then-matches-target-route-1.png +0 -0
- package/src/__screenshots__/Matcher.browser.test.ts/typed-router-Matcher--browser--reuses-shared-layers-and-layouts-across-route-changes-1.png +0 -0
- package/src/test-utils/matcherBrowserHarness.ts +22 -0
package/src/Matcher.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type * as Arr from "effect/Array";
|
|
|
3
3
|
import * as Cause from "effect/Cause";
|
|
4
4
|
import * as Effect from "effect/Effect";
|
|
5
5
|
import * as Exit from "effect/Exit";
|
|
6
|
-
import { interrupt
|
|
6
|
+
import { interrupt } from "effect/Exit";
|
|
7
7
|
import { dual, identity } from "effect/Function";
|
|
8
8
|
import * as Result from "effect/Result";
|
|
9
9
|
import * as Layer from "effect/Layer";
|
|
@@ -25,7 +25,7 @@ import { fromEffect, never } from "@typed/fx/Fx/constructors/fromEffect";
|
|
|
25
25
|
import { succeed } from "@typed/fx/Fx/constructors/succeed";
|
|
26
26
|
import type * as Fx from "@typed/fx/Fx/Fx";
|
|
27
27
|
import { fromStream } from "@typed/fx/Fx/stream";
|
|
28
|
-
import { isFx } from "@typed/fx/Fx/TypeId";
|
|
28
|
+
import { FxTypeId, isFx } from "@typed/fx/Fx/TypeId";
|
|
29
29
|
import { RefSubject } from "@typed/fx/RefSubject";
|
|
30
30
|
import { CurrentPath, Navigation } from "@typed/navigation/Navigation";
|
|
31
31
|
import type { MatchAst, RouteAst } from "./AST.js";
|
|
@@ -33,6 +33,7 @@ import * as AST from "./AST.js";
|
|
|
33
33
|
import { CurrentRoute } from "./CurrentRoute.js";
|
|
34
34
|
import { Join, make as makeRoute, type Route } from "./Route.js";
|
|
35
35
|
import type { Router } from "./Router.js";
|
|
36
|
+
import { Sink } from "@typed/fx";
|
|
36
37
|
|
|
37
38
|
export type Layout<Params, A, E, R, B, E2, R2> = (
|
|
38
39
|
params: LayoutParams<Params, A, E, R>,
|
|
@@ -57,21 +58,21 @@ export type AnyLayer =
|
|
|
57
58
|
| Layer.Layer<never, any, never>
|
|
58
59
|
| Layer.Layer<never, never, any>;
|
|
59
60
|
|
|
60
|
-
type AnyServiceMap = ServiceMap.ServiceMap<any> | ServiceMap.ServiceMap<never>;
|
|
61
|
-
type AnyDependency = AnyLayer | AnyServiceMap;
|
|
61
|
+
export type AnyServiceMap = ServiceMap.ServiceMap<any> | ServiceMap.ServiceMap<never>;
|
|
62
|
+
export type AnyDependency = AnyLayer | AnyServiceMap;
|
|
62
63
|
type AnyLayout = Layout<any, any, any, any, any, any, any>;
|
|
63
64
|
type AnyCatch = CatchHandler<any, any, any, any>;
|
|
64
65
|
type AnyGuard = GuardType<any, any, any, any>;
|
|
65
66
|
type AnyMatchHandler = (params: RefSubject.RefSubject<any>) => Fx.Fx<any, any, any>;
|
|
66
67
|
|
|
67
|
-
type DependencyProvided<D> =
|
|
68
|
+
export type DependencyProvided<D> =
|
|
68
69
|
D extends Layer.Layer<infer Provided, any, any>
|
|
69
70
|
? Provided
|
|
70
71
|
: D extends ServiceMap.ServiceMap<infer Provided>
|
|
71
72
|
? Provided
|
|
72
73
|
: never;
|
|
73
|
-
type DependencyError<D> = D extends Layer.Layer<any, infer E, any> ? E : never;
|
|
74
|
-
type DependencyRequirements<D> = D extends Layer.Layer<any, any, infer R> ? R : never;
|
|
74
|
+
export type DependencyError<D> = D extends Layer.Layer<any, infer E, any> ? E : never;
|
|
75
|
+
export type DependencyRequirements<D> = D extends Layer.Layer<any, any, infer R> ? R : never;
|
|
75
76
|
|
|
76
77
|
type LayerSuccess<L> = L extends Layer.Layer<infer Provided, any, any> ? Provided : never;
|
|
77
78
|
type LayerError<L> = L extends Layer.Layer<any, infer E, any> ? E : never;
|
|
@@ -85,19 +86,19 @@ export interface AsGuard<I, O, E = never, R = never> {
|
|
|
85
86
|
}
|
|
86
87
|
export type GuardInput<I, O, E = never, R = never> = GuardType<I, O, E, R> | AsGuard<I, O, E, R>;
|
|
87
88
|
|
|
88
|
-
type GuardOutput<G> =
|
|
89
|
+
export type GuardOutput<G> =
|
|
89
90
|
G extends GuardType<any, infer O, any, any>
|
|
90
91
|
? O
|
|
91
92
|
: G extends AsGuard<any, infer O, any, any>
|
|
92
93
|
? O
|
|
93
94
|
: never;
|
|
94
|
-
type GuardError<G> =
|
|
95
|
+
export type GuardError<G> =
|
|
95
96
|
G extends GuardType<any, any, infer E, any>
|
|
96
97
|
? E
|
|
97
98
|
: G extends AsGuard<any, any, infer E, any>
|
|
98
99
|
? E
|
|
99
100
|
: never;
|
|
100
|
-
type GuardServices<G> =
|
|
101
|
+
export type GuardServices<G> =
|
|
101
102
|
G extends GuardType<any, any, any, infer R>
|
|
102
103
|
? R
|
|
103
104
|
: G extends AsGuard<any, any, any, infer R>
|
|
@@ -114,7 +115,7 @@ type MatchOptions<Rt extends Route.Any, B, E2, R2, D, LB, LE2, LR2, C> = {
|
|
|
114
115
|
readonly catch?: C;
|
|
115
116
|
};
|
|
116
117
|
|
|
117
|
-
type MatchHandlerReturnValue<A, E, R> =
|
|
118
|
+
export type MatchHandlerReturnValue<A, E, R> =
|
|
118
119
|
| Fx.Fx<A, E, R>
|
|
119
120
|
| Stream.Stream<A, E, R>
|
|
120
121
|
| Effect.Effect<A, E, R>
|
|
@@ -149,7 +150,10 @@ type ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GE, GR> = ApplyCatch<
|
|
|
149
150
|
C
|
|
150
151
|
>;
|
|
151
152
|
|
|
152
|
-
export interface Matcher<A, E = never, R = never>
|
|
153
|
+
export interface Matcher<A, E = never, R = never>
|
|
154
|
+
extends
|
|
155
|
+
Fx.Fx<A, E | RouteNotFound | RouteDecodeError | RouteGuardError, R | Router | Scope.Scope>,
|
|
156
|
+
Pipeable {
|
|
153
157
|
readonly cases: ReadonlyArray<MatchAst>;
|
|
154
158
|
|
|
155
159
|
// Overload 1: match(route, handler) - function handler (must be first for inference)
|
|
@@ -431,6 +435,15 @@ function parseMatchArgs(args: [unknown, ...Array<unknown>]): ParsedMatch {
|
|
|
431
435
|
}
|
|
432
436
|
|
|
433
437
|
class MatcherImpl<A, E, R> implements Matcher<A, E, R> {
|
|
438
|
+
readonly [FxTypeId]: Fx.Fx.Variance<
|
|
439
|
+
A,
|
|
440
|
+
E | RouteNotFound | RouteDecodeError | RouteGuardError,
|
|
441
|
+
R | Scope.Scope | Router
|
|
442
|
+
> = {
|
|
443
|
+
_A: identity,
|
|
444
|
+
_E: identity,
|
|
445
|
+
_R: identity,
|
|
446
|
+
};
|
|
434
447
|
readonly cases: ReadonlyArray<MatchAst>;
|
|
435
448
|
constructor(cases: ReadonlyArray<MatchAst>) {
|
|
436
449
|
this.cases = cases;
|
|
@@ -660,118 +673,13 @@ class MatcherImpl<A, E, R> implements Matcher<A, E, R> {
|
|
|
660
673
|
pipe() {
|
|
661
674
|
return pipeArguments(this, arguments);
|
|
662
675
|
}
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
function normalizeHandler<Params, B, E2 = never, R2 = never>(
|
|
666
|
-
handler: MatchHandler<Params, B, E2, R2>,
|
|
667
|
-
): (params: RefSubject.RefSubject<Params>) => Fx.Fx<B, E2, R2> {
|
|
668
|
-
if (isMatchHandlerFn(handler)) return (params) => toFx(handler(params));
|
|
669
|
-
return () => toFx(handler);
|
|
670
|
-
}
|
|
671
676
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
): Fx.Fx<A, E, R> {
|
|
675
|
-
if (isFx(value)) return value;
|
|
676
|
-
if (Stream.isStream(value)) return fromStream(value);
|
|
677
|
-
if (Effect.isEffect(value)) return fromEffect(value);
|
|
678
|
-
return succeed(value);
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
export const empty: Matcher<never> = new MatcherImpl([]);
|
|
682
|
-
export const match = empty.match.bind(empty);
|
|
683
|
-
|
|
684
|
-
/**
|
|
685
|
-
* Merge multiple matchers into one. Each matcher's layouts and provide apply only to its own routes.
|
|
686
|
-
* Use this so directory layouts (e.g. api/_layout) and directory dependencies apply only to routes under that directory.
|
|
687
|
-
*/
|
|
688
|
-
export function merge<const Matchers extends ReadonlyArray<Matcher.Any>>(
|
|
689
|
-
...matchers: Matchers
|
|
690
|
-
): Matcher<
|
|
691
|
-
Matcher.MergeSuccess<Matchers>,
|
|
692
|
-
Matcher.MergeError<Matchers>,
|
|
693
|
-
Matcher.MergeServices<Matchers>
|
|
694
|
-
> {
|
|
695
|
-
if (matchers.length === 0) {
|
|
696
|
-
return empty as unknown as Matcher<
|
|
697
|
-
Matcher.MergeSuccess<Matchers>,
|
|
698
|
-
Matcher.MergeError<Matchers>,
|
|
699
|
-
Matcher.MergeServices<Matchers>
|
|
700
|
-
>;
|
|
701
|
-
}
|
|
702
|
-
if (matchers.length === 1) {
|
|
703
|
-
return matchers[0] as unknown as Matcher<
|
|
704
|
-
Matcher.MergeSuccess<Matchers>,
|
|
705
|
-
Matcher.MergeError<Matchers>,
|
|
706
|
-
Matcher.MergeServices<Matchers>
|
|
707
|
-
>;
|
|
708
|
-
}
|
|
709
|
-
const first = matchers[0] as MatcherImpl<
|
|
710
|
-
Matcher.MergeSuccess<Matchers>,
|
|
711
|
-
Matcher.MergeError<Matchers>,
|
|
712
|
-
Matcher.MergeServices<Matchers>
|
|
713
|
-
>;
|
|
714
|
-
const rest = matchers.slice(1) as ReadonlyArray<
|
|
715
|
-
Matcher<
|
|
716
|
-
Matcher.MergeSuccess<Matchers>,
|
|
717
|
-
Matcher.MergeError<Matchers>,
|
|
718
|
-
Matcher.MergeServices<Matchers>
|
|
719
|
-
>
|
|
720
|
-
>;
|
|
721
|
-
return first.merge(...rest) as unknown as Matcher<
|
|
722
|
-
Matcher.MergeSuccess<Matchers>,
|
|
723
|
-
Matcher.MergeError<Matchers>,
|
|
724
|
-
Matcher.MergeServices<Matchers>
|
|
725
|
-
>;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
export class RouteGuardError extends Schema.ErrorClass<RouteGuardError>(
|
|
729
|
-
"@typed/router/RouteGuardError",
|
|
730
|
-
)({
|
|
731
|
-
_tag: Schema.tag("RouteGuardError"),
|
|
732
|
-
path: Schema.String,
|
|
733
|
-
causes: Schema.Array(Schema.Unknown),
|
|
734
|
-
}) {}
|
|
735
|
-
|
|
736
|
-
export class RouteNotFound extends Schema.ErrorClass<RouteNotFound>("@typed/router/RouteNotFound")({
|
|
737
|
-
_tag: Schema.tag("RouteNotFound"),
|
|
738
|
-
path: Schema.String,
|
|
739
|
-
}) {}
|
|
740
|
-
|
|
741
|
-
export class RouteDecodeError extends Schema.ErrorClass<RouteDecodeError>(
|
|
742
|
-
"@typed/router/RouteDecodeError",
|
|
743
|
-
)({
|
|
744
|
-
_tag: Schema.tag("RouteDecodeError"),
|
|
745
|
-
path: Schema.String,
|
|
746
|
-
cause: Schema.String,
|
|
747
|
-
}) {}
|
|
748
|
-
|
|
749
|
-
/**
|
|
750
|
-
* @internal
|
|
751
|
-
*/
|
|
752
|
-
export type CompiledEntry = {
|
|
753
|
-
readonly route: Route.Any;
|
|
754
|
-
readonly guard: AnyGuard;
|
|
755
|
-
readonly handler: AnyMatchHandler;
|
|
756
|
-
readonly layers: ReadonlyArray<AnyLayer>;
|
|
757
|
-
readonly layouts: ReadonlyArray<AnyLayout>;
|
|
758
|
-
readonly catches: ReadonlyArray<AnyCatch>;
|
|
759
|
-
readonly decode: (input: unknown) => Effect.Effect<any, Schema.SchemaError, any>;
|
|
760
|
-
};
|
|
761
|
-
|
|
762
|
-
export function run<M extends Matcher.Any>(
|
|
763
|
-
matcher: M,
|
|
764
|
-
): Fx.Fx<
|
|
765
|
-
Matcher.Success<M>,
|
|
766
|
-
Matcher.Error<M> | RouteNotFound | RouteDecodeError | RouteGuardError,
|
|
767
|
-
Matcher.Services<M> | Router | CurrentRoute | Scope.Scope
|
|
768
|
-
> {
|
|
769
|
-
return unwrap(
|
|
770
|
-
Effect.gen(function* () {
|
|
677
|
+
run<RSink>(sink: Sink.Sink<A, E | RouteNotFound | RouteDecodeError | RouteGuardError, RSink>) {
|
|
678
|
+
return Effect.gen({ self: this }, function* () {
|
|
771
679
|
const fiberId = yield* Effect.fiberId;
|
|
772
680
|
const rootScope = yield* Effect.scope;
|
|
773
681
|
const current = yield* CurrentRoute;
|
|
774
|
-
const prefixed =
|
|
682
|
+
const prefixed = this.prefix(current.route);
|
|
775
683
|
const entries = compile(prefixed.cases);
|
|
776
684
|
const router = findMyWay.make<ReadonlyArray<CompiledEntry>>({
|
|
777
685
|
ignoreTrailingSlash: true,
|
|
@@ -798,11 +706,11 @@ export function run<M extends Matcher.Any>(
|
|
|
798
706
|
let currentState: {
|
|
799
707
|
entry: CompiledEntry;
|
|
800
708
|
params: RefSubject.RefSubject<any>;
|
|
801
|
-
fx: Fx.Fx<
|
|
709
|
+
fx: Fx.Fx<A, E, R | Scope.Scope | Router>;
|
|
802
710
|
scope: Scope.Closeable;
|
|
803
711
|
} | null = null;
|
|
804
712
|
|
|
805
|
-
|
|
713
|
+
const stream = CurrentPath.pipe(
|
|
806
714
|
mapEffect(
|
|
807
715
|
Effect.fn(function* (path) {
|
|
808
716
|
const result = router.find("GET", path);
|
|
@@ -905,10 +813,109 @@ export function run<M extends Matcher.Any>(
|
|
|
905
813
|
skipRepeats,
|
|
906
814
|
switchMap(identity),
|
|
907
815
|
);
|
|
908
|
-
|
|
909
|
-
|
|
816
|
+
|
|
817
|
+
return yield* stream.run(sink);
|
|
818
|
+
});
|
|
819
|
+
}
|
|
910
820
|
}
|
|
911
821
|
|
|
822
|
+
function normalizeHandler<Params, B, E2 = never, R2 = never>(
|
|
823
|
+
handler: MatchHandler<Params, B, E2, R2>,
|
|
824
|
+
): (params: RefSubject.RefSubject<Params>) => Fx.Fx<B, E2, R2> {
|
|
825
|
+
if (isMatchHandlerFn(handler)) return (params) => toFx(handler(params));
|
|
826
|
+
return () => toFx(handler);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function toFx<A, E, R>(
|
|
830
|
+
value: Fx.Fx<A, E, R> | Stream.Stream<A, E, R> | Effect.Effect<A, E, R> | A,
|
|
831
|
+
): Fx.Fx<A, E, R> {
|
|
832
|
+
if (isFx(value)) return value;
|
|
833
|
+
if (Stream.isStream(value)) return fromStream(value);
|
|
834
|
+
if (Effect.isEffect(value)) return fromEffect(value);
|
|
835
|
+
return succeed(value);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
export const empty: Matcher<never> = new MatcherImpl([]);
|
|
839
|
+
export const match = empty.match.bind(empty);
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Merge multiple matchers into one. Each matcher's layouts and provide apply only to its own routes.
|
|
843
|
+
* Use this so directory layouts (e.g. api/_layout) and directory dependencies apply only to routes under that directory.
|
|
844
|
+
*/
|
|
845
|
+
export function merge<const Matchers extends ReadonlyArray<Matcher.Any>>(
|
|
846
|
+
...matchers: Matchers
|
|
847
|
+
): Matcher<
|
|
848
|
+
Matcher.MergeSuccess<Matchers>,
|
|
849
|
+
Matcher.MergeError<Matchers>,
|
|
850
|
+
Matcher.MergeServices<Matchers>
|
|
851
|
+
> {
|
|
852
|
+
if (matchers.length === 0) {
|
|
853
|
+
return empty as unknown as Matcher<
|
|
854
|
+
Matcher.MergeSuccess<Matchers>,
|
|
855
|
+
Matcher.MergeError<Matchers>,
|
|
856
|
+
Matcher.MergeServices<Matchers>
|
|
857
|
+
>;
|
|
858
|
+
}
|
|
859
|
+
if (matchers.length === 1) {
|
|
860
|
+
return matchers[0] as unknown as Matcher<
|
|
861
|
+
Matcher.MergeSuccess<Matchers>,
|
|
862
|
+
Matcher.MergeError<Matchers>,
|
|
863
|
+
Matcher.MergeServices<Matchers>
|
|
864
|
+
>;
|
|
865
|
+
}
|
|
866
|
+
const first = matchers[0] as MatcherImpl<
|
|
867
|
+
Matcher.MergeSuccess<Matchers>,
|
|
868
|
+
Matcher.MergeError<Matchers>,
|
|
869
|
+
Matcher.MergeServices<Matchers>
|
|
870
|
+
>;
|
|
871
|
+
const rest = matchers.slice(1) as ReadonlyArray<
|
|
872
|
+
Matcher<
|
|
873
|
+
Matcher.MergeSuccess<Matchers>,
|
|
874
|
+
Matcher.MergeError<Matchers>,
|
|
875
|
+
Matcher.MergeServices<Matchers>
|
|
876
|
+
>
|
|
877
|
+
>;
|
|
878
|
+
return first.merge(...rest) as unknown as Matcher<
|
|
879
|
+
Matcher.MergeSuccess<Matchers>,
|
|
880
|
+
Matcher.MergeError<Matchers>,
|
|
881
|
+
Matcher.MergeServices<Matchers>
|
|
882
|
+
>;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
export class RouteGuardError extends Schema.ErrorClass<RouteGuardError>(
|
|
886
|
+
"@typed/router/RouteGuardError",
|
|
887
|
+
)({
|
|
888
|
+
_tag: Schema.tag("RouteGuardError"),
|
|
889
|
+
path: Schema.String,
|
|
890
|
+
causes: Schema.Array(Schema.Unknown),
|
|
891
|
+
}) {}
|
|
892
|
+
|
|
893
|
+
export class RouteNotFound extends Schema.ErrorClass<RouteNotFound>("@typed/router/RouteNotFound")({
|
|
894
|
+
_tag: Schema.tag("RouteNotFound"),
|
|
895
|
+
path: Schema.String,
|
|
896
|
+
}) {}
|
|
897
|
+
|
|
898
|
+
export class RouteDecodeError extends Schema.ErrorClass<RouteDecodeError>(
|
|
899
|
+
"@typed/router/RouteDecodeError",
|
|
900
|
+
)({
|
|
901
|
+
_tag: Schema.tag("RouteDecodeError"),
|
|
902
|
+
path: Schema.String,
|
|
903
|
+
cause: Schema.String,
|
|
904
|
+
}) {}
|
|
905
|
+
|
|
906
|
+
/**
|
|
907
|
+
* @internal
|
|
908
|
+
*/
|
|
909
|
+
export type CompiledEntry = {
|
|
910
|
+
readonly route: Route.Any;
|
|
911
|
+
readonly guard: AnyGuard;
|
|
912
|
+
readonly handler: AnyMatchHandler;
|
|
913
|
+
readonly layers: ReadonlyArray<AnyLayer>;
|
|
914
|
+
readonly layouts: ReadonlyArray<AnyLayout>;
|
|
915
|
+
readonly catches: ReadonlyArray<AnyCatch>;
|
|
916
|
+
readonly decode: (input: unknown) => Effect.Effect<any, Schema.SchemaError, any>;
|
|
917
|
+
};
|
|
918
|
+
|
|
912
919
|
type InputSucces<T> = [Matcher.Success<T> | Fx.Fx.Success<T>] extends [infer A] ? A : never;
|
|
913
920
|
type InputError<T> = [Matcher.Error<T> | Fx.Fx.Error<T>] extends [infer E] ? E : never;
|
|
914
921
|
type InputServices<T> = [Matcher.Services<T> | Fx.Fx.Services<T>] extends [infer R] ? R : never;
|
|
@@ -943,11 +950,10 @@ export const catchCause: {
|
|
|
943
950
|
const eff = Effect.gen(function* () {
|
|
944
951
|
const fiberId = yield* Effect.fiberId;
|
|
945
952
|
const rootScope = yield* Effect.scope;
|
|
946
|
-
const fx = isFx(input) ? input : run(input);
|
|
947
953
|
const manager = makeCatchManager(rootScope, fiberId);
|
|
948
954
|
const result = yield* manager.apply(
|
|
949
955
|
[f],
|
|
950
|
-
|
|
956
|
+
input,
|
|
951
957
|
ServiceMap.empty() as ServiceMap.ServiceMap<any>,
|
|
952
958
|
);
|
|
953
959
|
return result as Fx.Fx<A | B, E2, R | R2 | Router | Scope.Scope>;
|
|
@@ -960,6 +966,11 @@ export const catch_: {
|
|
|
960
966
|
<I extends Fx.Fx.Any | Matcher.Any, B, E2, R2>(
|
|
961
967
|
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
962
968
|
): (input: I) => Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
969
|
+
|
|
970
|
+
<I extends Fx.Fx.Any | Matcher.Any, B, E2, R2>(
|
|
971
|
+
input: I,
|
|
972
|
+
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
973
|
+
): Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
963
974
|
} = dual(
|
|
964
975
|
2,
|
|
965
976
|
<I extends Fx.Fx.Any | Matcher.Any, B, E2, R2>(
|
|
@@ -1092,16 +1103,38 @@ function normalizeDependencies(
|
|
|
1092
1103
|
return dependencies.map(toSingleLayer);
|
|
1093
1104
|
}
|
|
1094
1105
|
|
|
1106
|
+
type NormalizeLayer<T extends AnyDependency> =
|
|
1107
|
+
T extends Layer.Layer<infer A, infer E, infer R>
|
|
1108
|
+
? Layer.Layer<A, E, R>
|
|
1109
|
+
: T extends ServiceMap.ServiceMap<infer R>
|
|
1110
|
+
? Layer.Layer<R>
|
|
1111
|
+
: never;
|
|
1112
|
+
|
|
1113
|
+
type NormalizeLayers<T extends ReadonlyArray<AnyDependency>> = {
|
|
1114
|
+
[K in keyof T]: NormalizeLayer<T[K]>;
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
type ToLayer<T> =
|
|
1118
|
+
T extends ReadonlyArray<AnyLayer>
|
|
1119
|
+
? Layer.Layer<Layer.Success<T[number]>, Layer.Error<T[number]>, Layer.Services<T[number]>>
|
|
1120
|
+
: never;
|
|
1121
|
+
|
|
1122
|
+
type NormalizeDeps<T extends AnyDependency | ReadonlyArray<AnyDependency>> = T extends AnyDependency
|
|
1123
|
+
? NormalizeLayer<T>
|
|
1124
|
+
: T extends ReadonlyArray<AnyDependency>
|
|
1125
|
+
? ToLayer<NormalizeLayers<T>>
|
|
1126
|
+
: never;
|
|
1127
|
+
|
|
1095
1128
|
/**
|
|
1096
1129
|
* Normalize dependency input (ServiceMap | Layer | Array of either) into a single Layer.
|
|
1097
1130
|
* Use with `.provide(normalizeDependencyInput(deps))`.
|
|
1098
1131
|
*/
|
|
1099
|
-
export function normalizeDependencyInput(
|
|
1100
|
-
input:
|
|
1101
|
-
):
|
|
1132
|
+
export function normalizeDependencyInput<Deps extends AnyDependency | ReadonlyArray<AnyDependency>>(
|
|
1133
|
+
input: Deps,
|
|
1134
|
+
): NormalizeDeps<Deps> {
|
|
1102
1135
|
const arr = Array.isArray(input) ? input : [input];
|
|
1103
|
-
const layers = normalizeDependencies(arr);
|
|
1104
|
-
return mergeLayers(layers)
|
|
1136
|
+
const layers = normalizeDependencies(arr as AnyDependency[]);
|
|
1137
|
+
return mergeLayers(layers) as NormalizeDeps<Deps>;
|
|
1105
1138
|
}
|
|
1106
1139
|
|
|
1107
1140
|
function getGuard<I, O, E, R>(guard: GuardInput<I, O, E, R>): GuardType<I, O, E, R> {
|
|
@@ -1411,7 +1444,7 @@ export function makeCatchManager(rootScope: Scope.Scope, fiberId: number) {
|
|
|
1411
1444
|
exit,
|
|
1412
1445
|
mapEffect(
|
|
1413
1446
|
Effect.fn(function* (e) {
|
|
1414
|
-
if (isSuccess(e)) return succeed(e.value);
|
|
1447
|
+
if (Exit.isSuccess(e)) return succeed(e.value);
|
|
1415
1448
|
yield* RefSubject.set(causes, e.cause);
|
|
1416
1449
|
return fallback;
|
|
1417
1450
|
}),
|
package/src/Route.ts
CHANGED
|
@@ -35,6 +35,12 @@ export declare namespace Route {
|
|
|
35
35
|
export type QueryType<T extends Any> = T["querySchema"]["Type"];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export type Any = Route.Any;
|
|
39
|
+
export type Params<T> = Route.Params<T>;
|
|
40
|
+
export type Type<T> = Route.Type<T>;
|
|
41
|
+
export type PathType<T extends Any> = Route.PathType<T>;
|
|
42
|
+
export type QueryType<T extends Any> = Route.QueryType<T>;
|
|
43
|
+
|
|
38
44
|
export function make<
|
|
39
45
|
const P extends string,
|
|
40
46
|
S extends Schema.Codec<any, Path.Params<P>, any, any> = Schema.Codec<Path.Params<P>>,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import { BrowserRouter, Router } from "../Router.js";
|
|
3
|
+
import { Scope } from "effect";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Build an absolute URL for the Vitest browser page origin (same pattern as in-memory tests using http://localhost/...).
|
|
7
|
+
*/
|
|
8
|
+
export const absoluteUrl = (
|
|
9
|
+
path: string,
|
|
10
|
+
win: Window & typeof globalThis = globalThis.window,
|
|
11
|
+
): string => {
|
|
12
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
13
|
+
return new URL(normalized, win.location.origin).href;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Run an effect with {@link BrowserRouter} scoped over the real (or test) `window`.
|
|
18
|
+
*/
|
|
19
|
+
export const runWithBrowserRouter = <A, E>(
|
|
20
|
+
effect: Effect.Effect<A, E, Router | Scope.Scope>,
|
|
21
|
+
win: Window & typeof globalThis = globalThis.window,
|
|
22
|
+
): Promise<A> => effect.pipe(Effect.provide(BrowserRouter(win)), Effect.scoped, Effect.runPromise);
|