@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.
Files changed (46) hide show
  1. package/MatchInput/package.json +6 -0
  2. package/RouteGuard/package.json +6 -0
  3. package/RouteMatch/package.json +6 -0
  4. package/dist/cjs/CurrentRoute.js +48 -31
  5. package/dist/cjs/CurrentRoute.js.map +1 -1
  6. package/dist/cjs/MatchInput.js +96 -0
  7. package/dist/cjs/MatchInput.js.map +1 -0
  8. package/dist/cjs/Matcher.js +97 -73
  9. package/dist/cjs/Matcher.js.map +1 -1
  10. package/dist/cjs/RouteGuard.js +78 -0
  11. package/dist/cjs/RouteGuard.js.map +1 -0
  12. package/dist/cjs/RouteMatch.js +49 -0
  13. package/dist/cjs/RouteMatch.js.map +1 -0
  14. package/dist/cjs/index.js +25 -0
  15. package/dist/cjs/index.js.map +1 -1
  16. package/dist/dts/CurrentRoute.d.ts +19 -14
  17. package/dist/dts/CurrentRoute.d.ts.map +1 -1
  18. package/dist/dts/MatchInput.d.ts +135 -0
  19. package/dist/dts/MatchInput.d.ts.map +1 -0
  20. package/dist/dts/Matcher.d.ts +94 -33
  21. package/dist/dts/Matcher.d.ts.map +1 -1
  22. package/dist/dts/RouteGuard.d.ts +94 -0
  23. package/dist/dts/RouteGuard.d.ts.map +1 -0
  24. package/dist/dts/RouteMatch.d.ts +50 -0
  25. package/dist/dts/RouteMatch.d.ts.map +1 -0
  26. package/dist/dts/index.d.ts +12 -0
  27. package/dist/dts/index.d.ts.map +1 -1
  28. package/dist/esm/CurrentRoute.js +46 -29
  29. package/dist/esm/CurrentRoute.js.map +1 -1
  30. package/dist/esm/MatchInput.js +79 -0
  31. package/dist/esm/MatchInput.js.map +1 -0
  32. package/dist/esm/Matcher.js +95 -67
  33. package/dist/esm/Matcher.js.map +1 -1
  34. package/dist/esm/RouteGuard.js +57 -0
  35. package/dist/esm/RouteGuard.js.map +1 -0
  36. package/dist/esm/RouteMatch.js +29 -0
  37. package/dist/esm/RouteMatch.js.map +1 -0
  38. package/dist/esm/index.js +12 -0
  39. package/dist/esm/index.js.map +1 -1
  40. package/package.json +35 -10
  41. package/src/CurrentRoute.ts +113 -63
  42. package/src/MatchInput.ts +282 -0
  43. package/src/Matcher.ts +325 -143
  44. package/src/RouteGuard.ts +217 -0
  45. package/src/RouteMatch.ts +104 -0
  46. 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 * as Guard from "@typed/guard"
10
- import * as Navigation from "@typed/navigation"
11
- import type * as Path from "@typed/path"
12
- import * as Route from "@typed/route"
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, withCurrentRoute } from "./CurrentRoute.js"
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 interface RouteMatcher<out A, out E, out R> {
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
- readonly to: {
39
- <const P extends string, B>(
40
- route: Route.Route<P> | P,
41
- f: (params: Path.ParamsOf<P>) => B
42
- ): RouteMatcher<A | B, E, R>
43
-
44
- <const P extends string, B, E2, R2, C, E3, R3>(
45
- route: Route.Route<P> | P,
46
- guard: Guard.Guard<Path.ParamsOf<P>, B, E2, R2>,
47
- f: (b: B) => C
48
- ): RouteMatcher<A | C, E | E2 | E3, R | Exclude<R2, Scope.Scope> | Exclude<R3, Scope.Scope>>
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 notFound: <B, E2, R2>(
52
- f: (destination: typeof Navigation.CurrentEntry) => Fx.Fx<B, E2, R2>
53
- ) => Fx.Fx<
54
- A | B,
55
- Exclude<E | E2, Navigation.RedirectError>,
56
- Navigation.Navigation | CurrentEnvironment | R | R2 | Scope.Scope
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 redirect: <const P extends string>(
60
- route: Route.Route<P> | P,
61
- ...params: [keyof Path.ParamsOf<P>] extends [never] ? [{}?] : [Path.ParamsOf<P>]
62
- ) => Fx.Fx<
63
- A,
64
- Exclude<E, Navigation.RedirectError>,
65
- Navigation.Navigation | CurrentRoute | CurrentEnvironment | R | Scope.Scope
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 interface RouteGuard<
73
- P extends string,
74
- A,
75
- O,
76
- E = never,
77
- R = never,
78
- E2 = never,
79
- R2 = never
80
- > {
81
- readonly route: Route.Route<P>
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<A, E, R> implements RouteMatcher<A, E, R> {
87
- constructor(readonly guards: ReadonlyArray<RouteGuard<any, any, any, any, any, any, any>>) {
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
- ...args: any
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
- to(
114
- ...args: any
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
- const route = getRoute(args[0])
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
- notFound<B, E2, R2>(
125
- f: (destination: RefSubject.Computed<Navigation.Destination, never, Navigation.Navigation>) => Fx.Fx<B, E2, R2>
126
- ): Fx.Fx<
127
- A | B,
128
- Exclude<E | E2, Navigation.RedirectError>,
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
- redirect<const P extends string>(
152
- route: P | Route.Route<P>,
153
- ...params: [keyof Path.ParamsOf<P>] extends [never] ? [{}?] : [Path.ParamsOf<P>]
154
- ): Fx.Fx<
155
- A,
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
- function getRoute<const P extends string>(route: Route.Route<P> | P): Route.Route<P> {
176
- return typeof route === "string" ? Route.fromPath(route) : route
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 empty(): RouteMatcher<never, never, never> {
183
- return new RouteMatcherImpl<never, never, never>([]) as any
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.18.0
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
- } = empty()
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
+ }