@typed/router 0.32.0 → 1.0.0-beta.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/README.md +129 -2
- package/dist/AST.d.ts +96 -0
- package/dist/AST.d.ts.map +1 -0
- package/dist/AST.js +32 -0
- package/dist/CurrentRoute.d.ts +18 -0
- package/dist/CurrentRoute.d.ts.map +1 -0
- package/dist/CurrentRoute.js +18 -0
- package/dist/Matcher.d.ts +209 -0
- package/dist/Matcher.d.ts.map +1 -0
- package/dist/Matcher.js +633 -0
- package/dist/Parser.d.ts +92 -0
- package/dist/Parser.d.ts.map +1 -0
- package/dist/Parser.js +1 -0
- package/dist/Path.d.ts +216 -0
- package/dist/Path.d.ts.map +1 -0
- package/dist/Path.js +248 -0
- package/dist/Route.d.ts +57 -0
- package/dist/Route.d.ts.map +1 -0
- package/dist/Route.js +151 -0
- package/dist/Router.d.ts +9 -0
- package/dist/Router.d.ts.map +1 -0
- package/dist/Router.js +8 -0
- package/dist/Uri.d.ts +115 -0
- package/dist/Uri.d.ts.map +1 -0
- package/dist/Uri.js +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/package.json +32 -73
- package/src/AST.ts +166 -0
- package/src/CurrentRoute.ts +30 -331
- package/src/Matcher.test.ts +496 -0
- package/src/Matcher.ts +1375 -325
- package/src/Parser.ts +276 -0
- package/src/Path.test.ts +318 -0
- package/src/Path.ts +691 -0
- package/src/Route.test.ts +268 -0
- package/src/Route.ts +316 -0
- package/src/Router.ts +33 -0
- package/src/Uri.ts +214 -0
- package/src/index.ts +4 -28
- package/CurrentRoute/package.json +0 -6
- package/LICENSE +0 -21
- package/MatchInput/package.json +0 -6
- package/Matcher/package.json +0 -6
- package/RouteGuard/package.json +0 -6
- package/RouteMatch/package.json +0 -6
- package/dist/cjs/CurrentRoute.js +0 -170
- package/dist/cjs/CurrentRoute.js.map +0 -1
- package/dist/cjs/MatchInput.js +0 -96
- package/dist/cjs/MatchInput.js.map +0 -1
- package/dist/cjs/Matcher.js +0 -138
- package/dist/cjs/Matcher.js.map +0 -1
- package/dist/cjs/RouteGuard.js +0 -78
- package/dist/cjs/RouteGuard.js.map +0 -1
- package/dist/cjs/RouteMatch.js +0 -49
- package/dist/cjs/RouteMatch.js.map +0 -1
- package/dist/cjs/index.js +0 -53
- package/dist/cjs/index.js.map +0 -1
- package/dist/dts/CurrentRoute.d.ts +0 -94
- package/dist/dts/CurrentRoute.d.ts.map +0 -1
- package/dist/dts/MatchInput.d.ts +0 -143
- package/dist/dts/MatchInput.d.ts.map +0 -1
- package/dist/dts/Matcher.d.ts +0 -121
- package/dist/dts/Matcher.d.ts.map +0 -1
- package/dist/dts/RouteGuard.d.ts +0 -94
- package/dist/dts/RouteGuard.d.ts.map +0 -1
- package/dist/dts/RouteMatch.d.ts +0 -50
- package/dist/dts/RouteMatch.d.ts.map +0 -1
- package/dist/dts/index.d.ts +0 -24
- package/dist/dts/index.d.ts.map +0 -1
- package/dist/esm/CurrentRoute.js +0 -152
- package/dist/esm/CurrentRoute.js.map +0 -1
- package/dist/esm/MatchInput.js +0 -79
- package/dist/esm/MatchInput.js.map +0 -1
- package/dist/esm/Matcher.js +0 -130
- package/dist/esm/Matcher.js.map +0 -1
- package/dist/esm/RouteGuard.js +0 -57
- package/dist/esm/RouteGuard.js.map +0 -1
- package/dist/esm/RouteMatch.js +0 -29
- package/dist/esm/RouteMatch.js.map +0 -1
- package/dist/esm/index.js +0 -24
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/package.json +0 -4
- package/src/MatchInput.ts +0 -303
- package/src/RouteGuard.ts +0 -217
- package/src/RouteMatch.ts +0 -104
package/src/Matcher.ts
CHANGED
|
@@ -1,385 +1,1435 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
|
|
1
|
+
import * as findMyWay from "find-my-way-ts";
|
|
2
|
+
import type * as Arr from "effect/Array";
|
|
3
|
+
import * as Cause from "effect/Cause";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Exit from "effect/Exit";
|
|
6
|
+
import { interrupt, isSuccess } from "effect/Exit";
|
|
7
|
+
import { dual, identity } from "effect/Function";
|
|
8
|
+
import * as Result from "effect/Result";
|
|
9
|
+
import * as Layer from "effect/Layer";
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import { type Pipeable, pipeArguments } from "effect/Pipeable";
|
|
12
|
+
import * as Schema from "effect/Schema";
|
|
13
|
+
import { makeFormatterDefault } from "effect/SchemaIssue";
|
|
14
|
+
import * as Scope from "effect/Scope";
|
|
15
|
+
import * as ServiceMap from "effect/ServiceMap";
|
|
16
|
+
import * as Stream from "effect/Stream";
|
|
17
|
+
import type { ExcludeTag, ExtractTag, NoInfer, Tags } from "effect/Types";
|
|
18
|
+
import { exit } from "@typed/fx/Fx";
|
|
19
|
+
import { mapEffect } from "@typed/fx/Fx/combinators/mapEffect";
|
|
20
|
+
import { provideServices } from "@typed/fx/Fx/combinators/provide";
|
|
21
|
+
import { skipRepeats } from "@typed/fx/Fx/combinators/skipRepeats";
|
|
22
|
+
import { switchMap } from "@typed/fx/Fx/combinators/switchMap";
|
|
23
|
+
import { unwrap } from "@typed/fx/Fx/combinators/unwrap";
|
|
24
|
+
import { fromEffect, never } from "@typed/fx/Fx/constructors/fromEffect";
|
|
25
|
+
import { succeed } from "@typed/fx/Fx/constructors/succeed";
|
|
26
|
+
import type * as Fx from "@typed/fx/Fx/Fx";
|
|
27
|
+
import { fromStream } from "@typed/fx/Fx/stream";
|
|
28
|
+
import { isFx } from "@typed/fx/Fx/TypeId";
|
|
29
|
+
import { RefSubject } from "@typed/fx/RefSubject";
|
|
30
|
+
import { CurrentPath, Navigation } from "@typed/navigation/Navigation";
|
|
31
|
+
import type { MatchAst, RouteAst } from "./AST.js";
|
|
32
|
+
import * as AST from "./AST.js";
|
|
33
|
+
import { CurrentRoute } from "./CurrentRoute.js";
|
|
34
|
+
import { Join, make as makeRoute, type Route } from "./Route.js";
|
|
35
|
+
import type { Router } from "./Router.js";
|
|
4
36
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import type { Navigation } from "@typed/navigation"
|
|
9
|
-
import { CurrentPath, isRedirectError, navigate, RedirectError } from "@typed/navigation"
|
|
10
|
-
import type * as Route from "@typed/route"
|
|
11
|
-
import * as Data from "effect/Data"
|
|
12
|
-
import * as Effect from "effect/Effect"
|
|
13
|
-
import { dual, pipe } from "effect/Function"
|
|
14
|
-
import * as Option from "effect/Option"
|
|
15
|
-
import { type Pipeable, pipeArguments } from "effect/Pipeable"
|
|
16
|
-
import { hasProperty } from "effect/Predicate"
|
|
17
|
-
import type * as Scope from "effect/Scope"
|
|
18
|
-
import * as Unify from "effect/Unify"
|
|
19
|
-
import type { CurrentRoute } from "./CurrentRoute.js"
|
|
20
|
-
import { makeHref } from "./CurrentRoute.js"
|
|
21
|
-
import type { MatchInput } from "./MatchInput.js"
|
|
22
|
-
import * as RouteMatch from "./RouteMatch.js"
|
|
37
|
+
export type Layout<Params, A, E, R, B, E2, R2> = (
|
|
38
|
+
params: LayoutParams<Params, A, E, R>,
|
|
39
|
+
) => Fx.Fx<B, E2, R2>;
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
export type LayoutParams<Params, A, E, R> = {
|
|
42
|
+
readonly params: RefSubject.RefSubject<Params>;
|
|
43
|
+
readonly content: Fx.Fx<A, E, R>;
|
|
44
|
+
};
|
|
28
45
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
export type RouteMatcherTypeId = typeof RouteMatcherTypeId
|
|
46
|
+
export type CatchHandler<E, A, E2, R2> = (
|
|
47
|
+
cause: RefSubject.RefSubject<Cause.Cause<E>>,
|
|
48
|
+
) => Fx.Fx<A, E2, R2>;
|
|
33
49
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
50
|
+
export type AnyLayer =
|
|
51
|
+
| Layer.Layer<any, any, any>
|
|
52
|
+
| Layer.Layer<never, any, any>
|
|
53
|
+
| Layer.Layer<any, never, any>
|
|
54
|
+
| Layer.Layer<any, any, never>
|
|
55
|
+
| Layer.Layer<never, never, never>
|
|
56
|
+
| Layer.Layer<any, never, never>
|
|
57
|
+
| Layer.Layer<never, any, never>
|
|
58
|
+
| Layer.Layer<never, never, any>;
|
|
39
59
|
|
|
40
|
-
|
|
60
|
+
type AnyServiceMap = ServiceMap.ServiceMap<any> | ServiceMap.ServiceMap<never>;
|
|
61
|
+
type AnyDependency = AnyLayer | AnyServiceMap;
|
|
62
|
+
type AnyLayout = Layout<any, any, any, any, any, any, any>;
|
|
63
|
+
type AnyCatch = CatchHandler<any, any, any, any>;
|
|
64
|
+
type AnyGuard = GuardType<any, any, any, any>;
|
|
65
|
+
type AnyMatchHandler = (params: RefSubject.RefSubject<any>) => Fx.Fx<any, any, any>;
|
|
41
66
|
|
|
42
|
-
|
|
67
|
+
type DependencyProvided<D> =
|
|
68
|
+
D extends Layer.Layer<infer Provided, any, any>
|
|
69
|
+
? Provided
|
|
70
|
+
: D extends ServiceMap.ServiceMap<infer Provided>
|
|
71
|
+
? Provided
|
|
72
|
+
: 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;
|
|
43
75
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
) => RouteMatcher<
|
|
48
|
-
| Matches
|
|
49
|
-
| RouteMatch.RouteMatch<
|
|
50
|
-
MatchInput.Route<I>,
|
|
51
|
-
MatchInput.Success<I>,
|
|
52
|
-
MatchInput.Error<I>,
|
|
53
|
-
MatchInput.Context<I>,
|
|
54
|
-
A,
|
|
55
|
-
E,
|
|
56
|
-
R
|
|
57
|
-
>
|
|
58
|
-
>
|
|
76
|
+
type LayerSuccess<L> = L extends Layer.Layer<infer Provided, any, any> ? Provided : never;
|
|
77
|
+
type LayerError<L> = L extends Layer.Layer<any, infer E, any> ? E : never;
|
|
78
|
+
type LayerServices<L> = L extends Layer.Layer<any, any, infer R> ? R : never;
|
|
59
79
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
MatchInput.Success<I>,
|
|
68
|
-
MatchInput.Error<I>,
|
|
69
|
-
MatchInput.Context<I>,
|
|
70
|
-
A,
|
|
71
|
-
E,
|
|
72
|
-
R | Scope.Scope
|
|
73
|
-
>
|
|
74
|
-
>
|
|
80
|
+
export type GuardType<I, O, E = never, R = never> = (
|
|
81
|
+
input: I,
|
|
82
|
+
) => Effect.Effect<Option.Option<O>, E, R>;
|
|
83
|
+
export interface AsGuard<I, O, E = never, R = never> {
|
|
84
|
+
readonly asGuard: () => GuardType<I, O, E, R>;
|
|
85
|
+
}
|
|
86
|
+
export type GuardInput<I, O, E = never, R = never> = GuardType<I, O, E, R> | AsGuard<I, O, E, R>;
|
|
75
87
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
type GuardOutput<G> =
|
|
89
|
+
G extends GuardType<any, infer O, any, any>
|
|
90
|
+
? O
|
|
91
|
+
: G extends AsGuard<any, infer O, any, any>
|
|
92
|
+
? O
|
|
93
|
+
: never;
|
|
94
|
+
type GuardError<G> =
|
|
95
|
+
G extends GuardType<any, any, infer E, any>
|
|
96
|
+
? E
|
|
97
|
+
: G extends AsGuard<any, any, infer E, any>
|
|
98
|
+
? E
|
|
99
|
+
: never;
|
|
100
|
+
type GuardServices<G> =
|
|
101
|
+
G extends GuardType<any, any, any, infer R>
|
|
102
|
+
? R
|
|
103
|
+
: G extends AsGuard<any, any, any, infer R>
|
|
104
|
+
? R
|
|
105
|
+
: never;
|
|
91
106
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
type MatchOptions<Rt extends Route.Any, B, E2, R2, D, LB, LE2, LR2, C> = {
|
|
108
|
+
readonly route: Rt;
|
|
109
|
+
readonly handler:
|
|
110
|
+
| MatchHandlerReturnValue<B, E2, R2>
|
|
111
|
+
| ((params: RefSubject.RefSubject<Route.Type<Rt>>) => MatchHandlerReturnValue<B, E2, R2>);
|
|
112
|
+
readonly dependencies?: D;
|
|
113
|
+
readonly layout?: Layout<Route.Type<Rt>, B, E2, R2, LB, LE2, LR2>;
|
|
114
|
+
readonly catch?: C;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
type MatchHandlerReturnValue<A, E, R> =
|
|
118
|
+
| Fx.Fx<A, E, R>
|
|
119
|
+
| Stream.Stream<A, E, R>
|
|
120
|
+
| Effect.Effect<A, E, R>
|
|
121
|
+
| A;
|
|
122
|
+
|
|
123
|
+
type MatchHandlerOptions<Params, B, E2, R2, D, LB, LE2, LR2, C> = {
|
|
124
|
+
readonly handler:
|
|
125
|
+
| MatchHandlerReturnValue<B, E2, R2>
|
|
126
|
+
| ((params: RefSubject.RefSubject<Params>) => MatchHandlerReturnValue<B, E2, R2>);
|
|
127
|
+
readonly dependencies?: D;
|
|
128
|
+
readonly layout?: Layout<Params, B, E2, R2, LB, LE2, LR2>;
|
|
129
|
+
readonly catch?: C;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
type ApplyDependencies<E, R, D> =
|
|
133
|
+
D extends ReadonlyArray<infer Dep>
|
|
134
|
+
? {
|
|
135
|
+
readonly e: E | DependencyError<Dep>;
|
|
136
|
+
readonly r: Exclude<R, DependencyProvided<Dep>> | DependencyRequirements<Dep>;
|
|
137
|
+
}
|
|
138
|
+
: { readonly e: E; readonly r: R };
|
|
139
|
+
|
|
140
|
+
type ApplyCatch<A, E, R, C> =
|
|
141
|
+
C extends CatchHandler<any, infer CA, infer CE, infer CR>
|
|
142
|
+
? { readonly a: A | CA; readonly e: CE; readonly r: R | CR }
|
|
143
|
+
: { readonly a: A; readonly e: E; readonly r: R };
|
|
144
|
+
|
|
145
|
+
type ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GE, GR> = ApplyCatch<
|
|
146
|
+
LB,
|
|
147
|
+
ApplyDependencies<E2 | GE | LE2, R2 | GR | LR2, D>["e"],
|
|
148
|
+
ApplyDependencies<E2 | GE | LE2, R2 | GR | LR2, D>["r"],
|
|
149
|
+
C
|
|
150
|
+
>;
|
|
151
|
+
|
|
152
|
+
export interface Matcher<A, E = never, R = never> extends Pipeable {
|
|
153
|
+
readonly cases: ReadonlyArray<MatchAst>;
|
|
154
|
+
|
|
155
|
+
// Overload 1: match(route, handler) - function handler (must be first for inference)
|
|
156
|
+
match<Rt extends Route.Any, B, E2 = never, R2 = never>(
|
|
157
|
+
route: Rt,
|
|
158
|
+
handler: (params: RefSubject.RefSubject<Route.Type<Rt>>) => MatchHandlerReturnValue<B, E2, R2>,
|
|
159
|
+
): Matcher<A | B, E | E2, R | R2 | Scope.Scope>;
|
|
160
|
+
|
|
161
|
+
// Overload 2: match(route, effectLike) - Fx/Effect/Stream handler
|
|
162
|
+
match<Rt extends Route.Any, B, E2 = never, R2 = never>(
|
|
163
|
+
route: Rt,
|
|
164
|
+
handler: Fx.Fx<B, E2, R2> | Effect.Effect<B, E2, R2> | Stream.Stream<B, E2, R2>,
|
|
165
|
+
): Matcher<A | B, E | E2, R | R2 | Scope.Scope>;
|
|
166
|
+
|
|
167
|
+
// Overload 3: match(route, options) - route with options object
|
|
168
|
+
match<
|
|
169
|
+
Rt extends Route.Any,
|
|
170
|
+
B,
|
|
171
|
+
E2 = never,
|
|
172
|
+
R2 = never,
|
|
173
|
+
D extends ReadonlyArray<AnyDependency> | undefined = undefined,
|
|
174
|
+
LB = B,
|
|
175
|
+
LE2 = never,
|
|
176
|
+
LR2 = never,
|
|
177
|
+
C extends CatchHandler<any, any, any, any> | undefined = undefined,
|
|
178
|
+
>(
|
|
179
|
+
route: Rt,
|
|
180
|
+
options: MatchHandlerOptions<Route.Type<Rt>, B, E2, R2, D, LB, LE2, LR2, C>,
|
|
181
|
+
): Matcher<
|
|
182
|
+
A | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["a"],
|
|
183
|
+
E | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["e"],
|
|
184
|
+
R | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["r"] | Scope.Scope
|
|
185
|
+
>;
|
|
186
|
+
|
|
187
|
+
// Overload 4: match(route, value) - direct value handler (last for 2-arg form)
|
|
188
|
+
match<Rt extends Route.Any, const B>(route: Rt, handler: B): Matcher<A | B, E, R | Scope.Scope>;
|
|
189
|
+
|
|
190
|
+
// Overload 5: match(route, guard, handler) - guard with function handler (must be before value)
|
|
191
|
+
match<
|
|
192
|
+
Rt extends Route.Any,
|
|
193
|
+
G extends GuardInput<Route.Type<Rt>, any, any, any>,
|
|
194
|
+
B,
|
|
195
|
+
E2 = never,
|
|
196
|
+
R2 = never,
|
|
197
|
+
>(
|
|
198
|
+
route: Rt,
|
|
199
|
+
guard: G,
|
|
200
|
+
handler: (params: RefSubject.RefSubject<GuardOutput<G>>) => MatchHandlerReturnValue<B, E2, R2>,
|
|
201
|
+
): Matcher<A | B, E | E2 | GuardError<G>, R | R2 | GuardServices<G> | Scope.Scope>;
|
|
202
|
+
|
|
203
|
+
// Overload 6: match(route, guard, effectLike) - guard with Fx/Effect/Stream handler
|
|
204
|
+
match<
|
|
205
|
+
Rt extends Route.Any,
|
|
206
|
+
G extends GuardInput<Route.Type<Rt>, any, any, any>,
|
|
207
|
+
B,
|
|
208
|
+
E2 = never,
|
|
209
|
+
R2 = never,
|
|
210
|
+
>(
|
|
211
|
+
route: Rt,
|
|
212
|
+
guard: G,
|
|
213
|
+
handler: Fx.Fx<B, E2, R2> | Effect.Effect<B, E2, R2> | Stream.Stream<B, E2, R2>,
|
|
214
|
+
): Matcher<A | B, E | E2 | GuardError<G>, R | R2 | GuardServices<G> | Scope.Scope>;
|
|
215
|
+
|
|
216
|
+
// Overload 7: match(route, guard, options) - route with guard and options object
|
|
217
|
+
match<
|
|
218
|
+
Rt extends Route.Any,
|
|
219
|
+
G extends GuardInput<Route.Type<Rt>, any, any, any>,
|
|
220
|
+
B,
|
|
221
|
+
E2 = never,
|
|
222
|
+
R2 = never,
|
|
223
|
+
D extends ReadonlyArray<AnyDependency> | undefined = undefined,
|
|
224
|
+
LB = B,
|
|
225
|
+
LE2 = never,
|
|
226
|
+
LR2 = never,
|
|
227
|
+
C extends CatchHandler<any, any, any, any> | undefined = undefined,
|
|
228
|
+
>(
|
|
229
|
+
route: Rt,
|
|
230
|
+
guard: G,
|
|
231
|
+
options: MatchHandlerOptions<GuardOutput<G>, B, E2, R2, D, LB, LE2, LR2, C>,
|
|
232
|
+
): Matcher<
|
|
233
|
+
A | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GuardError<G>, GuardServices<G>>["a"],
|
|
234
|
+
E | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GuardError<G>, GuardServices<G>>["e"],
|
|
235
|
+
| R
|
|
236
|
+
| ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GuardError<G>, GuardServices<G>>["r"]
|
|
237
|
+
| Scope.Scope
|
|
238
|
+
>;
|
|
239
|
+
|
|
240
|
+
// Overload 8: match(route, guard, value) - guard with value handler (last for 3-arg form)
|
|
241
|
+
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B>(
|
|
242
|
+
route: Rt,
|
|
243
|
+
guard: G,
|
|
244
|
+
handler: B,
|
|
245
|
+
): Matcher<A | B, E | GuardError<G>, R | GuardServices<G> | Scope.Scope>;
|
|
246
|
+
|
|
247
|
+
// Overload 9: match(fullOptions) - full options object including route
|
|
248
|
+
match<
|
|
249
|
+
Rt extends Route.Any,
|
|
250
|
+
B,
|
|
251
|
+
E2 = never,
|
|
252
|
+
R2 = never,
|
|
253
|
+
D extends ReadonlyArray<AnyDependency> | undefined = undefined,
|
|
254
|
+
LB = B,
|
|
255
|
+
LE2 = never,
|
|
256
|
+
LR2 = never,
|
|
257
|
+
C extends CatchHandler<any, any, any, any> | undefined = undefined,
|
|
258
|
+
>(
|
|
259
|
+
options: MatchOptions<Rt, B, E2, R2, D, LB, LE2, LR2, C>,
|
|
260
|
+
): Matcher<
|
|
261
|
+
A | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["a"],
|
|
262
|
+
E | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["e"],
|
|
263
|
+
R | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["r"] | Scope.Scope
|
|
264
|
+
>;
|
|
265
|
+
|
|
266
|
+
readonly prefix: <Rt extends Route.Any>(route: Rt) => Matcher<A, E, R>;
|
|
267
|
+
|
|
268
|
+
readonly provide: <Layers extends readonly [AnyLayer, ...AnyLayer[]]>(
|
|
269
|
+
...layers: Layers
|
|
270
|
+
) => Matcher<
|
|
271
|
+
A,
|
|
272
|
+
E | LayerError<Layers[number]>,
|
|
273
|
+
Exclude<R, LayerSuccess<Layers[number]>> | LayerServices<Layers[number]>
|
|
274
|
+
>;
|
|
275
|
+
|
|
276
|
+
readonly provideService: <Id, S>(
|
|
277
|
+
tag: ServiceMap.Service<Id, S>,
|
|
278
|
+
service: S,
|
|
279
|
+
) => Matcher<A, E, Exclude<R, Id>>;
|
|
280
|
+
|
|
281
|
+
readonly provideServices: <R2>(
|
|
282
|
+
services: ServiceMap.ServiceMap<R2>,
|
|
283
|
+
) => Matcher<A, E, Exclude<R, R2>>;
|
|
284
|
+
|
|
285
|
+
readonly catchCause: <B, E2, R2>(f: CatchHandler<E, B, E2, R2>) => Matcher<A | B, E2, R | R2>;
|
|
286
|
+
|
|
287
|
+
readonly catch: <B, E2, R2>(f: (e: E) => Fx.Fx<B, E2, R2>) => Matcher<A | B, E2, R | R2>;
|
|
288
|
+
|
|
289
|
+
readonly catchTag: <const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, B, E2, R2>(
|
|
290
|
+
tag: K,
|
|
291
|
+
f: (
|
|
292
|
+
e: ExtractTag<NoInfer<E>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
293
|
+
) => Fx.Fx<B, E2, R2>,
|
|
294
|
+
) => Matcher<
|
|
295
|
+
A | B,
|
|
296
|
+
E2 | ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
297
|
+
R | R2
|
|
298
|
+
>;
|
|
299
|
+
|
|
300
|
+
readonly layout: <B, E2, R2>(
|
|
301
|
+
layout: Layout<any, A, E, R, B, E2, R2>,
|
|
302
|
+
) => Matcher<B, E | E2, R | R2>;
|
|
303
|
+
|
|
304
|
+
/** Merge this matcher with one or more others. Combined matcher matches all routes; each matcher's layouts/provide apply only to its own routes. */
|
|
305
|
+
readonly merge: <const Others extends ReadonlyArray<Matcher.Any>>(
|
|
306
|
+
...others: Others
|
|
307
|
+
) => Matcher<
|
|
308
|
+
A | Matcher.MergeSuccess<Others>,
|
|
309
|
+
E | Matcher.MergeError<Others>,
|
|
310
|
+
R | Matcher.MergeServices<Others>
|
|
311
|
+
>;
|
|
107
312
|
}
|
|
108
313
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
export type
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
314
|
+
export declare namespace Matcher {
|
|
315
|
+
export type Any =
|
|
316
|
+
| Matcher<any, any, any>
|
|
317
|
+
| Matcher<any, never, any>
|
|
318
|
+
| Matcher<any, any, never>
|
|
319
|
+
| Matcher<any, never, never>;
|
|
320
|
+
export type Success<T> = [T] extends [Matcher<infer A, infer _E, infer _R>] ? A : never;
|
|
321
|
+
export type Error<T> = [T] extends [Matcher<infer _A, infer E, infer _R>] ? E : never;
|
|
322
|
+
export type Services<T> = [T] extends [Matcher<infer _A, infer _E, infer R>] ? R : never;
|
|
323
|
+
|
|
324
|
+
/** Union of Success types from each matcher in a tuple. */
|
|
325
|
+
export type MergeSuccess<Matchers extends ReadonlyArray<Matcher.Any>> = Success<Matchers[number]>;
|
|
326
|
+
/** Union of Error types from each matcher in a tuple. */
|
|
327
|
+
export type MergeError<Matchers extends ReadonlyArray<Matcher.Any>> = Error<Matchers[number]>;
|
|
328
|
+
/** Union of Services types from each matcher in a tuple. */
|
|
329
|
+
export type MergeServices<Matchers extends ReadonlyArray<Matcher.Any>> = Services<
|
|
330
|
+
Matchers[number]
|
|
331
|
+
>;
|
|
122
332
|
}
|
|
123
333
|
|
|
124
|
-
|
|
125
|
-
|
|
334
|
+
export type MatchHandler<Params, A, E, R> =
|
|
335
|
+
| Fx.Fx<A, E, R>
|
|
336
|
+
| Stream.Stream<A, E, R>
|
|
337
|
+
| Effect.Effect<A, E, R>
|
|
338
|
+
| A
|
|
339
|
+
| ((
|
|
340
|
+
params: RefSubject.RefSubject<Params>,
|
|
341
|
+
) => Fx.Fx<A, E, R> | Stream.Stream<A, E, R> | Effect.Effect<A, E, R> | A);
|
|
126
342
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
343
|
+
type MatchHandlerFn<Params, A, E, R> = (
|
|
344
|
+
params: RefSubject.RefSubject<Params>,
|
|
345
|
+
) => Fx.Fx<A, E, R> | Stream.Stream<A, E, R> | Effect.Effect<A, E, R> | A;
|
|
346
|
+
|
|
347
|
+
function isMatchHandlerFn<Params, A, E, R>(
|
|
348
|
+
handler: MatchHandler<Params, A, E, R>,
|
|
349
|
+
): handler is MatchHandlerFn<Params, A, E, R> {
|
|
350
|
+
return typeof handler === "function";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function isHandlerOptions(value: unknown): value is { readonly handler: unknown } {
|
|
354
|
+
return typeof value === "object" && value !== null && "handler" in value;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Monomorphic shape - all properties always present for V8 optimization
|
|
358
|
+
type ParsedMatch = {
|
|
359
|
+
readonly route: Route.Any;
|
|
360
|
+
readonly handler: unknown;
|
|
361
|
+
readonly guard: AnyGuard | undefined;
|
|
362
|
+
readonly layout: AnyLayout | undefined;
|
|
363
|
+
readonly catchFn: AnyCatch | undefined;
|
|
364
|
+
readonly dependencies: ReadonlyArray<AnyDependency> | undefined;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
function parseMatchArgs(args: [unknown, ...Array<unknown>]): ParsedMatch {
|
|
368
|
+
const [first, second, third] = args;
|
|
369
|
+
|
|
370
|
+
// Single arg: full options object (Overload 9)
|
|
371
|
+
if (second === undefined) {
|
|
372
|
+
const opts = first as MatchOptions<Route.Any, any, any, any, any, any, any, any, any>;
|
|
373
|
+
return {
|
|
374
|
+
route: opts.route,
|
|
375
|
+
handler: opts.handler,
|
|
376
|
+
guard: undefined,
|
|
377
|
+
layout: opts.layout as AnyLayout | undefined,
|
|
378
|
+
catchFn: opts.catch as AnyCatch | undefined,
|
|
379
|
+
dependencies: opts.dependencies as ReadonlyArray<AnyDependency> | undefined,
|
|
380
|
+
};
|
|
133
381
|
}
|
|
134
382
|
|
|
135
|
-
|
|
136
|
-
|
|
383
|
+
// Two args
|
|
384
|
+
if (third === undefined) {
|
|
385
|
+
if (isHandlerOptions(second)) {
|
|
386
|
+
// Overload 3: match(route, options)
|
|
387
|
+
const opts = second as MatchHandlerOptions<any, any, any, any, any, any, any, any, any>;
|
|
388
|
+
return {
|
|
389
|
+
route: first as Route.Any,
|
|
390
|
+
handler: opts.handler,
|
|
391
|
+
guard: undefined,
|
|
392
|
+
layout: opts.layout as AnyLayout | undefined,
|
|
393
|
+
catchFn: opts.catch as AnyCatch | undefined,
|
|
394
|
+
dependencies: opts.dependencies as ReadonlyArray<AnyDependency> | undefined,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
// Overloads 1, 2, 4: match(route, handler)
|
|
398
|
+
return {
|
|
399
|
+
route: first as Route.Any,
|
|
400
|
+
handler: second,
|
|
401
|
+
guard: undefined,
|
|
402
|
+
layout: undefined,
|
|
403
|
+
catchFn: undefined,
|
|
404
|
+
dependencies: undefined,
|
|
405
|
+
};
|
|
137
406
|
}
|
|
138
407
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return
|
|
408
|
+
// Three args
|
|
409
|
+
if (isHandlerOptions(third)) {
|
|
410
|
+
// Overload 7: match(route, guard, options)
|
|
411
|
+
const opts = third as MatchHandlerOptions<any, any, any, any, any, any, any, any, any>;
|
|
412
|
+
return {
|
|
413
|
+
route: first as Route.Any,
|
|
414
|
+
handler: opts.handler,
|
|
415
|
+
guard: second as AnyGuard,
|
|
416
|
+
layout: opts.layout as AnyLayout | undefined,
|
|
417
|
+
catchFn: opts.catch as AnyCatch | undefined,
|
|
418
|
+
dependencies: opts.dependencies as ReadonlyArray<AnyDependency> | undefined,
|
|
419
|
+
};
|
|
144
420
|
}
|
|
145
421
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
422
|
+
// Overloads 5, 6, 8: match(route, guard, handler)
|
|
423
|
+
return {
|
|
424
|
+
route: first as Route.Any,
|
|
425
|
+
handler: third,
|
|
426
|
+
guard: second as AnyGuard,
|
|
427
|
+
layout: undefined,
|
|
428
|
+
catchFn: undefined,
|
|
429
|
+
dependencies: undefined,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
class MatcherImpl<A, E, R> implements Matcher<A, E, R> {
|
|
434
|
+
readonly cases: ReadonlyArray<MatchAst>;
|
|
435
|
+
constructor(cases: ReadonlyArray<MatchAst>) {
|
|
436
|
+
this.cases = cases;
|
|
437
|
+
this.match = this.match.bind(this);
|
|
438
|
+
this.catch = this.catch.bind(this);
|
|
439
|
+
this.catchTag = this.catchTag.bind(this);
|
|
440
|
+
this.layout = this.layout.bind(this);
|
|
441
|
+
this.provide = this.provide.bind(this);
|
|
442
|
+
this.provideService = this.provideService.bind(this);
|
|
151
443
|
}
|
|
152
444
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
445
|
+
// Implementation overloads for type inference - use simplified return types
|
|
446
|
+
match<Rt extends Route.Any, B, E2 = never, R2 = never>(
|
|
447
|
+
route: Rt,
|
|
448
|
+
handler: (params: RefSubject.RefSubject<Route.Type<Rt>>) => MatchHandlerReturnValue<B, E2, R2>,
|
|
449
|
+
): Matcher<A | B, E | E2, R | R2 | Scope.Scope>;
|
|
450
|
+
match<Rt extends Route.Any, B, E2 = never, R2 = never>(
|
|
451
|
+
route: Rt,
|
|
452
|
+
handler: Fx.Fx<B, E2, R2> | Effect.Effect<B, E2, R2> | Stream.Stream<B, E2, R2>,
|
|
453
|
+
): Matcher<A | B, E | E2, R | R2 | Scope.Scope>;
|
|
454
|
+
match<
|
|
455
|
+
Rt extends Route.Any,
|
|
456
|
+
B,
|
|
457
|
+
E2 = never,
|
|
458
|
+
R2 = never,
|
|
459
|
+
D extends ReadonlyArray<AnyDependency> | undefined = undefined,
|
|
460
|
+
LB = B,
|
|
461
|
+
LE2 = never,
|
|
462
|
+
LR2 = never,
|
|
463
|
+
C extends CatchHandler<any, any, any, any> | undefined = undefined,
|
|
464
|
+
>(
|
|
465
|
+
route: Rt,
|
|
466
|
+
options: MatchHandlerOptions<Route.Type<Rt>, B, E2, R2, D, LB, LE2, LR2, C>,
|
|
467
|
+
): Matcher<
|
|
468
|
+
A | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["a"],
|
|
469
|
+
E | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["e"],
|
|
470
|
+
R | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["r"] | Scope.Scope
|
|
471
|
+
>;
|
|
472
|
+
match<Rt extends Route.Any, B>(route: Rt, handler: B): Matcher<A | B, E, R | Scope.Scope>;
|
|
473
|
+
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B, E2, R2>(
|
|
474
|
+
route: Rt,
|
|
475
|
+
guard: G,
|
|
476
|
+
handler: (params: RefSubject.RefSubject<GuardOutput<G>>) => MatchHandlerReturnValue<B, E2, R2>,
|
|
477
|
+
): Matcher<A | B, E | E2 | GuardError<G>, R | R2 | GuardServices<G> | Scope.Scope>;
|
|
478
|
+
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B, E2, R2>(
|
|
479
|
+
route: Rt,
|
|
480
|
+
guard: G,
|
|
481
|
+
handler: Fx.Fx<B, E2, R2> | Effect.Effect<B, E2, R2> | Stream.Stream<B, E2, R2>,
|
|
482
|
+
): Matcher<A | B, E | E2 | GuardError<G>, R | R2 | GuardServices<G> | Scope.Scope>;
|
|
483
|
+
match<
|
|
484
|
+
Rt extends Route.Any,
|
|
485
|
+
G extends GuardInput<Route.Type<Rt>, any, any, any>,
|
|
486
|
+
B,
|
|
487
|
+
E2 = never,
|
|
488
|
+
R2 = never,
|
|
489
|
+
D extends ReadonlyArray<AnyDependency> | undefined = undefined,
|
|
490
|
+
LB = B,
|
|
491
|
+
LE2 = never,
|
|
492
|
+
LR2 = never,
|
|
493
|
+
C extends CatchHandler<any, any, any, any> | undefined = undefined,
|
|
494
|
+
>(
|
|
495
|
+
route: Rt,
|
|
496
|
+
guard: G,
|
|
497
|
+
options: MatchHandlerOptions<GuardOutput<G>, B, E2, R2, D, LB, LE2, LR2, C>,
|
|
498
|
+
): Matcher<
|
|
499
|
+
A | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GuardError<G>, GuardServices<G>>["a"],
|
|
500
|
+
E | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GuardError<G>, GuardServices<G>>["e"],
|
|
501
|
+
| R
|
|
502
|
+
| ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, GuardError<G>, GuardServices<G>>["r"]
|
|
503
|
+
| Scope.Scope
|
|
504
|
+
>;
|
|
505
|
+
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B>(
|
|
506
|
+
route: Rt,
|
|
507
|
+
guard: G,
|
|
508
|
+
handler: B,
|
|
509
|
+
): Matcher<A | B, E | GuardError<G>, R | GuardServices<G> | Scope.Scope>;
|
|
510
|
+
match<
|
|
511
|
+
Rt extends Route.Any,
|
|
512
|
+
B,
|
|
513
|
+
E2 = never,
|
|
514
|
+
R2 = never,
|
|
515
|
+
D extends ReadonlyArray<AnyDependency> | undefined = undefined,
|
|
516
|
+
LB = B,
|
|
517
|
+
LE2 = never,
|
|
518
|
+
LR2 = never,
|
|
519
|
+
C extends CatchHandler<any, any, any, any> | undefined = undefined,
|
|
520
|
+
>(
|
|
521
|
+
options: MatchOptions<Rt, B, E2, R2, D, LB, LE2, LR2, C>,
|
|
522
|
+
): Matcher<
|
|
523
|
+
A | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["a"],
|
|
524
|
+
E | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["e"],
|
|
525
|
+
R | ComputeMatchResult<E2, R2, D, LB, LE2, LR2, C, never, never>["r"] | Scope.Scope
|
|
526
|
+
>;
|
|
527
|
+
match(...args: [unknown, ...Array<unknown>]): Matcher<any, any, any> {
|
|
528
|
+
const parsed = parseMatchArgs(args);
|
|
529
|
+
const normalizedGuard =
|
|
530
|
+
parsed.guard !== undefined
|
|
531
|
+
? getGuard(parsed.guard as GuardInput<any, any, any, any>)
|
|
532
|
+
: defaultGuard();
|
|
533
|
+
|
|
534
|
+
const routeAst = AST.route(
|
|
535
|
+
parsed.route.ast,
|
|
536
|
+
parsed.handler as MatchHandler<any, any, any, any>,
|
|
537
|
+
normalizedGuard,
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
let matches: ReadonlyArray<MatchAst> = [routeAst];
|
|
541
|
+
if (parsed.layout !== undefined) {
|
|
542
|
+
matches = [AST.layout(matches, parsed.layout)];
|
|
543
|
+
}
|
|
544
|
+
if (parsed.catchFn !== undefined) {
|
|
545
|
+
matches = [AST.catchCause(matches, parsed.catchFn)];
|
|
546
|
+
}
|
|
547
|
+
if (parsed.dependencies !== undefined && parsed.dependencies.length > 0) {
|
|
548
|
+
matches = [AST.layer(matches, normalizeDependencies(parsed.dependencies))];
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return new MatcherImpl([...this.cases, ...matches]);
|
|
158
552
|
}
|
|
159
553
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
554
|
+
prefix<Rt extends Route.Any>(route: Rt): Matcher<A, E, R> {
|
|
555
|
+
return new MatcherImpl<A, E, R>([AST.prefixed(this.cases, route.ast)]);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
provide<Layers extends readonly [AnyLayer, ...AnyLayer[]]>(
|
|
559
|
+
...layers: Layers
|
|
560
|
+
): Matcher<
|
|
561
|
+
A,
|
|
562
|
+
E | LayerError<Layers[number]>,
|
|
563
|
+
Exclude<R, LayerSuccess<Layers[number]>> | LayerServices<Layers[number]>
|
|
564
|
+
> {
|
|
565
|
+
return new MatcherImpl([AST.layer(this.cases, layers)]) as Matcher<
|
|
566
|
+
A,
|
|
567
|
+
E | LayerError<Layers[number]>,
|
|
568
|
+
Exclude<R, LayerSuccess<Layers[number]>> | LayerServices<Layers[number]>
|
|
569
|
+
>;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
provideService<Id, S>(tag: ServiceMap.Service<Id, S>, service: S): Matcher<A, E, Exclude<R, Id>> {
|
|
573
|
+
return this.provideServices(ServiceMap.make(tag, service));
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
provideServices<R2>(services: ServiceMap.ServiceMap<R2>): Matcher<A, E, Exclude<R, R2>> {
|
|
577
|
+
return this.provide(Layer.succeedServices(services));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
catchCause<B, E2, R2>(f: CatchHandler<E, B, E2, R2>): Matcher<A | B, E2, R | R2> {
|
|
581
|
+
return new MatcherImpl<A | B, E2, R | R2>([AST.catchCause(this.cases, f as AnyCatch)]);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
catch<B, E2, R2>(f: (e: E) => Fx.Fx<B, E2, R2>): Matcher<A | B, E2, R | R2> {
|
|
585
|
+
return this.catchCause((causeRef) =>
|
|
586
|
+
unwrap(
|
|
587
|
+
Effect.gen(function* () {
|
|
588
|
+
const cause = yield* causeRef;
|
|
589
|
+
const result = Cause.findFail(cause);
|
|
590
|
+
if (Result.isFailure(result)) {
|
|
591
|
+
return fromEffect(Effect.failCause(result.failure));
|
|
592
|
+
}
|
|
593
|
+
return f(result.success.error);
|
|
594
|
+
}),
|
|
595
|
+
),
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
catchTag<const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, B, E2, R2>(
|
|
600
|
+
tag: K,
|
|
601
|
+
f: (
|
|
602
|
+
e: ExtractTag<NoInfer<E>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
603
|
+
) => Fx.Fx<B, E2, R2>,
|
|
604
|
+
): Matcher<
|
|
605
|
+
A | B,
|
|
606
|
+
E2 | ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
607
|
+
R | R2
|
|
608
|
+
> {
|
|
609
|
+
const rethrow = (cause: Cause.Cause<E>) =>
|
|
610
|
+
fromEffect(Effect.failCause(cause)) as Fx.Fx<
|
|
611
|
+
B,
|
|
612
|
+
E2 | ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
613
|
+
R2
|
|
614
|
+
>;
|
|
615
|
+
|
|
616
|
+
return new MatcherImpl<
|
|
617
|
+
A | B,
|
|
618
|
+
E2 | ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
619
|
+
R | R2
|
|
620
|
+
>([
|
|
621
|
+
AST.catchCause(this.cases, (causeRef) =>
|
|
622
|
+
unwrap(
|
|
623
|
+
Effect.gen(function* () {
|
|
624
|
+
const cause = yield* causeRef;
|
|
625
|
+
const result = Cause.findFail(cause);
|
|
626
|
+
if (Result.isFailure(result)) {
|
|
627
|
+
return rethrow(cause);
|
|
628
|
+
}
|
|
629
|
+
if (matchesTag(tag, result.success.error)) {
|
|
630
|
+
return f(result.success.error);
|
|
631
|
+
}
|
|
632
|
+
return rethrow(cause);
|
|
633
|
+
}),
|
|
634
|
+
),
|
|
635
|
+
),
|
|
636
|
+
]);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
layout<B, E2, R2>(layout: Layout<any, A, E, R, B, E2, R2>): Matcher<B, E | E2, R | R2> {
|
|
640
|
+
return new MatcherImpl<B, E | E2, R | R2>([
|
|
641
|
+
AST.layout(this.cases, layout as AnyLayout),
|
|
642
|
+
]) as Matcher<B, E | E2, R | R2>;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
merge<const Others extends ReadonlyArray<Matcher.Any>>(
|
|
646
|
+
...others: Others
|
|
647
|
+
): Matcher<
|
|
648
|
+
A | Matcher.MergeSuccess<Others>,
|
|
649
|
+
E | Matcher.MergeError<Others>,
|
|
650
|
+
R | Matcher.MergeServices<Others>
|
|
651
|
+
> {
|
|
652
|
+
const allCases = [...this.cases, ...others.flatMap((m) => m.cases)];
|
|
653
|
+
return new MatcherImpl(allCases) as Matcher<
|
|
654
|
+
A | Matcher.MergeSuccess<Others>,
|
|
655
|
+
E | Matcher.MergeError<Others>,
|
|
656
|
+
R | Matcher.MergeServices<Others>
|
|
657
|
+
>;
|
|
165
658
|
}
|
|
166
659
|
|
|
167
660
|
pipe() {
|
|
168
|
-
return pipeArguments(this, arguments)
|
|
661
|
+
return pipeArguments(this, arguments);
|
|
169
662
|
}
|
|
170
663
|
}
|
|
171
664
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
): RouteMatcher<Matches> {
|
|
178
|
-
return new RouteMatcherImpl(matches)
|
|
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);
|
|
179
670
|
}
|
|
180
671
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
export {
|
|
189
|
-
/**
|
|
190
|
-
* @since 1.0.0
|
|
191
|
-
*/
|
|
192
|
-
effect,
|
|
193
|
-
/**
|
|
194
|
-
* @since 1.0.0
|
|
195
|
-
*/
|
|
196
|
-
match,
|
|
197
|
-
/**
|
|
198
|
-
* @since 1.0.0
|
|
199
|
-
*/
|
|
200
|
-
switch_ as switch,
|
|
201
|
-
/**
|
|
202
|
-
* @since 1.0.0
|
|
203
|
-
*/
|
|
204
|
-
to
|
|
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);
|
|
205
679
|
}
|
|
206
680
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
*/
|
|
210
|
-
export const catchRedirectError = <A, E, R>(
|
|
211
|
-
fx: Fx.Fx<A, E | RedirectError, R>
|
|
212
|
-
): Fx.Fx<A, Exclude<E, RedirectError>, R | Scope.Scope | Navigation> =>
|
|
213
|
-
Fx.filterMapErrorEffect(
|
|
214
|
-
fx,
|
|
215
|
-
Unify.unify((_) =>
|
|
216
|
-
isRedirectError(_)
|
|
217
|
-
? Effect.as(Effect.forkScoped(Effect.ignoreLogged(navigate(_.path, _.options))), Option.none())
|
|
218
|
-
: Effect.succeedSome(_ as Exclude<typeof _, RedirectError>)
|
|
219
|
-
)
|
|
220
|
-
)
|
|
681
|
+
export const empty: Matcher<never> = new MatcherImpl([]);
|
|
682
|
+
export const match = empty.match.bind(empty);
|
|
221
683
|
|
|
222
684
|
/**
|
|
223
|
-
*
|
|
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.
|
|
224
687
|
*/
|
|
225
|
-
export const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
Scope.Scope | Navigation | R | RouteMatch.RouteMatch.Context<Matches>
|
|
232
|
-
>
|
|
233
|
-
|
|
234
|
-
<Matches extends RouteMatch.RouteMatch.Any, A, E, R>(
|
|
235
|
-
router: RouteMatcher<Matches>,
|
|
236
|
-
onNotFound: Fx.Fx<A, E, R>
|
|
237
|
-
): Fx.Fx<
|
|
238
|
-
A | RouteMatch.RouteMatch.Success<Matches>,
|
|
239
|
-
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
240
|
-
Scope.Scope | Navigation | R | RouteMatch.RouteMatch.Context<Matches>
|
|
241
|
-
>
|
|
242
|
-
} = dual(2, function notFound<Matches extends RouteMatch.RouteMatch.Any, A, E, R>(
|
|
243
|
-
router: RouteMatcher<Matches>,
|
|
244
|
-
onNotFound: Fx.Fx<A, E, R>
|
|
245
|
-
): Fx.Fx<
|
|
246
|
-
A | RouteMatch.RouteMatch.Success<Matches>,
|
|
247
|
-
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
248
|
-
R | Navigation | RouteMatch.RouteMatch.Context<Matches> | Scope.Scope
|
|
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>
|
|
249
694
|
> {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
+
>;
|
|
259
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
|
+
}) {}
|
|
260
735
|
|
|
261
|
-
|
|
262
|
-
|
|
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
|
+
}) {}
|
|
263
748
|
|
|
264
749
|
/**
|
|
265
|
-
* @
|
|
750
|
+
* @internal
|
|
266
751
|
*/
|
|
267
|
-
export
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
): Fx.Fx<
|
|
280
|
-
A | RouteMatch.RouteMatch.Success<Matches>,
|
|
281
|
-
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
282
|
-
R | Navigation | RouteMatch.RouteMatch.Context<Matches> | Scope.Scope
|
|
283
|
-
>
|
|
284
|
-
} = dual(2, function notFoundWith<Matches extends RouteMatch.RouteMatch.Any, A, E, R>(
|
|
285
|
-
router: RouteMatcher<Matches>,
|
|
286
|
-
onNotFound: Effect.Effect<A, E, R>
|
|
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,
|
|
287
764
|
): Fx.Fx<
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
765
|
+
Matcher.Success<M>,
|
|
766
|
+
Matcher.Error<M> | RouteNotFound | RouteDecodeError | RouteGuardError,
|
|
767
|
+
Matcher.Services<M> | Router | CurrentRoute | Scope.Scope
|
|
291
768
|
> {
|
|
292
|
-
return
|
|
293
|
-
|
|
769
|
+
return unwrap(
|
|
770
|
+
Effect.gen(function* () {
|
|
771
|
+
const fiberId = yield* Effect.fiberId;
|
|
772
|
+
const rootScope = yield* Effect.scope;
|
|
773
|
+
const current = yield* CurrentRoute;
|
|
774
|
+
const prefixed = matcher.prefix(current.route);
|
|
775
|
+
const entries = compile(prefixed.cases);
|
|
776
|
+
const router = findMyWay.make<ReadonlyArray<CompiledEntry>>({
|
|
777
|
+
ignoreTrailingSlash: true,
|
|
778
|
+
caseSensitive: false,
|
|
779
|
+
});
|
|
780
|
+
const handlersByPath = new Map<string, Array<CompiledEntry>>();
|
|
781
|
+
const memoMap = yield* Layer.makeMemoMap;
|
|
782
|
+
const layerManager = makeLayerManager(memoMap, rootScope, fiberId);
|
|
783
|
+
const layoutManager = makeLayoutManager(rootScope, fiberId);
|
|
784
|
+
const catchManager = makeCatchManager(rootScope, fiberId);
|
|
785
|
+
|
|
786
|
+
for (const entry of entries) {
|
|
787
|
+
const path = entry.route.path;
|
|
788
|
+
const existing = handlersByPath.get(path);
|
|
789
|
+
if (existing !== undefined) {
|
|
790
|
+
existing.push(entry);
|
|
791
|
+
} else {
|
|
792
|
+
const list: Array<CompiledEntry> = [entry];
|
|
793
|
+
handlersByPath.set(path, list);
|
|
794
|
+
router.all(path, list);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
let currentState: {
|
|
799
|
+
entry: CompiledEntry;
|
|
800
|
+
params: RefSubject.RefSubject<any>;
|
|
801
|
+
fx: Fx.Fx<Matcher.Success<M>, Matcher.Error<M>, Matcher.Services<M> | Scope.Scope | Router>;
|
|
802
|
+
scope: Scope.Closeable;
|
|
803
|
+
} | null = null;
|
|
804
|
+
|
|
805
|
+
return CurrentPath.pipe(
|
|
806
|
+
mapEffect(
|
|
807
|
+
Effect.fn(function* (path) {
|
|
808
|
+
const result = router.find("GET", path);
|
|
809
|
+
if (result === undefined) return yield* new RouteNotFound({ path });
|
|
810
|
+
|
|
811
|
+
const input = { ...result.params, ...result.searchParams };
|
|
812
|
+
const entries = result.handler;
|
|
813
|
+
const guardCauses: Array<Cause.Cause<any>> = [];
|
|
814
|
+
let matchedEntry: CompiledEntry | undefined = undefined;
|
|
815
|
+
let matchedParams: any = undefined;
|
|
816
|
+
let matchedPrepared:
|
|
817
|
+
| {
|
|
818
|
+
services: AnyServiceMap;
|
|
819
|
+
commit: Effect.Effect<void>;
|
|
820
|
+
rollback: Effect.Effect<void>;
|
|
821
|
+
}
|
|
822
|
+
| undefined = undefined;
|
|
823
|
+
|
|
824
|
+
for (const entry of entries) {
|
|
825
|
+
const params = yield* Effect.mapErrorEager(
|
|
826
|
+
entry.decode(input),
|
|
827
|
+
(cause) =>
|
|
828
|
+
new RouteDecodeError({ path, cause: makeFormatterDefault()(cause.issue) }),
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
const prepared = yield* layerManager.prepare(entry.layers);
|
|
832
|
+
const guardExit = yield* entry
|
|
833
|
+
.guard(params)
|
|
834
|
+
.pipe(Effect.provideServices(prepared.services), Effect.exit);
|
|
835
|
+
|
|
836
|
+
if (Exit.isFailure(guardExit)) {
|
|
837
|
+
guardCauses.push(guardExit.cause);
|
|
838
|
+
yield* prepared.rollback;
|
|
839
|
+
continue;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
if (Option.isNone(guardExit.value)) {
|
|
843
|
+
yield* prepared.rollback;
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
matchedEntry = entry;
|
|
848
|
+
matchedParams = guardExit.value.value;
|
|
849
|
+
matchedPrepared = prepared;
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if (matchedEntry === undefined || matchedPrepared === undefined) {
|
|
854
|
+
return yield* new RouteGuardError({ path, causes: guardCauses });
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
yield* matchedPrepared.commit;
|
|
858
|
+
|
|
859
|
+
if (currentState !== null && currentState.entry === matchedEntry) {
|
|
860
|
+
yield* RefSubject.set(currentState.params, matchedParams);
|
|
861
|
+
yield* layoutManager.updateParams(matchedEntry.layouts, matchedParams);
|
|
862
|
+
return currentState.fx;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (currentState !== null) {
|
|
866
|
+
yield* Scope.close(currentState.scope, interrupt(fiberId));
|
|
867
|
+
currentState = null;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const scope = yield* Scope.fork(rootScope);
|
|
871
|
+
const paramsRef = yield* RefSubject.make(matchedParams).pipe(Scope.provide(scope));
|
|
872
|
+
|
|
873
|
+
const preparedServices = matchedPrepared.services as ServiceMap.ServiceMap<any>;
|
|
874
|
+
const handlerServices = ServiceMap.merge(
|
|
875
|
+
preparedServices,
|
|
876
|
+
ServiceMap.make(Scope.Scope, scope),
|
|
877
|
+
);
|
|
878
|
+
|
|
879
|
+
const handlerFx = matchedEntry
|
|
880
|
+
.handler(paramsRef)
|
|
881
|
+
.pipe(provideServices(handlerServices));
|
|
882
|
+
const withLayouts = yield* layoutManager.apply(
|
|
883
|
+
matchedEntry.layouts,
|
|
884
|
+
matchedParams,
|
|
885
|
+
handlerFx,
|
|
886
|
+
preparedServices,
|
|
887
|
+
);
|
|
888
|
+
const withCatches = yield* catchManager.apply(
|
|
889
|
+
matchedEntry.catches,
|
|
890
|
+
withLayouts,
|
|
891
|
+
preparedServices,
|
|
892
|
+
);
|
|
893
|
+
const fx = withCatches;
|
|
894
|
+
|
|
895
|
+
currentState = {
|
|
896
|
+
entry: matchedEntry,
|
|
897
|
+
params: paramsRef,
|
|
898
|
+
scope,
|
|
899
|
+
fx,
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
return currentState.fx;
|
|
903
|
+
}),
|
|
904
|
+
),
|
|
905
|
+
skipRepeats,
|
|
906
|
+
switchMap(identity),
|
|
907
|
+
);
|
|
908
|
+
}),
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
type InputSucces<T> = [Matcher.Success<T> | Fx.Fx.Success<T>] extends [infer A] ? A : never;
|
|
913
|
+
type InputError<T> = [Matcher.Error<T> | Fx.Fx.Error<T>] extends [infer E] ? E : never;
|
|
914
|
+
type InputServices<T> = [Matcher.Services<T> | Fx.Fx.Services<T>] extends [infer R] ? R : never;
|
|
915
|
+
|
|
916
|
+
export const catchCause: {
|
|
917
|
+
<I extends Fx.Fx.Any | Matcher.Any, B, E2 = never, R2 = never>(
|
|
918
|
+
f: (
|
|
919
|
+
cause: RefSubject.RefSubject<
|
|
920
|
+
Cause.Cause<InputError<I> | RouteNotFound | RouteDecodeError | RouteGuardError>
|
|
921
|
+
>,
|
|
922
|
+
) => Fx.Fx<B, E2, R2>,
|
|
923
|
+
): (input: I) => Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
924
|
+
|
|
925
|
+
<I extends Fx.Fx.Any | Matcher.Any, B, E2 = never, R2 = never>(
|
|
926
|
+
input: I,
|
|
927
|
+
f: (
|
|
928
|
+
cause: RefSubject.RefSubject<
|
|
929
|
+
Cause.Cause<InputError<I> | RouteNotFound | RouteDecodeError | RouteGuardError>
|
|
930
|
+
>,
|
|
931
|
+
) => Fx.Fx<B, E2, R2>,
|
|
932
|
+
): Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
933
|
+
} = dual(
|
|
934
|
+
2,
|
|
935
|
+
<A, E, R, B, E2, R2>(
|
|
936
|
+
input: Fx.Fx<A, E, R> | Matcher<A, E, R>,
|
|
937
|
+
f: (
|
|
938
|
+
cause: RefSubject.RefSubject<
|
|
939
|
+
Cause.Cause<E | RouteNotFound | RouteDecodeError | RouteGuardError>
|
|
940
|
+
>,
|
|
941
|
+
) => Fx.Fx<B, E2, R2>,
|
|
942
|
+
): Fx.Fx<A | B, E2, R | R2 | Router | Scope.Scope> => {
|
|
943
|
+
const eff = Effect.gen(function* () {
|
|
944
|
+
const fiberId = yield* Effect.fiberId;
|
|
945
|
+
const rootScope = yield* Effect.scope;
|
|
946
|
+
const fx = isFx(input) ? input : run(input);
|
|
947
|
+
const manager = makeCatchManager(rootScope, fiberId);
|
|
948
|
+
const result = yield* manager.apply(
|
|
949
|
+
[f],
|
|
950
|
+
fx,
|
|
951
|
+
ServiceMap.empty() as ServiceMap.ServiceMap<any>,
|
|
952
|
+
);
|
|
953
|
+
return result as Fx.Fx<A | B, E2, R | R2 | Router | Scope.Scope>;
|
|
954
|
+
});
|
|
955
|
+
return unwrap(eff);
|
|
956
|
+
},
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
export const catch_: {
|
|
960
|
+
<I extends Fx.Fx.Any | Matcher.Any, B, E2, R2>(
|
|
961
|
+
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
962
|
+
): (input: I) => Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
963
|
+
} = dual(
|
|
964
|
+
2,
|
|
965
|
+
<I extends Fx.Fx.Any | Matcher.Any, B, E2, R2>(
|
|
966
|
+
input: I,
|
|
967
|
+
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
968
|
+
): Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope> =>
|
|
969
|
+
catchCause(input, (causeRef) =>
|
|
970
|
+
unwrap(
|
|
971
|
+
Effect.gen(function* () {
|
|
972
|
+
const cause = yield* causeRef;
|
|
973
|
+
const result = Cause.findFail(cause);
|
|
974
|
+
if (Result.isFailure(result)) {
|
|
975
|
+
return fromEffect(Effect.failCause(result.failure));
|
|
976
|
+
}
|
|
977
|
+
return f(result.success.error as InputError<I>);
|
|
978
|
+
}),
|
|
979
|
+
),
|
|
980
|
+
),
|
|
981
|
+
);
|
|
982
|
+
|
|
983
|
+
export { catch_ as catch };
|
|
984
|
+
|
|
985
|
+
export const catchTag: {
|
|
986
|
+
<
|
|
987
|
+
I extends Fx.Fx.Any | Matcher.Any,
|
|
988
|
+
const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>,
|
|
989
|
+
E,
|
|
990
|
+
B,
|
|
991
|
+
E2,
|
|
992
|
+
R2,
|
|
993
|
+
>(
|
|
994
|
+
k: K,
|
|
995
|
+
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
996
|
+
): (input: I) => Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
997
|
+
|
|
998
|
+
<
|
|
999
|
+
I extends Fx.Fx.Any | Matcher.Any,
|
|
1000
|
+
const K extends Tags<InputError<I>> | Arr.NonEmptyReadonlyArray<Tags<InputError<I>>>,
|
|
1001
|
+
B,
|
|
1002
|
+
E2,
|
|
1003
|
+
R2,
|
|
1004
|
+
>(
|
|
1005
|
+
input: I,
|
|
1006
|
+
k: K,
|
|
1007
|
+
f: (
|
|
1008
|
+
e: ExtractTag<InputError<I>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
1009
|
+
) => Fx.Fx<B, E2, R2>,
|
|
1010
|
+
): Fx.Fx<
|
|
1011
|
+
InputSucces<I> | B,
|
|
1012
|
+
E2 | ExcludeTag<InputError<I>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
1013
|
+
InputServices<I> | R2 | Router | Scope.Scope
|
|
1014
|
+
>;
|
|
1015
|
+
} = dual(
|
|
1016
|
+
3,
|
|
1017
|
+
<
|
|
1018
|
+
I extends Fx.Fx.Any | Matcher.Any,
|
|
1019
|
+
const K extends Tags<InputError<I>> | Arr.NonEmptyReadonlyArray<Tags<InputError<I>>>,
|
|
1020
|
+
B,
|
|
1021
|
+
E2,
|
|
1022
|
+
R2,
|
|
1023
|
+
>(
|
|
1024
|
+
input: I,
|
|
1025
|
+
k: K,
|
|
1026
|
+
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
1027
|
+
): Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope> =>
|
|
1028
|
+
catchCause(input, (causeRef) =>
|
|
1029
|
+
unwrap(
|
|
1030
|
+
Effect.gen(function* () {
|
|
1031
|
+
const cause = yield* causeRef;
|
|
1032
|
+
const result = Cause.findFail(cause);
|
|
1033
|
+
if (Result.isFailure(result)) {
|
|
1034
|
+
return fromEffect(Effect.failCause(result.failure));
|
|
1035
|
+
}
|
|
1036
|
+
if (matchesTag(k, result.success.error)) {
|
|
1037
|
+
return f(
|
|
1038
|
+
result.success.error as ExtractTag<
|
|
1039
|
+
InputError<I>,
|
|
1040
|
+
K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K
|
|
1041
|
+
>,
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
return fromEffect(Effect.fail(result.success.error as E2));
|
|
1045
|
+
}),
|
|
1046
|
+
),
|
|
1047
|
+
),
|
|
1048
|
+
);
|
|
1049
|
+
|
|
1050
|
+
export const redirectTo =
|
|
1051
|
+
(path: string) =>
|
|
1052
|
+
<I extends Fx.Fx.Any | Matcher.Any>(
|
|
1053
|
+
input: I,
|
|
1054
|
+
): Fx.Fx<InputSucces<I>, never, Router | Scope.Scope | InputServices<I>> =>
|
|
1055
|
+
catchCause(input, (_) =>
|
|
1056
|
+
Navigation.navigate(path).pipe(
|
|
1057
|
+
Effect.matchCause({
|
|
1058
|
+
onFailure: () => never,
|
|
1059
|
+
onSuccess: () => never,
|
|
1060
|
+
}),
|
|
1061
|
+
unwrap,
|
|
1062
|
+
),
|
|
1063
|
+
);
|
|
1064
|
+
|
|
1065
|
+
const hasTag = (u: unknown): u is { readonly _tag: string } =>
|
|
1066
|
+
typeof u === "object" &&
|
|
1067
|
+
u !== null &&
|
|
1068
|
+
"_tag" in u &&
|
|
1069
|
+
typeof (u as Record<string, unknown>)["_tag"] === "string";
|
|
1070
|
+
|
|
1071
|
+
const matchesTag = <E, K extends string>(
|
|
1072
|
+
tag: K | Arr.NonEmptyReadonlyArray<K>,
|
|
1073
|
+
error: E,
|
|
1074
|
+
): error is ExtractTag<E, K> => {
|
|
1075
|
+
if (!hasTag(error)) return false;
|
|
1076
|
+
if (typeof tag === "string") return error._tag === tag;
|
|
1077
|
+
return tag.some((t) => t === error._tag);
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
function isServiceMap(dep: AnyDependency): dep is AnyServiceMap {
|
|
1081
|
+
return !Layer.isLayer(dep);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function toSingleLayer(dep: AnyDependency): AnyLayer {
|
|
1085
|
+
if (isServiceMap(dep)) return Layer.succeedServices(dep);
|
|
1086
|
+
return dep;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
function normalizeDependencies(
|
|
1090
|
+
dependencies: ReadonlyArray<AnyDependency>,
|
|
1091
|
+
): ReadonlyArray<AnyLayer> {
|
|
1092
|
+
return dependencies.map(toSingleLayer);
|
|
1093
|
+
}
|
|
294
1094
|
|
|
295
1095
|
/**
|
|
296
|
-
*
|
|
1096
|
+
* Normalize dependency input (ServiceMap | Layer | Array of either) into a single Layer.
|
|
1097
|
+
* Use with `.provide(normalizeDependencyInput(deps))`.
|
|
297
1098
|
*/
|
|
298
|
-
export function
|
|
299
|
-
|
|
300
|
-
):
|
|
301
|
-
|
|
1099
|
+
export function normalizeDependencyInput(
|
|
1100
|
+
input: AnyDependency | ReadonlyArray<AnyDependency>,
|
|
1101
|
+
): AnyLayer {
|
|
1102
|
+
const arr = Array.isArray(input) ? input : [input];
|
|
1103
|
+
const layers = normalizeDependencies(arr);
|
|
1104
|
+
return mergeLayers(layers);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
function getGuard<I, O, E, R>(guard: GuardInput<I, O, E, R>): GuardType<I, O, E, R> {
|
|
1108
|
+
return "asGuard" in guard ? guard.asGuard() : guard;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function defaultGuard<A>(): GuardType<A, A> {
|
|
1112
|
+
return Effect.succeedSome;
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
function mergeLayers(layers: ReadonlyArray<AnyLayer>): AnyLayer {
|
|
1116
|
+
if (layers.length === 0) return Layer.empty;
|
|
1117
|
+
if (layers.length === 1) return layers[0];
|
|
1118
|
+
let current = layers[0];
|
|
1119
|
+
for (let i = 1; i < layers.length; i++) {
|
|
1120
|
+
current = Layer.merge(current, layers[i]);
|
|
1121
|
+
}
|
|
1122
|
+
return current;
|
|
302
1123
|
}
|
|
303
1124
|
|
|
304
1125
|
/**
|
|
305
|
-
* @
|
|
1126
|
+
* @internal
|
|
306
1127
|
*/
|
|
307
|
-
export
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
|
|
1128
|
+
export function compile(cases: ReadonlyArray<MatchAst>): ReadonlyArray<CompiledEntry> {
|
|
1129
|
+
const entries: Array<CompiledEntry> = [];
|
|
1130
|
+
|
|
1131
|
+
const visit = (
|
|
1132
|
+
matches: ReadonlyArray<MatchAst>,
|
|
1133
|
+
context: {
|
|
1134
|
+
readonly layers: ReadonlyArray<AnyLayer>;
|
|
1135
|
+
readonly layouts: ReadonlyArray<AnyLayout>;
|
|
1136
|
+
readonly catches: ReadonlyArray<AnyCatch>;
|
|
1137
|
+
readonly prefixes: ReadonlyArray<RouteAst>;
|
|
1138
|
+
},
|
|
1139
|
+
): void => {
|
|
1140
|
+
for (const match of matches) {
|
|
1141
|
+
switch (match.type) {
|
|
1142
|
+
case "route": {
|
|
1143
|
+
const baseRoute = makeRoute(match.route);
|
|
1144
|
+
const prefixedRoute = applyPrefixes(baseRoute, context.prefixes);
|
|
1145
|
+
entries.push({
|
|
1146
|
+
route: prefixedRoute,
|
|
1147
|
+
guard: getGuard(match.guard as GuardInput<any, any, any, any>),
|
|
1148
|
+
handler: normalizeHandler(match.handler),
|
|
1149
|
+
layers: context.layers,
|
|
1150
|
+
layouts: context.layouts,
|
|
1151
|
+
catches: context.catches,
|
|
1152
|
+
decode: Schema.decodeUnknownEffect(prefixedRoute.paramsSchema),
|
|
1153
|
+
});
|
|
1154
|
+
break;
|
|
1155
|
+
}
|
|
1156
|
+
case "layer": {
|
|
1157
|
+
const merged = mergeLayers(match.deps);
|
|
1158
|
+
visit(match.matches, {
|
|
1159
|
+
...context,
|
|
1160
|
+
layers: [...context.layers, merged],
|
|
1161
|
+
});
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
case "layout": {
|
|
1165
|
+
visit(match.matches, {
|
|
1166
|
+
...context,
|
|
1167
|
+
layouts: [...context.layouts, match.layout as AnyLayout],
|
|
1168
|
+
});
|
|
1169
|
+
break;
|
|
1170
|
+
}
|
|
1171
|
+
case "prefixed": {
|
|
1172
|
+
visit(match.matches, {
|
|
1173
|
+
...context,
|
|
1174
|
+
prefixes: [...context.prefixes, match.prefix],
|
|
1175
|
+
});
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
case "catch": {
|
|
1179
|
+
visit(match.matches, {
|
|
1180
|
+
...context,
|
|
1181
|
+
catches: [...context.catches, match.f as AnyCatch],
|
|
1182
|
+
});
|
|
1183
|
+
break;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
visit(cases, { layers: [], layouts: [], catches: [], prefixes: [] });
|
|
1190
|
+
return entries;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function applyPrefixes(route: Route.Any, prefixes: ReadonlyArray<RouteAst>): Route.Any {
|
|
1194
|
+
if (prefixes.length === 0) return route;
|
|
1195
|
+
const prefixRoutes = prefixes.map((prefix) => makeRoute(prefix));
|
|
1196
|
+
return Join(...prefixRoutes, route);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// Parallel scope cleanup helper
|
|
1200
|
+
const closeScopes = (scopes: Iterable<Scope.Closeable>, fiberId: number) =>
|
|
1201
|
+
Effect.forEach(scopes, (scope) => Scope.close(scope, interrupt(fiberId)), {
|
|
1202
|
+
concurrency: "unbounded",
|
|
1203
|
+
discard: true,
|
|
1204
|
+
});
|
|
330
1205
|
|
|
331
1206
|
/**
|
|
332
|
-
* @
|
|
1207
|
+
* @internal
|
|
333
1208
|
*/
|
|
334
|
-
export
|
|
335
|
-
<
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
1209
|
+
export function makeLayerManager(memoMap: Layer.MemoMap, rootScope: Scope.Scope, fiberId: number) {
|
|
1210
|
+
const states = new Map<AnyLayer, { scope: Scope.Closeable; services: AnyServiceMap }>();
|
|
1211
|
+
let order: ReadonlyArray<AnyLayer> = [];
|
|
1212
|
+
let cachedDesiredSet: Set<AnyLayer> | undefined = undefined;
|
|
1213
|
+
let cachedOrder: ReadonlyArray<AnyLayer> | undefined = undefined;
|
|
1214
|
+
|
|
1215
|
+
const prepare = (desired: ReadonlyArray<AnyLayer>) =>
|
|
1216
|
+
Effect.gen(function* () {
|
|
1217
|
+
const desiredSet =
|
|
1218
|
+
cachedOrder === desired
|
|
1219
|
+
? cachedDesiredSet!
|
|
1220
|
+
: ((cachedDesiredSet = new Set(desired)), (cachedOrder = desired), cachedDesiredSet);
|
|
1221
|
+
const removed = order.filter((layer) => !desiredSet.has(layer));
|
|
1222
|
+
const added: Array<AnyLayer> = [];
|
|
1223
|
+
let services = ServiceMap.empty();
|
|
1224
|
+
|
|
1225
|
+
for (const layer of desired) {
|
|
1226
|
+
const existing = states.get(layer);
|
|
1227
|
+
if (existing) {
|
|
1228
|
+
services = ServiceMap.merge(services, existing.services);
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
const scope = yield* Scope.fork(rootScope);
|
|
1233
|
+
const buildExit = yield* Layer.buildWithMemoMap(layer, memoMap, scope).pipe(
|
|
1234
|
+
Effect.provideServices(services),
|
|
1235
|
+
Effect.exit,
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
if (Exit.isFailure(buildExit)) {
|
|
1239
|
+
for (let i = added.length - 1; i >= 0; i--) {
|
|
1240
|
+
const addedLayer = added[i];
|
|
1241
|
+
const addedState = states.get(addedLayer);
|
|
1242
|
+
if (addedState) {
|
|
1243
|
+
states.delete(addedLayer);
|
|
1244
|
+
yield* Scope.close(addedState.scope, interrupt(fiberId));
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
yield* Scope.close(scope, buildExit);
|
|
1248
|
+
return yield* Effect.failCause(buildExit.cause);
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
const servicesForLayer = buildExit.value;
|
|
1252
|
+
services = ServiceMap.merge(services, servicesForLayer);
|
|
1253
|
+
states.set(layer, { scope, services: servicesForLayer });
|
|
1254
|
+
added.push(layer);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
const commit = Effect.gen(function* () {
|
|
1258
|
+
for (let i = removed.length - 1; i >= 0; i--) {
|
|
1259
|
+
const layer = removed[i];
|
|
1260
|
+
const state = states.get(layer);
|
|
1261
|
+
if (state) {
|
|
1262
|
+
states.delete(layer);
|
|
1263
|
+
yield* Scope.close(state.scope, interrupt(fiberId));
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
order = desired;
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
const rollback = Effect.gen(function* () {
|
|
1270
|
+
for (let i = added.length - 1; i >= 0; i--) {
|
|
1271
|
+
const layer = added[i];
|
|
1272
|
+
const state = states.get(layer);
|
|
1273
|
+
if (state) {
|
|
1274
|
+
states.delete(layer);
|
|
1275
|
+
yield* Scope.close(state.scope, interrupt(fiberId));
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
|
|
1280
|
+
return { services, commit, rollback };
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
return { prepare };
|
|
1284
|
+
}
|
|
377
1285
|
|
|
378
1286
|
/**
|
|
379
|
-
* @
|
|
1287
|
+
* @internal
|
|
380
1288
|
*/
|
|
381
|
-
export
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
1289
|
+
export function makeLayoutManager(rootScope: Scope.Scope, fiberId: number) {
|
|
1290
|
+
const states = new Map<
|
|
1291
|
+
AnyLayout,
|
|
1292
|
+
{
|
|
1293
|
+
params: RefSubject.RefSubject<any>;
|
|
1294
|
+
content: RefSubject.RefSubject<Fx.Fx<any, any, any>>;
|
|
1295
|
+
fx: Fx.Fx<any, any, any>;
|
|
1296
|
+
scope: Scope.Closeable;
|
|
1297
|
+
}
|
|
1298
|
+
>();
|
|
1299
|
+
let active: ReadonlyArray<AnyLayout> = [];
|
|
1300
|
+
|
|
1301
|
+
const removeUnused = (layouts: ReadonlyArray<AnyLayout>) =>
|
|
1302
|
+
Effect.gen(function* () {
|
|
1303
|
+
const next = new Set(layouts);
|
|
1304
|
+
const removed = active.filter((layout) => !next.has(layout));
|
|
1305
|
+
const scopes = removed.map((layout) => {
|
|
1306
|
+
const state = states.get(layout)!;
|
|
1307
|
+
states.delete(layout);
|
|
1308
|
+
return state.scope;
|
|
1309
|
+
});
|
|
1310
|
+
yield* closeScopes(scopes, fiberId);
|
|
1311
|
+
active = layouts;
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
const apply = (
|
|
1315
|
+
layouts: ReadonlyArray<AnyLayout>,
|
|
1316
|
+
paramsValue: any,
|
|
1317
|
+
inner: Fx.Fx<any, any, any>,
|
|
1318
|
+
services: ServiceMap.ServiceMap<any>,
|
|
1319
|
+
) =>
|
|
1320
|
+
Effect.gen(function* () {
|
|
1321
|
+
let current = inner;
|
|
1322
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
1323
|
+
const layout = layouts[i];
|
|
1324
|
+
const state = states.get(layout);
|
|
1325
|
+
if (state === undefined) {
|
|
1326
|
+
const scope = yield* Scope.fork(rootScope);
|
|
1327
|
+
const params = yield* RefSubject.make(paramsValue).pipe(Scope.provide(scope));
|
|
1328
|
+
const content = yield* RefSubject.make<Fx.Fx<any, any, any>>(Effect.succeed(current), {
|
|
1329
|
+
eq: (left, right) => left === right,
|
|
1330
|
+
}).pipe(Scope.provide(scope));
|
|
1331
|
+
const fx = layout({ params, content: content.pipe(switchMap(identity)) }).pipe(
|
|
1332
|
+
provideServices(ServiceMap.merge(services, ServiceMap.make(Scope.Scope, scope))),
|
|
1333
|
+
);
|
|
1334
|
+
states.set(layout, { params, content, fx, scope });
|
|
1335
|
+
current = fx;
|
|
1336
|
+
} else {
|
|
1337
|
+
yield* RefSubject.set(state.params, paramsValue);
|
|
1338
|
+
// @effect-diagnostics-next-line floatingEffect:off
|
|
1339
|
+
yield* RefSubject.set(state.content, current);
|
|
1340
|
+
current = state.fx;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
yield* removeUnused(layouts);
|
|
1344
|
+
return current;
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
const updateParams = (layouts: ReadonlyArray<AnyLayout>, paramsValue: any) =>
|
|
1348
|
+
Effect.forEach(
|
|
1349
|
+
layouts,
|
|
1350
|
+
(layout) => {
|
|
1351
|
+
const state = states.get(layout);
|
|
1352
|
+
return state !== undefined ? RefSubject.set(state.params, paramsValue) : Effect.void;
|
|
1353
|
+
},
|
|
1354
|
+
{ discard: true },
|
|
1355
|
+
);
|
|
1356
|
+
|
|
1357
|
+
return { apply, updateParams };
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* @internal
|
|
1362
|
+
*/
|
|
1363
|
+
export function makeCatchManager(rootScope: Scope.Scope, fiberId: number) {
|
|
1364
|
+
const states = new Map<
|
|
1365
|
+
AnyCatch,
|
|
1366
|
+
{
|
|
1367
|
+
causes: RefSubject.RefSubject<Cause.Cause<any>>;
|
|
1368
|
+
content: RefSubject.RefSubject<Fx.Fx<any, any, any>>;
|
|
1369
|
+
fx: Fx.Fx<any, any, any>;
|
|
1370
|
+
scope: Scope.Closeable;
|
|
1371
|
+
}
|
|
1372
|
+
>();
|
|
1373
|
+
let active: ReadonlyArray<AnyCatch> = [];
|
|
1374
|
+
|
|
1375
|
+
const removeUnused = (catches: ReadonlyArray<AnyCatch>) =>
|
|
1376
|
+
Effect.gen(function* () {
|
|
1377
|
+
const next = new Set(catches);
|
|
1378
|
+
const removed = active.filter((c) => !next.has(c));
|
|
1379
|
+
const scopes = removed.map((c) => {
|
|
1380
|
+
const state = states.get(c)!;
|
|
1381
|
+
states.delete(c);
|
|
1382
|
+
return state.scope;
|
|
1383
|
+
});
|
|
1384
|
+
yield* closeScopes(scopes, fiberId);
|
|
1385
|
+
active = catches;
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1388
|
+
const apply = (
|
|
1389
|
+
catches: ReadonlyArray<AnyCatch>,
|
|
1390
|
+
inner: Fx.Fx<any, any, any>,
|
|
1391
|
+
services: ServiceMap.ServiceMap<any>,
|
|
1392
|
+
) =>
|
|
1393
|
+
Effect.gen(function* () {
|
|
1394
|
+
let current = inner;
|
|
1395
|
+
for (let i = catches.length - 1; i >= 0; i--) {
|
|
1396
|
+
const catcher = catches[i];
|
|
1397
|
+
const state = states.get(catcher);
|
|
1398
|
+
if (state === undefined) {
|
|
1399
|
+
const scope = yield* Scope.fork(rootScope);
|
|
1400
|
+
const causes = yield* RefSubject.make<Cause.Cause<any>>(Cause.fail(undefined)).pipe(
|
|
1401
|
+
Scope.provide(scope),
|
|
1402
|
+
);
|
|
1403
|
+
const content = yield* RefSubject.make<Fx.Fx<any, any, any>>(Effect.succeed(current), {
|
|
1404
|
+
eq: (left, right) => left === right,
|
|
1405
|
+
}).pipe(Scope.provide(scope));
|
|
1406
|
+
const fallback = catcher(causes).pipe(
|
|
1407
|
+
provideServices(ServiceMap.merge(services, ServiceMap.make(Scope.Scope, scope))),
|
|
1408
|
+
);
|
|
1409
|
+
const fx = content.pipe(
|
|
1410
|
+
switchMap(identity),
|
|
1411
|
+
exit,
|
|
1412
|
+
mapEffect(
|
|
1413
|
+
Effect.fn(function* (e) {
|
|
1414
|
+
if (isSuccess(e)) return succeed(e.value);
|
|
1415
|
+
yield* RefSubject.set(causes, e.cause);
|
|
1416
|
+
return fallback;
|
|
1417
|
+
}),
|
|
1418
|
+
),
|
|
1419
|
+
skipRepeats,
|
|
1420
|
+
switchMap(identity),
|
|
1421
|
+
);
|
|
1422
|
+
states.set(catcher, { causes, content, fx, scope });
|
|
1423
|
+
current = fx;
|
|
1424
|
+
} else {
|
|
1425
|
+
// @effect-diagnostics-next-line floatingEffect:off
|
|
1426
|
+
yield* RefSubject.set(state.content, current);
|
|
1427
|
+
current = state.fx;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
yield* removeUnused(catches);
|
|
1431
|
+
return current;
|
|
1432
|
+
});
|
|
1433
|
+
|
|
1434
|
+
return { apply };
|
|
385
1435
|
}
|