@typed/router 0.32.0 → 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -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 +191 -0
- package/dist/Matcher.d.ts.map +1 -0
- package/dist/Matcher.js +597 -0
- package/dist/Parser.d.ts +96 -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 +25 -70
- package/src/AST.ts +166 -0
- package/src/CurrentRoute.ts +30 -331
- package/src/Matcher.test.ts +476 -0
- package/src/Matcher.ts +1269 -328
- package/src/Parser.ts +282 -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 +31 -0
- package/src/Uri.ts +214 -0
- package/src/index.ts +4 -28
- package/tsconfig.json +6 -0
- 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,1326 @@
|
|
|
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>;
|
|
107
303
|
}
|
|
108
304
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
export type
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
305
|
+
export declare namespace Matcher {
|
|
306
|
+
export type Any =
|
|
307
|
+
| Matcher<any, any, any>
|
|
308
|
+
| Matcher<any, never, any>
|
|
309
|
+
| Matcher<any, any, never>
|
|
310
|
+
| Matcher<any, never, never>;
|
|
311
|
+
export type Success<T> = [T] extends [Matcher<infer A, infer _E, infer _R>] ? A : never;
|
|
312
|
+
export type Error<T> = [T] extends [Matcher<infer _A, infer E, infer _R>] ? E : never;
|
|
313
|
+
export type Services<T> = [T] extends [Matcher<infer _A, infer _E, infer R>] ? R : never;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export type MatchHandler<Params, A, E, R> =
|
|
317
|
+
| Fx.Fx<A, E, R>
|
|
318
|
+
| Stream.Stream<A, E, R>
|
|
319
|
+
| Effect.Effect<A, E, R>
|
|
320
|
+
| A
|
|
321
|
+
| ((
|
|
322
|
+
params: RefSubject.RefSubject<Params>,
|
|
323
|
+
) => Fx.Fx<A, E, R> | Stream.Stream<A, E, R> | Effect.Effect<A, E, R> | A);
|
|
324
|
+
|
|
325
|
+
type MatchHandlerFn<Params, A, E, R> = (
|
|
326
|
+
params: RefSubject.RefSubject<Params>,
|
|
327
|
+
) => Fx.Fx<A, E, R> | Stream.Stream<A, E, R> | Effect.Effect<A, E, R> | A;
|
|
328
|
+
|
|
329
|
+
function isMatchHandlerFn<Params, A, E, R>(
|
|
330
|
+
handler: MatchHandler<Params, A, E, R>,
|
|
331
|
+
): handler is MatchHandlerFn<Params, A, E, R> {
|
|
332
|
+
return typeof handler === "function";
|
|
122
333
|
}
|
|
123
334
|
|
|
124
|
-
|
|
125
|
-
|
|
335
|
+
function isHandlerOptions(value: unknown): value is { readonly handler: unknown } {
|
|
336
|
+
return typeof value === "object" && value !== null && "handler" in value;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Monomorphic shape - all properties always present for V8 optimization
|
|
340
|
+
type ParsedMatch = {
|
|
341
|
+
readonly route: Route.Any;
|
|
342
|
+
readonly handler: unknown;
|
|
343
|
+
readonly guard: AnyGuard | undefined;
|
|
344
|
+
readonly layout: AnyLayout | undefined;
|
|
345
|
+
readonly catchFn: AnyCatch | undefined;
|
|
346
|
+
readonly dependencies: ReadonlyArray<AnyDependency> | undefined;
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
function parseMatchArgs(args: [unknown, ...Array<unknown>]): ParsedMatch {
|
|
350
|
+
const [first, second, third] = args;
|
|
126
351
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
352
|
+
// Single arg: full options object (Overload 9)
|
|
353
|
+
if (second === undefined) {
|
|
354
|
+
const opts = first as MatchOptions<Route.Any, any, any, any, any, any, any, any, any>;
|
|
355
|
+
return {
|
|
356
|
+
route: opts.route,
|
|
357
|
+
handler: opts.handler,
|
|
358
|
+
guard: undefined,
|
|
359
|
+
layout: opts.layout as AnyLayout | undefined,
|
|
360
|
+
catchFn: opts.catch as AnyCatch | undefined,
|
|
361
|
+
dependencies: opts.dependencies as ReadonlyArray<AnyDependency> | undefined,
|
|
362
|
+
};
|
|
133
363
|
}
|
|
134
364
|
|
|
135
|
-
|
|
136
|
-
|
|
365
|
+
// Two args
|
|
366
|
+
if (third === undefined) {
|
|
367
|
+
if (isHandlerOptions(second)) {
|
|
368
|
+
// Overload 3: match(route, options)
|
|
369
|
+
const opts = second as MatchHandlerOptions<any, any, any, any, any, any, any, any, any>;
|
|
370
|
+
return {
|
|
371
|
+
route: first as Route.Any,
|
|
372
|
+
handler: opts.handler,
|
|
373
|
+
guard: undefined,
|
|
374
|
+
layout: opts.layout as AnyLayout | undefined,
|
|
375
|
+
catchFn: opts.catch as AnyCatch | undefined,
|
|
376
|
+
dependencies: opts.dependencies as ReadonlyArray<AnyDependency> | undefined,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
// Overloads 1, 2, 4: match(route, handler)
|
|
380
|
+
return {
|
|
381
|
+
route: first as Route.Any,
|
|
382
|
+
handler: second,
|
|
383
|
+
guard: undefined,
|
|
384
|
+
layout: undefined,
|
|
385
|
+
catchFn: undefined,
|
|
386
|
+
dependencies: undefined,
|
|
387
|
+
};
|
|
137
388
|
}
|
|
138
389
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return
|
|
390
|
+
// Three args
|
|
391
|
+
if (isHandlerOptions(third)) {
|
|
392
|
+
// Overload 7: match(route, guard, options)
|
|
393
|
+
const opts = third as MatchHandlerOptions<any, any, any, any, any, any, any, any, any>;
|
|
394
|
+
return {
|
|
395
|
+
route: first as Route.Any,
|
|
396
|
+
handler: opts.handler,
|
|
397
|
+
guard: second as AnyGuard,
|
|
398
|
+
layout: opts.layout as AnyLayout | undefined,
|
|
399
|
+
catchFn: opts.catch as AnyCatch | undefined,
|
|
400
|
+
dependencies: opts.dependencies as ReadonlyArray<AnyDependency> | undefined,
|
|
401
|
+
};
|
|
144
402
|
}
|
|
145
403
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
404
|
+
// Overloads 5, 6, 8: match(route, guard, handler)
|
|
405
|
+
return {
|
|
406
|
+
route: first as Route.Any,
|
|
407
|
+
handler: third,
|
|
408
|
+
guard: second as AnyGuard,
|
|
409
|
+
layout: undefined,
|
|
410
|
+
catchFn: undefined,
|
|
411
|
+
dependencies: undefined,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
class MatcherImpl<A, E, R> implements Matcher<A, E, R> {
|
|
416
|
+
readonly cases: ReadonlyArray<MatchAst>;
|
|
417
|
+
constructor(cases: ReadonlyArray<MatchAst>) {
|
|
418
|
+
this.cases = cases;
|
|
419
|
+
this.match = this.match.bind(this);
|
|
420
|
+
this.catch = this.catch.bind(this);
|
|
421
|
+
this.catchTag = this.catchTag.bind(this);
|
|
422
|
+
this.layout = this.layout.bind(this);
|
|
423
|
+
this.provide = this.provide.bind(this);
|
|
424
|
+
this.provideService = this.provideService.bind(this);
|
|
151
425
|
}
|
|
152
426
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
427
|
+
// Implementation overloads for type inference - use simplified return types
|
|
428
|
+
match<Rt extends Route.Any, B, E2 = never, R2 = never>(
|
|
429
|
+
route: Rt,
|
|
430
|
+
handler: (params: RefSubject.RefSubject<Route.Type<Rt>>) => MatchHandlerReturnValue<B, E2, R2>,
|
|
431
|
+
): Matcher<A | B, E | E2, R | R2 | Scope.Scope>;
|
|
432
|
+
match<Rt extends Route.Any, B, E2 = never, R2 = never>(
|
|
433
|
+
route: Rt,
|
|
434
|
+
handler: Fx.Fx<B, E2, R2> | Effect.Effect<B, E2, R2> | Stream.Stream<B, E2, R2>,
|
|
435
|
+
): Matcher<A | B, E | E2, R | R2 | Scope.Scope>;
|
|
436
|
+
match<
|
|
437
|
+
Rt extends Route.Any,
|
|
438
|
+
B,
|
|
439
|
+
E2 = never,
|
|
440
|
+
R2 = never,
|
|
441
|
+
D extends ReadonlyArray<AnyDependency> | undefined = undefined,
|
|
442
|
+
>(
|
|
443
|
+
route: Rt,
|
|
444
|
+
options: MatchHandlerOptions<Route.Type<Rt>, B, E2, R2, D, B, never, never, undefined>,
|
|
445
|
+
): Matcher<
|
|
446
|
+
A | B,
|
|
447
|
+
E | E2 | DependencyError<D extends ReadonlyArray<infer Dep> ? Dep : never>,
|
|
448
|
+
R | R2 | Scope.Scope
|
|
449
|
+
>;
|
|
450
|
+
match<Rt extends Route.Any, B>(route: Rt, handler: B): Matcher<A | B, E, R | Scope.Scope>;
|
|
451
|
+
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B, E2, R2>(
|
|
452
|
+
route: Rt,
|
|
453
|
+
guard: G,
|
|
454
|
+
handler: (params: RefSubject.RefSubject<GuardOutput<G>>) => MatchHandlerReturnValue<B, E2, R2>,
|
|
455
|
+
): Matcher<A | B, E | E2 | GuardError<G>, R | R2 | GuardServices<G> | Scope.Scope>;
|
|
456
|
+
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B, E2, R2>(
|
|
457
|
+
route: Rt,
|
|
458
|
+
guard: G,
|
|
459
|
+
handler: Fx.Fx<B, E2, R2> | Effect.Effect<B, E2, R2> | Stream.Stream<B, E2, R2>,
|
|
460
|
+
): Matcher<A | B, E | E2 | GuardError<G>, R | R2 | GuardServices<G> | Scope.Scope>;
|
|
461
|
+
match<
|
|
462
|
+
Rt extends Route.Any,
|
|
463
|
+
G extends GuardInput<Route.Type<Rt>, any, any, any>,
|
|
464
|
+
B,
|
|
465
|
+
E2 = never,
|
|
466
|
+
R2 = never,
|
|
467
|
+
D extends ReadonlyArray<AnyDependency> | undefined = undefined,
|
|
468
|
+
>(
|
|
469
|
+
route: Rt,
|
|
470
|
+
guard: G,
|
|
471
|
+
options: MatchHandlerOptions<GuardOutput<G>, B, E2, R2, D, B, never, never, undefined>,
|
|
472
|
+
): Matcher<
|
|
473
|
+
A | B,
|
|
474
|
+
E | E2 | GuardError<G> | DependencyError<D extends ReadonlyArray<infer Dep> ? Dep : never>,
|
|
475
|
+
R | R2 | GuardServices<G> | Scope.Scope
|
|
476
|
+
>;
|
|
477
|
+
match<Rt extends Route.Any, G extends GuardInput<Route.Type<Rt>, any, any, any>, B>(
|
|
478
|
+
route: Rt,
|
|
479
|
+
guard: G,
|
|
480
|
+
handler: B,
|
|
481
|
+
): Matcher<A | B, E | GuardError<G>, R | GuardServices<G> | Scope.Scope>;
|
|
482
|
+
match<
|
|
483
|
+
Rt extends Route.Any,
|
|
484
|
+
B,
|
|
485
|
+
E2 = never,
|
|
486
|
+
R2 = never,
|
|
487
|
+
D extends ReadonlyArray<AnyDependency> | undefined = undefined,
|
|
488
|
+
>(
|
|
489
|
+
options: MatchOptions<Rt, B, E2, R2, D, B, never, never, undefined>,
|
|
490
|
+
): Matcher<
|
|
491
|
+
A | B,
|
|
492
|
+
E | E2 | DependencyError<D extends ReadonlyArray<infer Dep> ? Dep : never>,
|
|
493
|
+
R | R2 | Scope.Scope
|
|
494
|
+
>;
|
|
495
|
+
match(...args: [unknown, ...Array<unknown>]): Matcher<any, any, any> {
|
|
496
|
+
const parsed = parseMatchArgs(args);
|
|
497
|
+
const normalizedGuard =
|
|
498
|
+
parsed.guard !== undefined
|
|
499
|
+
? getGuard(parsed.guard as GuardInput<any, any, any, any>)
|
|
500
|
+
: defaultGuard();
|
|
501
|
+
|
|
502
|
+
const routeAst = AST.route(
|
|
503
|
+
parsed.route.ast,
|
|
504
|
+
parsed.handler as MatchHandler<any, any, any, any>,
|
|
505
|
+
normalizedGuard,
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
let matches: ReadonlyArray<MatchAst> = [routeAst];
|
|
509
|
+
if (parsed.layout !== undefined) {
|
|
510
|
+
matches = [AST.layout(matches, parsed.layout)];
|
|
511
|
+
}
|
|
512
|
+
if (parsed.catchFn !== undefined) {
|
|
513
|
+
matches = [AST.catchCause(matches, parsed.catchFn)];
|
|
514
|
+
}
|
|
515
|
+
if (parsed.dependencies !== undefined && parsed.dependencies.length > 0) {
|
|
516
|
+
matches = [AST.layer(matches, normalizeDependencies(parsed.dependencies))];
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return new MatcherImpl([...this.cases, ...matches]);
|
|
158
520
|
}
|
|
159
521
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
522
|
+
prefix<Rt extends Route.Any>(route: Rt): Matcher<A, E, R> {
|
|
523
|
+
return new MatcherImpl<A, E, R>([AST.prefixed(this.cases, route.ast)]);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
provide<Layers extends readonly [AnyLayer, ...AnyLayer[]]>(
|
|
527
|
+
...layers: Layers
|
|
528
|
+
): Matcher<
|
|
529
|
+
A,
|
|
530
|
+
E | LayerError<Layers[number]>,
|
|
531
|
+
Exclude<R, LayerSuccess<Layers[number]>> | LayerServices<Layers[number]>
|
|
532
|
+
> {
|
|
533
|
+
return new MatcherImpl([AST.layer(this.cases, layers)]) as Matcher<
|
|
534
|
+
A,
|
|
535
|
+
E | LayerError<Layers[number]>,
|
|
536
|
+
Exclude<R, LayerSuccess<Layers[number]>> | LayerServices<Layers[number]>
|
|
537
|
+
>;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
provideService<Id, S>(tag: ServiceMap.Service<Id, S>, service: S): Matcher<A, E, Exclude<R, Id>> {
|
|
541
|
+
return this.provideServices(ServiceMap.make(tag, service));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
provideServices<R2>(services: ServiceMap.ServiceMap<R2>): Matcher<A, E, Exclude<R, R2>> {
|
|
545
|
+
return this.provide(Layer.succeedServices(services));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
catchCause<B, E2, R2>(f: CatchHandler<E, B, E2, R2>): Matcher<A | B, E2, R | R2> {
|
|
549
|
+
return new MatcherImpl<A | B, E2, R | R2>([AST.catchCause(this.cases, f as AnyCatch)]);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
catch<B, E2, R2>(f: (e: E) => Fx.Fx<B, E2, R2>): Matcher<A | B, E2, R | R2> {
|
|
553
|
+
return this.catchCause((causeRef) =>
|
|
554
|
+
unwrap(
|
|
555
|
+
Effect.gen(function* () {
|
|
556
|
+
const cause = yield* causeRef;
|
|
557
|
+
const result = Cause.findFail(cause);
|
|
558
|
+
if (Result.isFailure(result)) {
|
|
559
|
+
return fromEffect(Effect.failCause(result.failure));
|
|
560
|
+
}
|
|
561
|
+
return f(result.success.error);
|
|
562
|
+
}),
|
|
563
|
+
),
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
catchTag<const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>, B, E2, R2>(
|
|
568
|
+
tag: K,
|
|
569
|
+
f: (
|
|
570
|
+
e: ExtractTag<NoInfer<E>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
571
|
+
) => Fx.Fx<B, E2, R2>,
|
|
572
|
+
): Matcher<
|
|
573
|
+
A | B,
|
|
574
|
+
E2 | ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
575
|
+
R | R2
|
|
576
|
+
> {
|
|
577
|
+
const rethrow = (cause: Cause.Cause<E>) =>
|
|
578
|
+
fromEffect(Effect.failCause(cause)) as Fx.Fx<
|
|
579
|
+
B,
|
|
580
|
+
E2 | ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
581
|
+
R2
|
|
582
|
+
>;
|
|
583
|
+
|
|
584
|
+
return new MatcherImpl<
|
|
585
|
+
A | B,
|
|
586
|
+
E2 | ExcludeTag<E, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
587
|
+
R | R2
|
|
588
|
+
>([
|
|
589
|
+
AST.catchCause(this.cases, (causeRef) =>
|
|
590
|
+
unwrap(
|
|
591
|
+
Effect.gen(function* () {
|
|
592
|
+
const cause = yield* causeRef;
|
|
593
|
+
const result = Cause.findFail(cause);
|
|
594
|
+
if (Result.isFailure(result)) {
|
|
595
|
+
return rethrow(cause);
|
|
596
|
+
}
|
|
597
|
+
if (matchesTag(tag, result.success.error)) {
|
|
598
|
+
return f(result.success.error);
|
|
599
|
+
}
|
|
600
|
+
return rethrow(cause);
|
|
601
|
+
}),
|
|
602
|
+
),
|
|
603
|
+
),
|
|
604
|
+
]);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
layout<B, E2, R2>(layout: Layout<any, A, E, R, B, E2, R2>): Matcher<B, E | E2, R | R2> {
|
|
608
|
+
return new MatcherImpl<B, E | E2, R | R2>([
|
|
609
|
+
AST.layout(this.cases, layout as AnyLayout),
|
|
610
|
+
]) as Matcher<B, E | E2, R | R2>;
|
|
165
611
|
}
|
|
166
612
|
|
|
167
613
|
pipe() {
|
|
168
|
-
return pipeArguments(this, arguments)
|
|
614
|
+
return pipeArguments(this, arguments);
|
|
169
615
|
}
|
|
170
616
|
}
|
|
171
617
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
): RouteMatcher<Matches> {
|
|
178
|
-
return new RouteMatcherImpl(matches)
|
|
618
|
+
function normalizeHandler<Params, B, E2 = never, R2 = never>(
|
|
619
|
+
handler: MatchHandler<Params, B, E2, R2>,
|
|
620
|
+
): (params: RefSubject.RefSubject<Params>) => Fx.Fx<B, E2, R2> {
|
|
621
|
+
if (isMatchHandlerFn(handler)) return (params) => toFx(handler(params));
|
|
622
|
+
return () => toFx(handler);
|
|
179
623
|
}
|
|
180
624
|
|
|
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
|
|
625
|
+
function toFx<A, E, R>(
|
|
626
|
+
value: Fx.Fx<A, E, R> | Stream.Stream<A, E, R> | Effect.Effect<A, E, R> | A,
|
|
627
|
+
): Fx.Fx<A, E, R> {
|
|
628
|
+
if (isFx(value)) return value;
|
|
629
|
+
if (Stream.isStream(value)) return fromStream(value);
|
|
630
|
+
if (Effect.isEffect(value)) return fromEffect(value);
|
|
631
|
+
return succeed(value);
|
|
205
632
|
}
|
|
206
633
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
export
|
|
211
|
-
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
634
|
+
export const empty: Matcher<never> = new MatcherImpl([]);
|
|
635
|
+
export const match = empty.match.bind(empty);
|
|
636
|
+
|
|
637
|
+
export class RouteGuardError extends Schema.ErrorClass<RouteGuardError>(
|
|
638
|
+
"@typed/router/RouteGuardError",
|
|
639
|
+
)({
|
|
640
|
+
_tag: Schema.tag("RouteGuardError"),
|
|
641
|
+
path: Schema.String,
|
|
642
|
+
causes: Schema.Array(Schema.Unknown),
|
|
643
|
+
}) {}
|
|
644
|
+
|
|
645
|
+
export class RouteNotFound extends Schema.ErrorClass<RouteNotFound>("@typed/router/RouteNotFound")({
|
|
646
|
+
_tag: Schema.tag("RouteNotFound"),
|
|
647
|
+
path: Schema.String,
|
|
648
|
+
}) {}
|
|
649
|
+
|
|
650
|
+
export class RouteDecodeError extends Schema.ErrorClass<RouteDecodeError>(
|
|
651
|
+
"@typed/router/RouteDecodeError",
|
|
652
|
+
)({
|
|
653
|
+
_tag: Schema.tag("RouteDecodeError"),
|
|
654
|
+
path: Schema.String,
|
|
655
|
+
cause: Schema.String,
|
|
656
|
+
}) {}
|
|
221
657
|
|
|
222
658
|
/**
|
|
223
|
-
* @
|
|
659
|
+
* @internal
|
|
224
660
|
*/
|
|
225
|
-
export
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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>
|
|
661
|
+
export type CompiledEntry = {
|
|
662
|
+
readonly route: Route.Any;
|
|
663
|
+
readonly guard: AnyGuard;
|
|
664
|
+
readonly handler: AnyMatchHandler;
|
|
665
|
+
readonly layers: ReadonlyArray<AnyLayer>;
|
|
666
|
+
readonly layouts: ReadonlyArray<AnyLayout>;
|
|
667
|
+
readonly catches: ReadonlyArray<AnyCatch>;
|
|
668
|
+
readonly decode: (input: unknown) => Effect.Effect<any, Schema.SchemaError, any>;
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
export function run<M extends Matcher.Any>(
|
|
672
|
+
matcher: M,
|
|
245
673
|
): Fx.Fx<
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
674
|
+
Matcher.Success<M>,
|
|
675
|
+
Matcher.Error<M> | RouteNotFound | RouteDecodeError | RouteGuardError,
|
|
676
|
+
Matcher.Services<M> | Router | CurrentRoute | Scope.Scope
|
|
249
677
|
> {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
678
|
+
return unwrap(
|
|
679
|
+
Effect.gen(function* () {
|
|
680
|
+
const fiberId = yield* Effect.fiberId;
|
|
681
|
+
const rootScope = yield* Effect.scope;
|
|
682
|
+
const current = yield* CurrentRoute;
|
|
683
|
+
const prefixed = matcher.prefix(current.route);
|
|
684
|
+
const entries = compile(prefixed.cases);
|
|
685
|
+
const router = findMyWay.make<ReadonlyArray<CompiledEntry>>({
|
|
686
|
+
ignoreTrailingSlash: true,
|
|
687
|
+
caseSensitive: false,
|
|
688
|
+
});
|
|
689
|
+
const handlersByPath = new Map<string, Array<CompiledEntry>>();
|
|
690
|
+
const memoMap = yield* Layer.makeMemoMap;
|
|
691
|
+
const layerManager = makeLayerManager(memoMap, rootScope, fiberId);
|
|
692
|
+
const layoutManager = makeLayoutManager(rootScope, fiberId);
|
|
693
|
+
const catchManager = makeCatchManager(rootScope, fiberId);
|
|
260
694
|
|
|
261
|
-
|
|
262
|
-
|
|
695
|
+
for (const entry of entries) {
|
|
696
|
+
const path = entry.route.path;
|
|
697
|
+
const existing = handlersByPath.get(path);
|
|
698
|
+
if (existing !== undefined) {
|
|
699
|
+
existing.push(entry);
|
|
700
|
+
} else {
|
|
701
|
+
const list: Array<CompiledEntry> = [entry];
|
|
702
|
+
handlersByPath.set(path, list);
|
|
703
|
+
router.all(path, list);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
263
706
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
707
|
+
let currentState: {
|
|
708
|
+
entry: CompiledEntry;
|
|
709
|
+
params: RefSubject.RefSubject<any>;
|
|
710
|
+
fx: Fx.Fx<Matcher.Success<M>, Matcher.Error<M>, Matcher.Services<M> | Scope.Scope | Router>;
|
|
711
|
+
scope: Scope.Closeable;
|
|
712
|
+
} | null = null;
|
|
713
|
+
|
|
714
|
+
return CurrentPath.pipe(
|
|
715
|
+
mapEffect(
|
|
716
|
+
Effect.fn(function* (path) {
|
|
717
|
+
const result = router.find("GET", path);
|
|
718
|
+
if (result === undefined) return yield* new RouteNotFound({ path });
|
|
719
|
+
|
|
720
|
+
const input = { ...result.params, ...result.searchParams };
|
|
721
|
+
const entries = result.handler;
|
|
722
|
+
const guardCauses: Array<Cause.Cause<any>> = [];
|
|
723
|
+
let matchedEntry: CompiledEntry | undefined = undefined;
|
|
724
|
+
let matchedParams: any = undefined;
|
|
725
|
+
let matchedPrepared:
|
|
726
|
+
| {
|
|
727
|
+
services: AnyServiceMap;
|
|
728
|
+
commit: Effect.Effect<void>;
|
|
729
|
+
rollback: Effect.Effect<void>;
|
|
730
|
+
}
|
|
731
|
+
| undefined = undefined;
|
|
732
|
+
|
|
733
|
+
for (const entry of entries) {
|
|
734
|
+
const params = yield* Effect.mapErrorEager(
|
|
735
|
+
entry.decode(input),
|
|
736
|
+
(cause) =>
|
|
737
|
+
new RouteDecodeError({ path, cause: makeFormatterDefault()(cause.issue) }),
|
|
738
|
+
);
|
|
739
|
+
|
|
740
|
+
const prepared = yield* layerManager.prepare(entry.layers);
|
|
741
|
+
const guardExit = yield* entry
|
|
742
|
+
.guard(params)
|
|
743
|
+
.pipe(Effect.provideServices(prepared.services), Effect.exit);
|
|
744
|
+
|
|
745
|
+
if (Exit.isFailure(guardExit)) {
|
|
746
|
+
guardCauses.push(guardExit.cause);
|
|
747
|
+
yield* prepared.rollback;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (Option.isNone(guardExit.value)) {
|
|
752
|
+
yield* prepared.rollback;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
matchedEntry = entry;
|
|
757
|
+
matchedParams = guardExit.value.value;
|
|
758
|
+
matchedPrepared = prepared;
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
if (matchedEntry === undefined || matchedPrepared === undefined) {
|
|
763
|
+
return yield* new RouteGuardError({ path, causes: guardCauses });
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
yield* matchedPrepared.commit;
|
|
767
|
+
|
|
768
|
+
if (currentState !== null && currentState.entry === matchedEntry) {
|
|
769
|
+
yield* RefSubject.set(currentState.params, matchedParams);
|
|
770
|
+
yield* layoutManager.updateParams(matchedEntry.layouts, matchedParams);
|
|
771
|
+
return currentState.fx;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
if (currentState !== null) {
|
|
775
|
+
yield* Scope.close(currentState.scope, interrupt(fiberId));
|
|
776
|
+
currentState = null;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const scope = yield* Scope.fork(rootScope);
|
|
780
|
+
const paramsRef = yield* RefSubject.make(matchedParams).pipe(Scope.provide(scope));
|
|
781
|
+
|
|
782
|
+
const preparedServices = matchedPrepared.services as ServiceMap.ServiceMap<any>;
|
|
783
|
+
const handlerServices = ServiceMap.merge(
|
|
784
|
+
preparedServices,
|
|
785
|
+
ServiceMap.make(Scope.Scope, scope),
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
const handlerFx = matchedEntry
|
|
789
|
+
.handler(paramsRef)
|
|
790
|
+
.pipe(provideServices(handlerServices));
|
|
791
|
+
const withLayouts = yield* layoutManager.apply(
|
|
792
|
+
matchedEntry.layouts,
|
|
793
|
+
matchedParams,
|
|
794
|
+
handlerFx,
|
|
795
|
+
preparedServices,
|
|
796
|
+
);
|
|
797
|
+
const withCatches = yield* catchManager.apply(
|
|
798
|
+
matchedEntry.catches,
|
|
799
|
+
withLayouts,
|
|
800
|
+
preparedServices,
|
|
801
|
+
);
|
|
802
|
+
const fx = withCatches;
|
|
803
|
+
|
|
804
|
+
currentState = {
|
|
805
|
+
entry: matchedEntry,
|
|
806
|
+
params: paramsRef,
|
|
807
|
+
scope,
|
|
808
|
+
fx,
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
return currentState.fx;
|
|
812
|
+
}),
|
|
813
|
+
),
|
|
814
|
+
skipRepeats,
|
|
815
|
+
switchMap(identity),
|
|
816
|
+
);
|
|
817
|
+
}),
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
type InputSucces<T> = [Matcher.Success<T> | Fx.Fx.Success<T>] extends [infer A] ? A : never;
|
|
822
|
+
type InputError<T> = [Matcher.Error<T> | Fx.Fx.Error<T>] extends [infer E] ? E : never;
|
|
823
|
+
type InputServices<T> = [Matcher.Services<T> | Fx.Fx.Services<T>] extends [infer R] ? R : never;
|
|
824
|
+
|
|
825
|
+
export const catchCause: {
|
|
826
|
+
<I extends Fx.Fx.Any | Matcher.Any, B, E2 = never, R2 = never>(
|
|
827
|
+
f: (
|
|
828
|
+
cause: RefSubject.RefSubject<
|
|
829
|
+
Cause.Cause<InputError<I> | RouteNotFound | RouteDecodeError | RouteGuardError>
|
|
830
|
+
>,
|
|
831
|
+
) => Fx.Fx<B, E2, R2>,
|
|
832
|
+
): (input: I) => Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
833
|
+
|
|
834
|
+
<I extends Fx.Fx.Any | Matcher.Any, B, E2 = never, R2 = never>(
|
|
835
|
+
input: I,
|
|
836
|
+
f: (
|
|
837
|
+
cause: RefSubject.RefSubject<
|
|
838
|
+
Cause.Cause<InputError<I> | RouteNotFound | RouteDecodeError | RouteGuardError>
|
|
839
|
+
>,
|
|
840
|
+
) => Fx.Fx<B, E2, R2>,
|
|
841
|
+
): Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
842
|
+
} = dual(
|
|
843
|
+
2,
|
|
844
|
+
<A, E, R, B, E2, R2>(
|
|
845
|
+
input: Fx.Fx<A, E, R> | Matcher<A, E, R>,
|
|
846
|
+
f: (
|
|
847
|
+
cause: RefSubject.RefSubject<
|
|
848
|
+
Cause.Cause<E | RouteNotFound | RouteDecodeError | RouteGuardError>
|
|
849
|
+
>,
|
|
850
|
+
) => Fx.Fx<B, E2, R2>,
|
|
851
|
+
): Fx.Fx<A | B, E2, R | R2 | Router | Scope.Scope> => {
|
|
852
|
+
const eff = Effect.gen(function* () {
|
|
853
|
+
const fiberId = yield* Effect.fiberId;
|
|
854
|
+
const rootScope = yield* Effect.scope;
|
|
855
|
+
const fx = isFx(input) ? input : run(input);
|
|
856
|
+
const manager = makeCatchManager(rootScope, fiberId);
|
|
857
|
+
const result = yield* manager.apply(
|
|
858
|
+
[f],
|
|
859
|
+
fx,
|
|
860
|
+
ServiceMap.empty() as ServiceMap.ServiceMap<any>,
|
|
861
|
+
);
|
|
862
|
+
return result as Fx.Fx<A | B, E2, R | R2 | Router | Scope.Scope>;
|
|
863
|
+
});
|
|
864
|
+
return unwrap(eff);
|
|
865
|
+
},
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
export const catch_: {
|
|
869
|
+
<I extends Fx.Fx.Any | Matcher.Any, B, E2, R2>(
|
|
870
|
+
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
871
|
+
): (input: I) => Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
872
|
+
} = dual(
|
|
873
|
+
2,
|
|
874
|
+
<I extends Fx.Fx.Any | Matcher.Any, B, E2, R2>(
|
|
875
|
+
input: I,
|
|
876
|
+
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
877
|
+
): Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope> =>
|
|
878
|
+
catchCause(input, (causeRef) =>
|
|
879
|
+
unwrap(
|
|
880
|
+
Effect.gen(function* () {
|
|
881
|
+
const cause = yield* causeRef;
|
|
882
|
+
const result = Cause.findFail(cause);
|
|
883
|
+
if (Result.isFailure(result)) {
|
|
884
|
+
return fromEffect(Effect.failCause(result.failure));
|
|
885
|
+
}
|
|
886
|
+
return f(result.success.error as InputError<I>);
|
|
887
|
+
}),
|
|
888
|
+
),
|
|
889
|
+
),
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
export { catch_ as catch };
|
|
893
|
+
|
|
894
|
+
export const catchTag: {
|
|
895
|
+
<
|
|
896
|
+
I extends Fx.Fx.Any | Matcher.Any,
|
|
897
|
+
const K extends Tags<E> | Arr.NonEmptyReadonlyArray<Tags<E>>,
|
|
898
|
+
E,
|
|
899
|
+
B,
|
|
900
|
+
E2,
|
|
901
|
+
R2,
|
|
902
|
+
>(
|
|
903
|
+
k: K,
|
|
904
|
+
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
905
|
+
): (input: I) => Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope>;
|
|
906
|
+
|
|
907
|
+
<
|
|
908
|
+
I extends Fx.Fx.Any | Matcher.Any,
|
|
909
|
+
const K extends Tags<InputError<I>> | Arr.NonEmptyReadonlyArray<Tags<InputError<I>>>,
|
|
910
|
+
B,
|
|
911
|
+
E2,
|
|
912
|
+
R2,
|
|
913
|
+
>(
|
|
914
|
+
input: I,
|
|
915
|
+
k: K,
|
|
916
|
+
f: (
|
|
917
|
+
e: ExtractTag<InputError<I>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
918
|
+
) => Fx.Fx<B, E2, R2>,
|
|
279
919
|
): Fx.Fx<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
} = dual(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
920
|
+
InputSucces<I> | B,
|
|
921
|
+
E2 | ExcludeTag<InputError<I>, K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K>,
|
|
922
|
+
InputServices<I> | R2 | Router | Scope.Scope
|
|
923
|
+
>;
|
|
924
|
+
} = dual(
|
|
925
|
+
3,
|
|
926
|
+
<
|
|
927
|
+
I extends Fx.Fx.Any | Matcher.Any,
|
|
928
|
+
const K extends Tags<InputError<I>> | Arr.NonEmptyReadonlyArray<Tags<InputError<I>>>,
|
|
929
|
+
B,
|
|
930
|
+
E2,
|
|
931
|
+
R2,
|
|
932
|
+
>(
|
|
933
|
+
input: I,
|
|
934
|
+
k: K,
|
|
935
|
+
f: (e: InputError<I>) => Fx.Fx<B, E2, R2>,
|
|
936
|
+
): Fx.Fx<InputSucces<I> | B, E2, InputServices<I> | R2 | Router | Scope.Scope> =>
|
|
937
|
+
catchCause(input, (causeRef) =>
|
|
938
|
+
unwrap(
|
|
939
|
+
Effect.gen(function* () {
|
|
940
|
+
const cause = yield* causeRef;
|
|
941
|
+
const result = Cause.findFail(cause);
|
|
942
|
+
if (Result.isFailure(result)) {
|
|
943
|
+
return fromEffect(Effect.failCause(result.failure));
|
|
944
|
+
}
|
|
945
|
+
if (matchesTag(k, result.success.error)) {
|
|
946
|
+
return f(
|
|
947
|
+
result.success.error as ExtractTag<
|
|
948
|
+
InputError<I>,
|
|
949
|
+
K extends Arr.NonEmptyReadonlyArray<string> ? K[number] : K
|
|
950
|
+
>,
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
return fromEffect(Effect.fail(result.success.error as E2));
|
|
954
|
+
}),
|
|
955
|
+
),
|
|
956
|
+
),
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
export const redirectTo =
|
|
960
|
+
(path: string) =>
|
|
961
|
+
<I extends Fx.Fx.Any | Matcher.Any>(
|
|
962
|
+
input: I,
|
|
963
|
+
): Fx.Fx<InputSucces<I>, never, Router | Scope.Scope | InputServices<I>> =>
|
|
964
|
+
catchCause(input, (_) =>
|
|
965
|
+
Navigation.navigate(path).pipe(
|
|
966
|
+
Effect.matchCause({
|
|
967
|
+
onFailure: () => never,
|
|
968
|
+
onSuccess: () => never,
|
|
969
|
+
}),
|
|
970
|
+
unwrap,
|
|
971
|
+
),
|
|
972
|
+
);
|
|
973
|
+
|
|
974
|
+
const hasTag = (u: unknown): u is { readonly _tag: string } =>
|
|
975
|
+
typeof u === "object" &&
|
|
976
|
+
u !== null &&
|
|
977
|
+
"_tag" in u &&
|
|
978
|
+
typeof (u as Record<string, unknown>)["_tag"] === "string";
|
|
979
|
+
|
|
980
|
+
const matchesTag = <E, K extends string>(
|
|
981
|
+
tag: K | Arr.NonEmptyReadonlyArray<K>,
|
|
982
|
+
error: E,
|
|
983
|
+
): error is ExtractTag<E, K> => {
|
|
984
|
+
if (!hasTag(error)) return false;
|
|
985
|
+
if (typeof tag === "string") return error._tag === tag;
|
|
986
|
+
return tag.some((t) => t === error._tag);
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
function normalizeDependencies(
|
|
990
|
+
dependencies: ReadonlyArray<AnyDependency>,
|
|
991
|
+
): ReadonlyArray<AnyLayer> {
|
|
992
|
+
return dependencies.map((dep) =>
|
|
993
|
+
Layer.isLayer(dep)
|
|
994
|
+
? dep
|
|
995
|
+
: (Layer.succeedServices(dep as ServiceMap.ServiceMap<any>) as AnyLayer),
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function getGuard<I, O, E, R>(guard: GuardInput<I, O, E, R>): GuardType<I, O, E, R> {
|
|
1000
|
+
return "asGuard" in guard ? guard.asGuard() : guard;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function defaultGuard<A>(): GuardType<A, A> {
|
|
1004
|
+
return Effect.succeedSome;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function mergeLayers(layers: ReadonlyArray<AnyLayer>): AnyLayer {
|
|
1008
|
+
if (layers.length === 1) return layers[0];
|
|
1009
|
+
let current = layers[0];
|
|
1010
|
+
for (let i = 1; i < layers.length; i++) {
|
|
1011
|
+
current = Layer.merge(current, layers[i]);
|
|
1012
|
+
}
|
|
1013
|
+
return current;
|
|
1014
|
+
}
|
|
294
1015
|
|
|
295
1016
|
/**
|
|
296
|
-
* @
|
|
1017
|
+
* @internal
|
|
297
1018
|
*/
|
|
298
|
-
export function
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
1019
|
+
export function compile(cases: ReadonlyArray<MatchAst>): ReadonlyArray<CompiledEntry> {
|
|
1020
|
+
const entries: Array<CompiledEntry> = [];
|
|
1021
|
+
|
|
1022
|
+
const visit = (
|
|
1023
|
+
matches: ReadonlyArray<MatchAst>,
|
|
1024
|
+
context: {
|
|
1025
|
+
readonly layers: ReadonlyArray<AnyLayer>;
|
|
1026
|
+
readonly layouts: ReadonlyArray<AnyLayout>;
|
|
1027
|
+
readonly catches: ReadonlyArray<AnyCatch>;
|
|
1028
|
+
readonly prefixes: ReadonlyArray<RouteAst>;
|
|
1029
|
+
},
|
|
1030
|
+
): void => {
|
|
1031
|
+
for (const match of matches) {
|
|
1032
|
+
switch (match.type) {
|
|
1033
|
+
case "route": {
|
|
1034
|
+
const baseRoute = makeRoute(match.route);
|
|
1035
|
+
const prefixedRoute = applyPrefixes(baseRoute, context.prefixes);
|
|
1036
|
+
entries.push({
|
|
1037
|
+
route: prefixedRoute,
|
|
1038
|
+
guard: getGuard(match.guard as GuardInput<any, any, any, any>),
|
|
1039
|
+
handler: normalizeHandler(match.handler),
|
|
1040
|
+
layers: context.layers,
|
|
1041
|
+
layouts: context.layouts,
|
|
1042
|
+
catches: context.catches,
|
|
1043
|
+
decode: Schema.decodeUnknownEffect(prefixedRoute.paramsSchema),
|
|
1044
|
+
});
|
|
1045
|
+
break;
|
|
1046
|
+
}
|
|
1047
|
+
case "layer": {
|
|
1048
|
+
const merged = mergeLayers(match.deps);
|
|
1049
|
+
visit(match.matches, {
|
|
1050
|
+
...context,
|
|
1051
|
+
layers: [...context.layers, merged],
|
|
1052
|
+
});
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
case "layout": {
|
|
1056
|
+
visit(match.matches, {
|
|
1057
|
+
...context,
|
|
1058
|
+
layouts: [...context.layouts, match.layout as AnyLayout],
|
|
1059
|
+
});
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
case "prefixed": {
|
|
1063
|
+
visit(match.matches, {
|
|
1064
|
+
...context,
|
|
1065
|
+
prefixes: [...context.prefixes, match.prefix],
|
|
1066
|
+
});
|
|
1067
|
+
break;
|
|
1068
|
+
}
|
|
1069
|
+
case "catch": {
|
|
1070
|
+
visit(match.matches, {
|
|
1071
|
+
...context,
|
|
1072
|
+
catches: [...context.catches, match.f as AnyCatch],
|
|
1073
|
+
});
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
|
|
1080
|
+
visit(cases, { layers: [], layouts: [], catches: [], prefixes: [] });
|
|
1081
|
+
return entries;
|
|
302
1082
|
}
|
|
303
1083
|
|
|
1084
|
+
function applyPrefixes(route: Route.Any, prefixes: ReadonlyArray<RouteAst>): Route.Any {
|
|
1085
|
+
if (prefixes.length === 0) return route;
|
|
1086
|
+
const prefixRoutes = prefixes.map((prefix) => makeRoute(prefix));
|
|
1087
|
+
return Join(...prefixRoutes, route);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// Parallel scope cleanup helper
|
|
1091
|
+
const closeScopes = (scopes: Iterable<Scope.Closeable>, fiberId: number) =>
|
|
1092
|
+
Effect.forEach(scopes, (scope) => Scope.close(scope, interrupt(fiberId)), {
|
|
1093
|
+
concurrency: "unbounded",
|
|
1094
|
+
discard: true,
|
|
1095
|
+
});
|
|
1096
|
+
|
|
304
1097
|
/**
|
|
305
|
-
* @
|
|
1098
|
+
* @internal
|
|
306
1099
|
*/
|
|
307
|
-
export
|
|
308
|
-
<
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
1100
|
+
export function makeLayerManager(memoMap: Layer.MemoMap, rootScope: Scope.Scope, fiberId: number) {
|
|
1101
|
+
const states = new Map<AnyLayer, { scope: Scope.Closeable; services: AnyServiceMap }>();
|
|
1102
|
+
let order: ReadonlyArray<AnyLayer> = [];
|
|
1103
|
+
let cachedDesiredSet: Set<AnyLayer> | undefined = undefined;
|
|
1104
|
+
let cachedOrder: ReadonlyArray<AnyLayer> | undefined = undefined;
|
|
1105
|
+
|
|
1106
|
+
const prepare = (desired: ReadonlyArray<AnyLayer>) =>
|
|
1107
|
+
Effect.gen(function* () {
|
|
1108
|
+
const desiredSet =
|
|
1109
|
+
cachedOrder === desired
|
|
1110
|
+
? cachedDesiredSet!
|
|
1111
|
+
: ((cachedDesiredSet = new Set(desired)), (cachedOrder = desired), cachedDesiredSet);
|
|
1112
|
+
const removed = order.filter((layer) => !desiredSet.has(layer));
|
|
1113
|
+
const added: Array<AnyLayer> = [];
|
|
1114
|
+
let services = ServiceMap.empty();
|
|
1115
|
+
|
|
1116
|
+
for (const layer of desired) {
|
|
1117
|
+
const existing = states.get(layer);
|
|
1118
|
+
if (existing) {
|
|
1119
|
+
services = ServiceMap.merge(services, existing.services);
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const scope = yield* Scope.fork(rootScope);
|
|
1124
|
+
const buildExit = yield* Layer.buildWithMemoMap(layer, memoMap, scope).pipe(
|
|
1125
|
+
Effect.provideServices(services),
|
|
1126
|
+
Effect.exit,
|
|
1127
|
+
);
|
|
1128
|
+
|
|
1129
|
+
if (Exit.isFailure(buildExit)) {
|
|
1130
|
+
for (let i = added.length - 1; i >= 0; i--) {
|
|
1131
|
+
const addedLayer = added[i];
|
|
1132
|
+
const addedState = states.get(addedLayer);
|
|
1133
|
+
if (addedState) {
|
|
1134
|
+
states.delete(addedLayer);
|
|
1135
|
+
yield* Scope.close(addedState.scope, interrupt(fiberId));
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
yield* Scope.close(scope, buildExit);
|
|
1139
|
+
return yield* Effect.failCause(buildExit.cause);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
const servicesForLayer = buildExit.value;
|
|
1143
|
+
services = ServiceMap.merge(services, servicesForLayer);
|
|
1144
|
+
states.set(layer, { scope, services: servicesForLayer });
|
|
1145
|
+
added.push(layer);
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const commit = Effect.gen(function* () {
|
|
1149
|
+
for (let i = removed.length - 1; i >= 0; i--) {
|
|
1150
|
+
const layer = removed[i];
|
|
1151
|
+
const state = states.get(layer);
|
|
1152
|
+
if (state) {
|
|
1153
|
+
states.delete(layer);
|
|
1154
|
+
yield* Scope.close(state.scope, interrupt(fiberId));
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
order = desired;
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
const rollback = Effect.gen(function* () {
|
|
1161
|
+
for (let i = added.length - 1; i >= 0; i--) {
|
|
1162
|
+
const layer = added[i];
|
|
1163
|
+
const state = states.get(layer);
|
|
1164
|
+
if (state) {
|
|
1165
|
+
states.delete(layer);
|
|
1166
|
+
yield* Scope.close(state.scope, interrupt(fiberId));
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
return { services, commit, rollback };
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
return { prepare };
|
|
1175
|
+
}
|
|
330
1176
|
|
|
331
1177
|
/**
|
|
332
|
-
* @
|
|
1178
|
+
* @internal
|
|
333
1179
|
*/
|
|
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
|
-
)
|
|
1180
|
+
export function makeLayoutManager(rootScope: Scope.Scope, fiberId: number) {
|
|
1181
|
+
const states = new Map<
|
|
1182
|
+
AnyLayout,
|
|
1183
|
+
{
|
|
1184
|
+
params: RefSubject.RefSubject<any>;
|
|
1185
|
+
content: RefSubject.RefSubject<Fx.Fx<any, any, any>>;
|
|
1186
|
+
fx: Fx.Fx<any, any, any>;
|
|
1187
|
+
scope: Scope.Closeable;
|
|
1188
|
+
}
|
|
1189
|
+
>();
|
|
1190
|
+
let active: ReadonlyArray<AnyLayout> = [];
|
|
1191
|
+
|
|
1192
|
+
const removeUnused = (layouts: ReadonlyArray<AnyLayout>) =>
|
|
1193
|
+
Effect.gen(function* () {
|
|
1194
|
+
const next = new Set(layouts);
|
|
1195
|
+
const removed = active.filter((layout) => !next.has(layout));
|
|
1196
|
+
const scopes = removed.map((layout) => {
|
|
1197
|
+
const state = states.get(layout)!;
|
|
1198
|
+
states.delete(layout);
|
|
1199
|
+
return state.scope;
|
|
1200
|
+
});
|
|
1201
|
+
yield* closeScopes(scopes, fiberId);
|
|
1202
|
+
active = layouts;
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
const apply = (
|
|
1206
|
+
layouts: ReadonlyArray<AnyLayout>,
|
|
1207
|
+
paramsValue: any,
|
|
1208
|
+
inner: Fx.Fx<any, any, any>,
|
|
1209
|
+
services: ServiceMap.ServiceMap<any>,
|
|
1210
|
+
) =>
|
|
1211
|
+
Effect.gen(function* () {
|
|
1212
|
+
let current = inner;
|
|
1213
|
+
for (let i = layouts.length - 1; i >= 0; i--) {
|
|
1214
|
+
const layout = layouts[i];
|
|
1215
|
+
const state = states.get(layout);
|
|
1216
|
+
if (state === undefined) {
|
|
1217
|
+
const scope = yield* Scope.fork(rootScope);
|
|
1218
|
+
const params = yield* RefSubject.make(paramsValue).pipe(Scope.provide(scope));
|
|
1219
|
+
const content = yield* RefSubject.make<Fx.Fx<any, any, any>>(Effect.succeed(current), {
|
|
1220
|
+
eq: (left, right) => left === right,
|
|
1221
|
+
}).pipe(Scope.provide(scope));
|
|
1222
|
+
const fx = layout({ params, content: content.pipe(switchMap(identity)) }).pipe(
|
|
1223
|
+
provideServices(ServiceMap.merge(services, ServiceMap.make(Scope.Scope, scope))),
|
|
1224
|
+
);
|
|
1225
|
+
states.set(layout, { params, content, fx, scope });
|
|
1226
|
+
current = fx;
|
|
1227
|
+
} else {
|
|
1228
|
+
yield* RefSubject.set(state.params, paramsValue);
|
|
1229
|
+
// @effect-diagnostics-next-line floatingEffect:off
|
|
1230
|
+
yield* RefSubject.set(state.content, current);
|
|
1231
|
+
current = state.fx;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
yield* removeUnused(layouts);
|
|
1235
|
+
return current;
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
const updateParams = (layouts: ReadonlyArray<AnyLayout>, paramsValue: any) =>
|
|
1239
|
+
Effect.forEach(
|
|
1240
|
+
layouts,
|
|
1241
|
+
(layout) => {
|
|
1242
|
+
const state = states.get(layout);
|
|
1243
|
+
return state !== undefined ? RefSubject.set(state.params, paramsValue) : Effect.void;
|
|
1244
|
+
},
|
|
1245
|
+
{ discard: true },
|
|
1246
|
+
);
|
|
1247
|
+
|
|
1248
|
+
return { apply, updateParams };
|
|
1249
|
+
}
|
|
377
1250
|
|
|
378
1251
|
/**
|
|
379
|
-
* @
|
|
1252
|
+
* @internal
|
|
380
1253
|
*/
|
|
381
|
-
export
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
1254
|
+
export function makeCatchManager(rootScope: Scope.Scope, fiberId: number) {
|
|
1255
|
+
const states = new Map<
|
|
1256
|
+
AnyCatch,
|
|
1257
|
+
{
|
|
1258
|
+
causes: RefSubject.RefSubject<Cause.Cause<any>>;
|
|
1259
|
+
content: RefSubject.RefSubject<Fx.Fx<any, any, any>>;
|
|
1260
|
+
fx: Fx.Fx<any, any, any>;
|
|
1261
|
+
scope: Scope.Closeable;
|
|
1262
|
+
}
|
|
1263
|
+
>();
|
|
1264
|
+
let active: ReadonlyArray<AnyCatch> = [];
|
|
1265
|
+
|
|
1266
|
+
const removeUnused = (catches: ReadonlyArray<AnyCatch>) =>
|
|
1267
|
+
Effect.gen(function* () {
|
|
1268
|
+
const next = new Set(catches);
|
|
1269
|
+
const removed = active.filter((c) => !next.has(c));
|
|
1270
|
+
const scopes = removed.map((c) => {
|
|
1271
|
+
const state = states.get(c)!;
|
|
1272
|
+
states.delete(c);
|
|
1273
|
+
return state.scope;
|
|
1274
|
+
});
|
|
1275
|
+
yield* closeScopes(scopes, fiberId);
|
|
1276
|
+
active = catches;
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
const apply = (
|
|
1280
|
+
catches: ReadonlyArray<AnyCatch>,
|
|
1281
|
+
inner: Fx.Fx<any, any, any>,
|
|
1282
|
+
services: ServiceMap.ServiceMap<any>,
|
|
1283
|
+
) =>
|
|
1284
|
+
Effect.gen(function* () {
|
|
1285
|
+
let current = inner;
|
|
1286
|
+
for (let i = catches.length - 1; i >= 0; i--) {
|
|
1287
|
+
const catcher = catches[i];
|
|
1288
|
+
const state = states.get(catcher);
|
|
1289
|
+
if (state === undefined) {
|
|
1290
|
+
const scope = yield* Scope.fork(rootScope);
|
|
1291
|
+
const causes = yield* RefSubject.make<Cause.Cause<any>>(Cause.fail(undefined)).pipe(
|
|
1292
|
+
Scope.provide(scope),
|
|
1293
|
+
);
|
|
1294
|
+
const content = yield* RefSubject.make<Fx.Fx<any, any, any>>(Effect.succeed(current), {
|
|
1295
|
+
eq: (left, right) => left === right,
|
|
1296
|
+
}).pipe(Scope.provide(scope));
|
|
1297
|
+
const fallback = catcher(causes).pipe(
|
|
1298
|
+
provideServices(ServiceMap.merge(services, ServiceMap.make(Scope.Scope, scope))),
|
|
1299
|
+
);
|
|
1300
|
+
const fx = content.pipe(
|
|
1301
|
+
switchMap(identity),
|
|
1302
|
+
exit,
|
|
1303
|
+
mapEffect(
|
|
1304
|
+
Effect.fn(function* (e) {
|
|
1305
|
+
if (isSuccess(e)) return succeed(e.value);
|
|
1306
|
+
yield* RefSubject.set(causes, e.cause);
|
|
1307
|
+
return fallback;
|
|
1308
|
+
}),
|
|
1309
|
+
),
|
|
1310
|
+
skipRepeats,
|
|
1311
|
+
switchMap(identity),
|
|
1312
|
+
);
|
|
1313
|
+
states.set(catcher, { causes, content, fx, scope });
|
|
1314
|
+
current = fx;
|
|
1315
|
+
} else {
|
|
1316
|
+
// @effect-diagnostics-next-line floatingEffect:off
|
|
1317
|
+
yield* RefSubject.set(state.content, current);
|
|
1318
|
+
current = state.fx;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
yield* removeUnused(catches);
|
|
1322
|
+
return current;
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
return { apply };
|
|
385
1326
|
}
|