@typed/router 1.0.0-beta.2 → 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 +6 -7
- 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/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 +16 -13
- package/src/Matcher.browser.test.ts +771 -0
- package/src/Matcher.test.ts +344 -67
- package/src/Matcher.ts +146 -134
- 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>,
|
|
@@ -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
|
-
|
|
672
|
-
function toFx<A, E, R>(
|
|
673
|
-
value: Fx.Fx<A, E, R> | Stream.Stream<A, E, R> | Effect.Effect<A, E, R> | A,
|
|
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
676
|
|
|
728
|
-
|
|
729
|
-
|
|
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,26 +1103,27 @@ function normalizeDependencies(
|
|
|
1092
1103
|
return dependencies.map(toSingleLayer);
|
|
1093
1104
|
}
|
|
1094
1105
|
|
|
1095
|
-
type NormalizeLayer<T extends AnyDependency> =
|
|
1096
|
-
T extends Layer.Layer<infer A, infer E, infer R>
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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
|
+
};
|
|
1101
1116
|
|
|
1102
|
-
type ToLayer<T> =
|
|
1103
|
-
T extends ReadonlyArray<AnyLayer>
|
|
1104
|
-
Layer.Success<T[number]>,
|
|
1105
|
-
|
|
1106
|
-
Layer.Services<T[number]>
|
|
1107
|
-
> : never
|
|
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;
|
|
1108
1121
|
|
|
1109
|
-
type NormalizeDeps<T extends AnyDependency | ReadonlyArray<AnyDependency>> =
|
|
1110
|
-
T extends AnyDependency
|
|
1122
|
+
type NormalizeDeps<T extends AnyDependency | ReadonlyArray<AnyDependency>> = T extends AnyDependency
|
|
1111
1123
|
? NormalizeLayer<T>
|
|
1112
1124
|
: T extends ReadonlyArray<AnyDependency>
|
|
1113
|
-
|
|
1114
|
-
|
|
1125
|
+
? ToLayer<NormalizeLayers<T>>
|
|
1126
|
+
: never;
|
|
1115
1127
|
|
|
1116
1128
|
/**
|
|
1117
1129
|
* Normalize dependency input (ServiceMap | Layer | Array of either) into a single Layer.
|
|
@@ -1432,7 +1444,7 @@ export function makeCatchManager(rootScope: Scope.Scope, fiberId: number) {
|
|
|
1432
1444
|
exit,
|
|
1433
1445
|
mapEffect(
|
|
1434
1446
|
Effect.fn(function* (e) {
|
|
1435
|
-
if (isSuccess(e)) return succeed(e.value);
|
|
1447
|
+
if (Exit.isSuccess(e)) return succeed(e.value);
|
|
1436
1448
|
yield* RefSubject.set(causes, e.cause);
|
|
1437
1449
|
return fallback;
|
|
1438
1450
|
}),
|
|
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);
|