@typed/router 0.27.7 → 0.28.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/MatchInput/package.json +6 -0
- package/RouteGuard/package.json +6 -0
- package/RouteMatch/package.json +6 -0
- package/dist/cjs/CurrentRoute.js +48 -31
- package/dist/cjs/CurrentRoute.js.map +1 -1
- package/dist/cjs/MatchInput.js +96 -0
- package/dist/cjs/MatchInput.js.map +1 -0
- package/dist/cjs/Matcher.js +97 -73
- package/dist/cjs/Matcher.js.map +1 -1
- package/dist/cjs/RouteGuard.js +78 -0
- package/dist/cjs/RouteGuard.js.map +1 -0
- package/dist/cjs/RouteMatch.js +49 -0
- package/dist/cjs/RouteMatch.js.map +1 -0
- package/dist/cjs/index.js +25 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/dts/CurrentRoute.d.ts +19 -14
- package/dist/dts/CurrentRoute.d.ts.map +1 -1
- package/dist/dts/MatchInput.d.ts +135 -0
- package/dist/dts/MatchInput.d.ts.map +1 -0
- package/dist/dts/Matcher.d.ts +94 -33
- package/dist/dts/Matcher.d.ts.map +1 -1
- package/dist/dts/RouteGuard.d.ts +94 -0
- package/dist/dts/RouteGuard.d.ts.map +1 -0
- package/dist/dts/RouteMatch.d.ts +50 -0
- package/dist/dts/RouteMatch.d.ts.map +1 -0
- package/dist/dts/index.d.ts +12 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/CurrentRoute.js +46 -29
- package/dist/esm/CurrentRoute.js.map +1 -1
- package/dist/esm/MatchInput.js +79 -0
- package/dist/esm/MatchInput.js.map +1 -0
- package/dist/esm/Matcher.js +95 -67
- package/dist/esm/Matcher.js.map +1 -1
- package/dist/esm/RouteGuard.js +57 -0
- package/dist/esm/RouteGuard.js.map +1 -0
- package/dist/esm/RouteMatch.js +29 -0
- package/dist/esm/RouteMatch.js.map +1 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -1
- package/package.json +35 -10
- package/src/CurrentRoute.ts +113 -63
- package/src/MatchInput.ts +282 -0
- package/src/Matcher.ts +325 -143
- package/src/RouteGuard.ts +217 -0
- package/src/RouteMatch.ts +104 -0
- package/src/index.ts +15 -0
package/src/Matcher.ts
CHANGED
|
@@ -2,197 +2,379 @@
|
|
|
2
2
|
* @since 1.0.0
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { CurrentEnvironment } from "@typed/environment"
|
|
6
5
|
import * as Fx from "@typed/fx/Fx"
|
|
7
6
|
import * as Match from "@typed/fx/Match"
|
|
8
|
-
import * as RefSubject from "@typed/fx/RefSubject"
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import type * as
|
|
12
|
-
import * as
|
|
13
|
-
import type { Scope } from "effect"
|
|
7
|
+
import type * as RefSubject from "@typed/fx/RefSubject"
|
|
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"
|
|
14
12
|
import * as Effect from "effect/Effect"
|
|
13
|
+
import { dual, pipe } from "effect/Function"
|
|
15
14
|
import * as Option from "effect/Option"
|
|
15
|
+
import { hasProperty } from "effect/Predicate"
|
|
16
|
+
import type * as Scope from "effect/Scope"
|
|
17
|
+
import * as Unify from "effect/Unify"
|
|
16
18
|
import type { CurrentRoute } from "./CurrentRoute.js"
|
|
17
|
-
import { makeHref
|
|
19
|
+
import { makeHref } from "./CurrentRoute.js"
|
|
20
|
+
import type { MatchInput } from "./MatchInput.js"
|
|
21
|
+
import * as RouteMatch from "./RouteMatch.js"
|
|
18
22
|
|
|
19
23
|
/**
|
|
20
24
|
* @since 1.0.0
|
|
21
25
|
*/
|
|
22
|
-
export
|
|
23
|
-
readonly guards: ReadonlyArray<RouteGuard<string, any, A, E, R, E, R>>
|
|
24
|
-
|
|
25
|
-
readonly match: {
|
|
26
|
-
<const P extends string, B, E2, R2>(
|
|
27
|
-
route: Route.Route<P> | P,
|
|
28
|
-
f: (ref: RefSubject.RefSubject<Path.ParamsOf<P>>) => Fx.Fx<B, E2, R2>
|
|
29
|
-
): RouteMatcher<A | B, E | E2, R | Exclude<R2, Scope.Scope>>
|
|
30
|
-
|
|
31
|
-
<const P extends string, B, E2, R2, C, E3, R3>(
|
|
32
|
-
route: Route.Route<P> | P,
|
|
33
|
-
guard: Guard.Guard<Path.ParamsOf<P>, B, E2, R2>,
|
|
34
|
-
f: (ref: RefSubject.RefSubject<B>) => Fx.Fx<C, E3, R3>
|
|
35
|
-
): RouteMatcher<A | C, E | E2 | E3, R | Exclude<R2, Scope.Scope> | Exclude<R3, Scope.Scope>>
|
|
36
|
-
}
|
|
26
|
+
export const RouteMatcherTypeId = Symbol.for("@typed/router/RouteMatcher")
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
28
|
+
/**
|
|
29
|
+
* @since 1.0.0
|
|
30
|
+
*/
|
|
31
|
+
export type RouteMatcherTypeId = typeof RouteMatcherTypeId
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @since 1.0.0
|
|
35
|
+
*/
|
|
36
|
+
export interface RouteMatcher<Matches extends RouteMatch.RouteMatch.Any> {
|
|
37
|
+
readonly [RouteMatcherTypeId]: RouteMatcherTypeId
|
|
38
|
+
|
|
39
|
+
readonly matches: ReadonlyArray<Matches>
|
|
40
|
+
|
|
41
|
+
readonly add: <I extends RouteMatch.RouteMatch.Any>(match: I) => RouteMatcher<Matches | I>
|
|
50
42
|
|
|
51
|
-
readonly
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
43
|
+
readonly match: <I extends MatchInput.Any, A, E, R>(
|
|
44
|
+
input: I,
|
|
45
|
+
match: (ref: RefSubject.RefSubject<MatchInput.Success<I>>) => Fx.Fx<A, E, R>
|
|
46
|
+
) => RouteMatcher<
|
|
47
|
+
| Matches
|
|
48
|
+
| RouteMatch.RouteMatch<
|
|
49
|
+
MatchInput.Route<I>,
|
|
50
|
+
MatchInput.Success<I>,
|
|
51
|
+
MatchInput.Error<I>,
|
|
52
|
+
MatchInput.Context<I>,
|
|
53
|
+
A,
|
|
54
|
+
E,
|
|
55
|
+
R
|
|
56
|
+
>
|
|
57
57
|
>
|
|
58
58
|
|
|
59
|
-
readonly
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
) =>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
59
|
+
readonly switch: <I extends MatchInput.Any, A, E, R>(
|
|
60
|
+
input: I,
|
|
61
|
+
match: (ref: MatchInput.Success<I>) => Fx.Fx<A, E, R>
|
|
62
|
+
) => RouteMatcher<
|
|
63
|
+
| Matches
|
|
64
|
+
| RouteMatch.RouteMatch<
|
|
65
|
+
MatchInput.Route<I>,
|
|
66
|
+
MatchInput.Success<I>,
|
|
67
|
+
MatchInput.Error<I>,
|
|
68
|
+
MatchInput.Context<I>,
|
|
69
|
+
A,
|
|
70
|
+
E,
|
|
71
|
+
R | Scope.Scope
|
|
72
|
+
>
|
|
73
|
+
>
|
|
74
|
+
|
|
75
|
+
readonly effect: <I extends MatchInput.Any, A, E, R>(
|
|
76
|
+
input: I,
|
|
77
|
+
match: (ref: MatchInput.Success<I>) => Effect.Effect<A, E, R>
|
|
78
|
+
) => RouteMatcher<
|
|
79
|
+
| Matches
|
|
80
|
+
| RouteMatch.RouteMatch<
|
|
81
|
+
MatchInput.Route<I>,
|
|
82
|
+
MatchInput.Success<I>,
|
|
83
|
+
MatchInput.Error<I>,
|
|
84
|
+
MatchInput.Context<I>,
|
|
85
|
+
A,
|
|
86
|
+
E,
|
|
87
|
+
R | Scope.Scope
|
|
88
|
+
>
|
|
89
|
+
>
|
|
90
|
+
|
|
91
|
+
readonly to: <I extends MatchInput.Any, B>(
|
|
92
|
+
input: I,
|
|
93
|
+
f: (value: MatchInput.Success<I>) => B
|
|
94
|
+
) => RouteMatcher<
|
|
95
|
+
| Matches
|
|
96
|
+
| RouteMatch.RouteMatch<
|
|
97
|
+
MatchInput.Route<I>,
|
|
98
|
+
MatchInput.Success<I>,
|
|
99
|
+
MatchInput.Error<I>,
|
|
100
|
+
MatchInput.Context<I>,
|
|
101
|
+
B,
|
|
102
|
+
never,
|
|
103
|
+
Scope.Scope
|
|
104
|
+
>
|
|
66
105
|
>
|
|
67
106
|
}
|
|
68
107
|
|
|
69
108
|
/**
|
|
70
109
|
* @since 1.0.0
|
|
71
110
|
*/
|
|
72
|
-
export
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
readonly guard: Guard.Guard<string, A, E, R>
|
|
83
|
-
readonly match: (ref: RefSubject.RefSubject<A>) => Fx.Fx<O, E2, R2>
|
|
111
|
+
export namespace RouteMatcher {
|
|
112
|
+
/**
|
|
113
|
+
* @since 1.0.0
|
|
114
|
+
*/
|
|
115
|
+
export type Any = RouteMatcher<RouteMatch.RouteMatch.Any>
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @since 1.0.0
|
|
119
|
+
*/
|
|
120
|
+
export type Context<T> = T extends RouteMatcher<infer Matches> ? RouteMatch.RouteMatch.Context<Matches> : never
|
|
84
121
|
}
|
|
85
122
|
|
|
86
|
-
class RouteMatcherImpl<
|
|
87
|
-
|
|
123
|
+
class RouteMatcherImpl<Matches extends RouteMatch.RouteMatch.Any> implements RouteMatcher<Matches> {
|
|
124
|
+
readonly [RouteMatcherTypeId]: RouteMatcherTypeId = RouteMatcherTypeId
|
|
125
|
+
|
|
126
|
+
constructor(readonly matches: ReadonlyArray<Matches>) {
|
|
127
|
+
this.add = this.add.bind(this)
|
|
88
128
|
this.match = this.match.bind(this)
|
|
129
|
+
this.switch = this.switch.bind(this)
|
|
130
|
+
this.effect = this.effect.bind(this)
|
|
89
131
|
this.to = this.to.bind(this)
|
|
90
|
-
this.notFound = this.notFound.bind(this)
|
|
91
|
-
this.redirect = this.redirect.bind(this)
|
|
92
132
|
}
|
|
93
133
|
|
|
94
|
-
match
|
|
95
|
-
...
|
|
96
|
-
) {
|
|
97
|
-
const route = getRoute(args[0])
|
|
98
|
-
if (args.length === 2) {
|
|
99
|
-
return new RouteMatcherImpl([...this.guards, {
|
|
100
|
-
route,
|
|
101
|
-
guard: getGuard(route),
|
|
102
|
-
match: (ref: any) => Fx.scoped(args[1](ref))
|
|
103
|
-
}]) as any
|
|
104
|
-
} else {
|
|
105
|
-
return new RouteMatcherImpl([...this.guards, {
|
|
106
|
-
route,
|
|
107
|
-
guard: getGuard(route, args[1]),
|
|
108
|
-
match: (ref: any) => Fx.scoped(args[2](ref))
|
|
109
|
-
}]) as any
|
|
110
|
-
}
|
|
134
|
+
add<I extends RouteMatch.RouteMatch.Any>(match: I): RouteMatcher<Matches | I> {
|
|
135
|
+
return new RouteMatcherImpl([...this.matches, match])
|
|
111
136
|
}
|
|
112
137
|
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
match<I extends MatchInput.Any, A, E, R>(
|
|
139
|
+
input: I,
|
|
140
|
+
match: (ref: RefSubject.RefSubject<MatchInput.Success<I>>) => Fx.Fx<A, E, R>
|
|
115
141
|
) {
|
|
116
|
-
|
|
117
|
-
if (args.length === 2) {
|
|
118
|
-
return this.match(route, (ref: any) => RefSubject.map(ref, args[1]))
|
|
119
|
-
} else {
|
|
120
|
-
return this.match(route, args[1], (ref: any) => RefSubject.map(ref, args[2]))
|
|
121
|
-
}
|
|
142
|
+
return this.add(RouteMatch.fromInput(input, match))
|
|
122
143
|
}
|
|
123
144
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
R | R2 | CurrentEnvironment | Navigation.Navigation | Scope.Scope
|
|
130
|
-
> {
|
|
131
|
-
return Fx.fromFxEffect(CurrentEnvironment.with((env) => {
|
|
132
|
-
const onNotFound = Fx.scoped(f(Navigation.CurrentEntry))
|
|
133
|
-
let matcher: Match.ValueMatcher<string, A | B, E | E2, R | R2 | Navigation.Navigation | Scope.Scope> = Match
|
|
134
|
-
.value(
|
|
135
|
-
// Only if we're rendering in a DOM-based environment should we allow for routing to last indefinitely
|
|
136
|
-
env === "dom" || env === "test:dom" ? Navigation.CurrentPath : Fx.take(Navigation.CurrentPath, 1)
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
for (const { guard, match, route } of this.guards) {
|
|
140
|
-
matcher = matcher.when(guard, (ref) => Fx.middleware(match(ref), withCurrentRoute(route)))
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return Fx.filterMapErrorEffect(matcher.getOrElse(() => onNotFound), (e) =>
|
|
144
|
-
Navigation.isRedirectError(e)
|
|
145
|
-
// Fork the redirect to ensure it does not occur within the same runUpdates as the initial navigation
|
|
146
|
-
? Effect.as(Effect.forkScoped(Navigation.handleRedirect(e)), Option.none())
|
|
147
|
-
: Effect.succeedSome(e as Exclude<E | E2, Navigation.RedirectError>))
|
|
148
|
-
}))
|
|
145
|
+
switch<I extends MatchInput.Any, A, E, R>(
|
|
146
|
+
input: I,
|
|
147
|
+
match: (ref: MatchInput.Success<I>) => Fx.Fx<A, E, R>
|
|
148
|
+
) {
|
|
149
|
+
return this.match(input, Fx.switchMap(match))
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
Exclude<E, Navigation.RedirectError>,
|
|
157
|
-
R | Navigation.Navigation | CurrentEnvironment | CurrentRoute | Scope.Scope
|
|
158
|
-
> {
|
|
159
|
-
return this.notFound(() =>
|
|
160
|
-
RefSubject.mapEffect(makeHref(route, ...params), (s) => Effect.fail(Navigation.redirectToPath(s)))
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function getGuard<const P extends string, B, E2, R2>(
|
|
165
|
-
path: Route.Route<P>,
|
|
166
|
-
guard?: Guard.Guard<Path.ParamsOf<P>, B, E2, R2>
|
|
167
|
-
) {
|
|
168
|
-
if (guard) {
|
|
169
|
-
return Guard.compose(path, guard)
|
|
170
|
-
} else {
|
|
171
|
-
return path.asGuard()
|
|
152
|
+
effect<I extends MatchInput.Any, A, E, R>(
|
|
153
|
+
input: I,
|
|
154
|
+
match: (ref: MatchInput.Success<I>) => Effect.Effect<A, E, R>
|
|
155
|
+
) {
|
|
156
|
+
return this.match(input, Fx.switchMapEffect(match))
|
|
172
157
|
}
|
|
173
|
-
}
|
|
174
158
|
|
|
175
|
-
|
|
176
|
-
|
|
159
|
+
to<I extends MatchInput.Any, B>(
|
|
160
|
+
input: I,
|
|
161
|
+
f: (value: MatchInput.Success<I>) => B
|
|
162
|
+
) {
|
|
163
|
+
return this.match(input, Fx.map(f))
|
|
164
|
+
}
|
|
177
165
|
}
|
|
178
166
|
|
|
179
167
|
/**
|
|
180
168
|
* @since 1.0.0
|
|
181
169
|
*/
|
|
182
|
-
export function
|
|
183
|
-
|
|
170
|
+
export function make<Matches extends RouteMatch.RouteMatch.Any>(
|
|
171
|
+
matches: ReadonlyArray<Matches>
|
|
172
|
+
): RouteMatcher<Matches> {
|
|
173
|
+
return new RouteMatcherImpl(matches)
|
|
184
174
|
}
|
|
185
175
|
|
|
186
176
|
/**
|
|
187
|
-
* @since 1.
|
|
177
|
+
* @since 1.0.0
|
|
188
178
|
*/
|
|
189
|
-
export const
|
|
179
|
+
export const empty: RouteMatcher<never> = make<never>([])
|
|
180
|
+
|
|
181
|
+
const { effect, match, switch: switch_, to } = empty
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
/**
|
|
185
|
+
* @since 1.0.0
|
|
186
|
+
*/
|
|
187
|
+
effect,
|
|
190
188
|
/**
|
|
191
189
|
* @since 1.0.0
|
|
192
190
|
*/
|
|
193
191
|
match,
|
|
192
|
+
/**
|
|
193
|
+
* @since 1.0.0
|
|
194
|
+
*/
|
|
195
|
+
switch_ as switch,
|
|
194
196
|
/**
|
|
195
197
|
* @since 1.0.0
|
|
196
198
|
*/
|
|
197
199
|
to
|
|
198
|
-
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @since 1.0.0
|
|
204
|
+
*/
|
|
205
|
+
export const catchRedirectError = <A, E, R>(
|
|
206
|
+
fx: Fx.Fx<A, E | RedirectError, R>
|
|
207
|
+
): Fx.Fx<A, Exclude<E, RedirectError>, R | Scope.Scope | Navigation> =>
|
|
208
|
+
Fx.filterMapErrorEffect(
|
|
209
|
+
fx,
|
|
210
|
+
Unify.unify((_) =>
|
|
211
|
+
isRedirectError(_)
|
|
212
|
+
? Effect.as(Effect.forkScoped(Effect.ignoreLogged(navigate(_.path, _.options))), Option.none())
|
|
213
|
+
: Effect.succeedSome(_ as Exclude<typeof _, RedirectError>)
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* @since 1.0.0
|
|
219
|
+
*/
|
|
220
|
+
export const notFound: {
|
|
221
|
+
<A, E, R>(
|
|
222
|
+
onNotFound: Fx.Fx<A, E, R>
|
|
223
|
+
): <Matches extends RouteMatch.RouteMatch.Any>(router: RouteMatcher<Matches>) => Fx.Fx<
|
|
224
|
+
A | RouteMatch.RouteMatch.Success<Matches>,
|
|
225
|
+
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
226
|
+
Scope.Scope | Navigation | R | RouteMatch.RouteMatch.Context<Matches>
|
|
227
|
+
>
|
|
228
|
+
|
|
229
|
+
<Matches extends RouteMatch.RouteMatch.Any, A, E, R>(
|
|
230
|
+
router: RouteMatcher<Matches>,
|
|
231
|
+
onNotFound: Fx.Fx<A, E, R>
|
|
232
|
+
): Fx.Fx<
|
|
233
|
+
A | RouteMatch.RouteMatch.Success<Matches>,
|
|
234
|
+
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
235
|
+
Scope.Scope | Navigation | R | RouteMatch.RouteMatch.Context<Matches>
|
|
236
|
+
>
|
|
237
|
+
} = dual(2, function notFound<Matches extends RouteMatch.RouteMatch.Any, A, E, R>(
|
|
238
|
+
router: RouteMatcher<Matches>,
|
|
239
|
+
onNotFound: Fx.Fx<A, E, R>
|
|
240
|
+
): Fx.Fx<
|
|
241
|
+
A | RouteMatch.RouteMatch.Success<Matches>,
|
|
242
|
+
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
243
|
+
R | Navigation | RouteMatch.RouteMatch.Context<Matches> | Scope.Scope
|
|
244
|
+
> {
|
|
245
|
+
let matcher = Match.value(CurrentPath) as Match.ValueMatcher<
|
|
246
|
+
string,
|
|
247
|
+
RouteMatch.RouteMatch.Success<Matches>,
|
|
248
|
+
RouteMatch.RouteMatch.Error<Matches>,
|
|
249
|
+
RouteMatch.RouteMatch.Context<Matches> | Navigation | Scope.Scope
|
|
250
|
+
>
|
|
251
|
+
|
|
252
|
+
for (const match of router.matches) {
|
|
253
|
+
matcher = matcher.when(match.guard, match.match)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return catchRedirectError(matcher.getOrElse(() => onNotFound))
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* @since 1.0.0
|
|
261
|
+
*/
|
|
262
|
+
export const notFoundWith: {
|
|
263
|
+
<A, E, R>(
|
|
264
|
+
onNotFound: Effect.Effect<A, E, R>
|
|
265
|
+
): <Matches extends RouteMatch.RouteMatch.Any>(router: RouteMatcher<Matches>) => Fx.Fx<
|
|
266
|
+
A | RouteMatch.RouteMatch.Success<Matches>,
|
|
267
|
+
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
268
|
+
R | Navigation | RouteMatch.RouteMatch.Context<Matches> | Scope.Scope
|
|
269
|
+
>
|
|
270
|
+
|
|
271
|
+
<Matches extends RouteMatch.RouteMatch.Any, A, E, R>(
|
|
272
|
+
router: RouteMatcher<Matches>,
|
|
273
|
+
onNotFound: Effect.Effect<A, E, R>
|
|
274
|
+
): Fx.Fx<
|
|
275
|
+
A | RouteMatch.RouteMatch.Success<Matches>,
|
|
276
|
+
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
277
|
+
R | Navigation | RouteMatch.RouteMatch.Context<Matches> | Scope.Scope
|
|
278
|
+
>
|
|
279
|
+
} = dual(2, function notFoundWith<Matches extends RouteMatch.RouteMatch.Any, A, E, R>(
|
|
280
|
+
router: RouteMatcher<Matches>,
|
|
281
|
+
onNotFound: Effect.Effect<A, E, R>
|
|
282
|
+
): Fx.Fx<
|
|
283
|
+
A | RouteMatch.RouteMatch.Success<Matches>,
|
|
284
|
+
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
285
|
+
R | Navigation | RouteMatch.RouteMatch.Context<Matches> | Scope.Scope
|
|
286
|
+
> {
|
|
287
|
+
return notFound(router, Fx.fromEffect(onNotFound))
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @since 1.0.0
|
|
292
|
+
*/
|
|
293
|
+
export function isRouteMatcher<M extends RouteMatch.RouteMatch.Any = RouteMatch.RouteMatch.Any>(
|
|
294
|
+
value: unknown
|
|
295
|
+
): value is RouteMatcher<M> {
|
|
296
|
+
return hasProperty(value, RouteMatcherTypeId)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @since 1.0.0
|
|
301
|
+
*/
|
|
302
|
+
export const redirectWith: {
|
|
303
|
+
<E, R>(
|
|
304
|
+
effect: Effect.Effect<string, E, R>
|
|
305
|
+
): <Matches extends RouteMatch.RouteMatch.Any>(router: RouteMatcher<Matches>) => Fx.Fx<
|
|
306
|
+
RouteMatch.RouteMatch.Success<Matches>,
|
|
307
|
+
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
308
|
+
Navigation | Scope.Scope | R | RouteMatch.RouteMatch.Context<Matches>
|
|
309
|
+
>
|
|
310
|
+
|
|
311
|
+
<Matches extends RouteMatch.RouteMatch.Any, E, R>(
|
|
312
|
+
router: RouteMatcher<Matches>,
|
|
313
|
+
effect: Effect.Effect<string, E, R>
|
|
314
|
+
): Fx.Fx<
|
|
315
|
+
RouteMatch.RouteMatch.Success<Matches>,
|
|
316
|
+
Exclude<E | RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
317
|
+
Navigation | Scope.Scope | R | RouteMatch.RouteMatch.Context<Matches>
|
|
318
|
+
>
|
|
319
|
+
} = dual(2, function redirectWith<Matches extends RouteMatch.RouteMatch.Any, E, R>(
|
|
320
|
+
router: RouteMatcher<Matches>,
|
|
321
|
+
effect: Effect.Effect<string, E, R>
|
|
322
|
+
) {
|
|
323
|
+
return notFoundWith(router, Effect.flatMap(effect, (path) => new RedirectError({ path })))
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @since 1.0.0
|
|
328
|
+
*/
|
|
329
|
+
export const redirectTo: {
|
|
330
|
+
<R extends Route.Route.Any>(
|
|
331
|
+
route: R,
|
|
332
|
+
...params: Route.Route.ParamsList<R>
|
|
333
|
+
): <Matches extends RouteMatch.RouteMatch.Any>(router: RouteMatcher<Matches>) => Fx.Fx<
|
|
334
|
+
RouteMatch.RouteMatch.Success<Matches>,
|
|
335
|
+
RedirectRouteMatchError<R> | Exclude<RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
336
|
+
Scope.Scope | Navigation | CurrentRoute | RouteMatch.RouteMatch.Context<Matches>
|
|
337
|
+
>
|
|
338
|
+
|
|
339
|
+
<Matches extends RouteMatch.RouteMatch.Any, R extends Route.Route.Any>(
|
|
340
|
+
router: RouteMatcher<Matches>,
|
|
341
|
+
route: R,
|
|
342
|
+
...params: Route.Route.ParamsList<R>
|
|
343
|
+
): Fx.Fx<
|
|
344
|
+
RouteMatch.RouteMatch.Success<Matches>,
|
|
345
|
+
RedirectRouteMatchError<R> | Exclude<RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
346
|
+
Scope.Scope | Navigation | CurrentRoute | RouteMatch.RouteMatch.Context<Matches>
|
|
347
|
+
>
|
|
348
|
+
} = dual(
|
|
349
|
+
(args) => isRouteMatcher(args[0]),
|
|
350
|
+
function redirect<Matches extends RouteMatch.RouteMatch.Any, R extends Route.Route.Any>(
|
|
351
|
+
router: RouteMatcher<Matches>,
|
|
352
|
+
route: R,
|
|
353
|
+
...params: Route.Route.ParamsList<R>
|
|
354
|
+
): Fx.Fx<
|
|
355
|
+
RouteMatch.RouteMatch.Success<Matches>,
|
|
356
|
+
RedirectRouteMatchError<R> | Exclude<RouteMatch.RouteMatch.Error<Matches>, RedirectError>,
|
|
357
|
+
Scope.Scope | Navigation | CurrentRoute | RouteMatch.RouteMatch.Context<Matches>
|
|
358
|
+
> {
|
|
359
|
+
return redirectWith(
|
|
360
|
+
router,
|
|
361
|
+
pipe(
|
|
362
|
+
Effect.catchTag(
|
|
363
|
+
makeHref<R>(route, ...params),
|
|
364
|
+
"NoSuchElementException",
|
|
365
|
+
() => Effect.fail(new RedirectRouteMatchError<R>(route, (params[0] || {}) as Route.Route.Params<R>))
|
|
366
|
+
),
|
|
367
|
+
Effect.flatMap((path) => new RedirectError({ path }))
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @since 1.0.0
|
|
375
|
+
*/
|
|
376
|
+
export class RedirectRouteMatchError<R extends Route.Route.Any> extends Data.TaggedError("RedirectRouteMatchError") {
|
|
377
|
+
constructor(readonly route: R, readonly params: Route.Route.Params<R>) {
|
|
378
|
+
super()
|
|
379
|
+
}
|
|
380
|
+
}
|