@typed/router 0.13.0 → 0.14.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 (95) hide show
  1. package/dist/Link.d.ts +26 -11
  2. package/dist/Link.d.ts.map +1 -1
  3. package/dist/Link.js +47 -23
  4. package/dist/Link.js.map +1 -1
  5. package/dist/Match.d.ts +33 -0
  6. package/dist/Match.d.ts.map +1 -0
  7. package/dist/Match.js +16 -0
  8. package/dist/Match.js.map +1 -0
  9. package/dist/Matcher.d.ts +28 -0
  10. package/dist/Matcher.d.ts.map +1 -0
  11. package/dist/Matcher.js +24 -0
  12. package/dist/Matcher.js.map +1 -0
  13. package/dist/Navigation.d.ts +10 -0
  14. package/dist/Navigation.d.ts.map +1 -0
  15. package/dist/Navigation.js +7 -0
  16. package/dist/Navigation.js.map +1 -0
  17. package/dist/Redirect.d.ts +27 -0
  18. package/dist/Redirect.d.ts.map +1 -0
  19. package/dist/Redirect.js +17 -0
  20. package/dist/Redirect.js.map +1 -0
  21. package/dist/RouteOutlet.d.ts +3 -0
  22. package/dist/RouteOutlet.d.ts.map +1 -0
  23. package/dist/RouteOutlet.js +2 -0
  24. package/dist/RouteOutlet.js.map +1 -0
  25. package/dist/ScrollRestoration.d.ts +19 -0
  26. package/dist/ScrollRestoration.d.ts.map +1 -0
  27. package/dist/ScrollRestoration.js +64 -0
  28. package/dist/ScrollRestoration.js.map +1 -0
  29. package/dist/cjs/Link.d.ts +26 -11
  30. package/dist/cjs/Link.d.ts.map +1 -1
  31. package/dist/cjs/Link.js +47 -22
  32. package/dist/cjs/Link.js.map +1 -1
  33. package/dist/cjs/Match.d.ts +33 -0
  34. package/dist/cjs/Match.d.ts.map +1 -0
  35. package/dist/cjs/Match.js +43 -0
  36. package/dist/cjs/Match.js.map +1 -0
  37. package/dist/cjs/Matcher.d.ts +28 -0
  38. package/dist/cjs/Matcher.d.ts.map +1 -0
  39. package/dist/cjs/Matcher.js +52 -0
  40. package/dist/cjs/Matcher.js.map +1 -0
  41. package/dist/cjs/Navigation.d.ts +10 -0
  42. package/dist/cjs/Navigation.d.ts.map +1 -0
  43. package/dist/cjs/Navigation.js +34 -0
  44. package/dist/cjs/Navigation.js.map +1 -0
  45. package/dist/cjs/Redirect.d.ts +27 -0
  46. package/dist/cjs/Redirect.d.ts.map +1 -0
  47. package/dist/cjs/Redirect.js +44 -0
  48. package/dist/cjs/Redirect.js.map +1 -0
  49. package/dist/cjs/ScrollRestoration.d.ts +19 -0
  50. package/dist/cjs/ScrollRestoration.d.ts.map +1 -0
  51. package/dist/cjs/ScrollRestoration.js +91 -0
  52. package/dist/cjs/ScrollRestoration.js.map +1 -0
  53. package/dist/cjs/index.d.ts +7 -3
  54. package/dist/cjs/index.d.ts.map +1 -1
  55. package/dist/cjs/index.js +7 -3
  56. package/dist/cjs/index.js.map +1 -1
  57. package/dist/cjs/matchRoutes.d.ts +8 -0
  58. package/dist/cjs/matchRoutes.d.ts.map +1 -0
  59. package/dist/cjs/matchRoutes.js +77 -0
  60. package/dist/cjs/matchRoutes.js.map +1 -0
  61. package/dist/cjs/router.d.ts +24 -63
  62. package/dist/cjs/router.d.ts.map +1 -1
  63. package/dist/cjs/router.js +22 -159
  64. package/dist/cjs/router.js.map +1 -1
  65. package/dist/index.d.ts +7 -3
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +7 -3
  68. package/dist/index.js.map +1 -1
  69. package/dist/matchRoutes.d.ts +8 -0
  70. package/dist/matchRoutes.d.ts.map +1 -0
  71. package/dist/matchRoutes.js +50 -0
  72. package/dist/matchRoutes.js.map +1 -0
  73. package/dist/router.d.ts +24 -63
  74. package/dist/router.d.ts.map +1 -1
  75. package/dist/router.js +19 -153
  76. package/dist/router.js.map +1 -1
  77. package/dist/tsconfig.cjs.build.tsbuildinfo +1 -1
  78. package/package.json +12 -10
  79. package/project.json +12 -10
  80. package/src/Link.ts +129 -39
  81. package/src/Match.ts +114 -0
  82. package/src/Matcher.ts +139 -0
  83. package/src/Navigation.ts +24 -0
  84. package/src/Redirect.ts +21 -0
  85. package/src/ScrollRestoration.ts +110 -0
  86. package/src/index.ts +7 -3
  87. package/src/matchRoutes.ts +112 -0
  88. package/src/router.ts +56 -311
  89. package/tsconfig.build.json +5 -1
  90. package/tsconfig.build.tsbuildinfo +1 -1
  91. package/tsconfig.cjs.build.json +6 -0
  92. package/tsconfig.json +6 -0
  93. package/vite.config.js +3 -0
  94. package/src/RouteMatch.ts +0 -56
  95. package/src/RouteMatcher.ts +0 -264
@@ -13,12 +13,18 @@
13
13
  {
14
14
  "path": "../dom/tsconfig.cjs.build.json"
15
15
  },
16
+ {
17
+ "path": "../error/tsconfig.cjs.build.json"
18
+ },
16
19
  {
17
20
  "path": "../fx/tsconfig.cjs.build.json"
18
21
  },
19
22
  {
20
23
  "path": "../html/tsconfig.cjs.build.json"
21
24
  },
25
+ {
26
+ "path": "../navigation/tsconfig.cjs.build.json"
27
+ },
22
28
  {
23
29
  "path": "../path/tsconfig.cjs.build.json"
24
30
  },
package/tsconfig.json CHANGED
@@ -17,12 +17,18 @@
17
17
  {
18
18
  "path": "../dom/tsconfig.build.json"
19
19
  },
20
+ {
21
+ "path": "../error/tsconfig.build.json"
22
+ },
20
23
  {
21
24
  "path": "../fx/tsconfig.build.json"
22
25
  },
23
26
  {
24
27
  "path": "../html/tsconfig.build.json"
25
28
  },
29
+ {
30
+ "path": "../navigation/tsconfig.build.json"
31
+ },
26
32
  {
27
33
  "path": "../path/tsconfig.build.json"
28
34
  },
package/vite.config.js ADDED
@@ -0,0 +1,3 @@
1
+ import config from '../../vite.config.js'
2
+
3
+ export default config
package/src/RouteMatch.ts DELETED
@@ -1,56 +0,0 @@
1
- /* eslint-disable @typescript-eslint/ban-types */
2
- import { flow } from '@effect/data/Function'
3
- import type * as Layer from '@effect/io/Layer'
4
- import * as Context from '@typed/context'
5
- import * as Fx from '@typed/fx'
6
- import type * as html from '@typed/html'
7
- import type * as Path from '@typed/path'
8
- import * as Route from '@typed/route'
9
-
10
- export interface RouteMatch<R, E, P extends string> {
11
- readonly route: Route.Route<R, E, P>
12
-
13
- readonly layout?: Fx.Fx<R, E, html.Renderable>
14
-
15
- readonly match: (params: Fx.Fx<never, never, Path.ParamsOf<P>>) => Fx.Fx<R, E, html.Renderable>
16
-
17
- readonly provideContext: (environment: Context.Context<R>) => RouteMatch<never, E, P>
18
-
19
- readonly provideService: <S>(tag: Context.Tag<S>, service: S) => RouteMatch<Exclude<R, S>, E, P>
20
-
21
- readonly provideSomeLayer: <R2, S>(
22
- layer: Layer.Layer<R2, never, S>,
23
- ) => RouteMatch<R2 | Exclude<R, S>, E, P>
24
- }
25
-
26
- export function RouteMatch<R, E, P extends string, R2, E2, R3, E3>(
27
- route: Route.Route<R, E, P>,
28
- match: (params: Fx.Fx<never, never, Path.ParamsOf<P>>) => Fx.Fx<R2, E2, html.Renderable>,
29
- layout?: Fx.Fx<R3, E3, html.Renderable>,
30
- ): RouteMatch<R | R2 | R3, E | E2 | E3, P> {
31
- const routeMatch: RouteMatch<R | R2 | R3, E | E2 | E3, P> = {
32
- route,
33
- match,
34
- layout,
35
- provideContext: (env) =>
36
- RouteMatch(
37
- Route.provideContext(env)(route),
38
- flow(match, Fx.provideSomeContext(env)),
39
- layout ? Fx.provideContext(env)(layout) : undefined,
40
- ),
41
- provideService: (tag, service) =>
42
- RouteMatch(
43
- Route.provideService(tag, service)(route),
44
- flow(match, Fx.provideService(tag, service)),
45
- layout ? Fx.provideService(tag, service)(layout) : undefined,
46
- ),
47
- provideSomeLayer: (layer) =>
48
- RouteMatch(
49
- Route.provideSomeLayer(layer)(route),
50
- flow(match, Fx.provideSomeLayer(layer)),
51
- layout ? Fx.provideSomeLayer(layer)(layout) : undefined,
52
- ),
53
- }
54
-
55
- return routeMatch
56
- }
@@ -1,264 +0,0 @@
1
- /* eslint-disable @typescript-eslint/ban-types */
2
- import { identity, pipe } from '@effect/data/Function'
3
- import * as Option from '@effect/data/Option'
4
- import * as Effect from '@effect/io/Effect'
5
- import * as Fiber from '@effect/io/Fiber'
6
- import type * as Layer from '@effect/io/Layer'
7
- import type * as Context from '@typed/context'
8
- import * as Fx from '@typed/fx'
9
- import type * as html from '@typed/html'
10
- import { RenderContext } from '@typed/html'
11
- import type * as Path from '@typed/path'
12
- import type * as Route from '@typed/route'
13
-
14
- import { RouteMatch } from './RouteMatch.js'
15
- import { Redirect, redirectTo, Router } from './router.js'
16
-
17
- export interface RouteMatcher<R = never, E = never> {
18
- // Where things are actually stored immutably
19
- readonly routes: ReadonlyMap<Route.Route<any, any, any>, RouteMatch<any, any, any>>
20
-
21
- // Add Routes
22
-
23
- readonly match: <R2, E2, P extends string, R3, E3>(
24
- route: Route.Route<R2, E2, P>,
25
- f: (params: Path.ParamsOf<P>) => Fx.Fx<R3, E3, html.Renderable>,
26
- ) => RouteMatcher<R | R2 | R3, E | E2 | E3>
27
-
28
- readonly matchFx: <R2, E2, P extends string, R3, E3>(
29
- route: Route.Route<R2, E2, P>,
30
- f: (params: Fx.Fx<never, never, Path.ParamsOf<P>>) => Fx.Fx<R3, E3, html.Renderable>,
31
- ) => RouteMatcher<R | R2 | R3, E | E2 | E3>
32
-
33
- readonly matchEffect: <R2, E2, P extends string, R3, E3>(
34
- route: Route.Route<R2, E2, P>,
35
- f: (params: Path.ParamsOf<P>) => Effect.Effect<R3, E3, html.Renderable>,
36
- ) => RouteMatcher<R | R2 | R3, E | E2 | E3>
37
-
38
- // Add Layout
39
-
40
- readonly withLayout: <R2, E2>(fx: Fx.Fx<R2, E2, html.Renderable>) => RouteMatcher<R | R2, E | E2>
41
-
42
- // Provide resources
43
-
44
- readonly provideContext: <R2>(environment: Context.Context<R2>) => RouteMatcher<Exclude<R, R2>, E>
45
-
46
- readonly provideService: <R2>(
47
- tag: Context.Tag<R2>,
48
- service: R2,
49
- ) => RouteMatcher<Exclude<R, R2>, E>
50
-
51
- readonly provideSomeLayer: <R2, S>(
52
- layer: Layer.Layer<R2, never, S>,
53
- ) => RouteMatcher<Exclude<R, S> | R2, E>
54
-
55
- // Runners that turn a RouterMatcher back into an Fx.
56
- // Error handling should be handled after converting to an Fx for maximum flexibility.
57
-
58
- readonly notFound: <R2, E2, R3 = never, E3 = never>(
59
- f: (path: string) => Fx.Fx<R2, E2, html.Renderable>,
60
- options?: FallbackOptions<R3, E3>,
61
- ) => Fx.Fx<Router | R | R2 | R3, E | E2 | E3, html.Renderable>
62
-
63
- readonly notFoundEffect: <R2, E2, R3 = never, E3 = never>(
64
- f: (path: string) => Effect.Effect<R2, E2, html.Renderable>,
65
- options?: FallbackOptions<R3, E3>,
66
- ) => Fx.Fx<Router | R | R2 | R3, E | E2 | E3, html.Renderable>
67
-
68
- readonly redirectTo: <R2, E2, P extends string>(
69
- route: Route.Route<R2, E2, P>,
70
- ...params: [keyof Path.ParamsOf<P>] extends [never]
71
- ? // eslint-disable-next-line @typescript-eslint/ban-types
72
- [{}?]
73
- : [(path: string) => Path.ParamsOf<P>]
74
- ) => Fx.Fx<Router | R | R2, E | Redirect, html.Renderable>
75
-
76
- readonly run: Fx.Fx<Router | R, E | Redirect, html.Renderable | null>
77
- }
78
-
79
- export interface FallbackOptions<R, E> {
80
- readonly layout?: Fx.Fx<R, E, html.Renderable>
81
- }
82
-
83
- export function RouteMatcher<R, E>(routes: RouteMatcher<R, E>['routes']): RouteMatcher<R, E> {
84
- const matcher: RouteMatcher<R, E> = {
85
- routes,
86
- matchFx: (route, f) => RouteMatcher(new Map(routes).set(route, RouteMatch(route, f))),
87
- match: (route, f) =>
88
- RouteMatcher(new Map(routes).set(route, RouteMatch(route, Fx.switchMap(f)))),
89
- matchEffect: (route, f) =>
90
- RouteMatcher(new Map(routes).set(route, RouteMatch(route, Fx.switchMapEffect(f)))),
91
- withLayout: (layout) =>
92
- RouteMatcher(
93
- new Map(
94
- Array.from(routes).map(([k, match]) => [
95
- k,
96
- RouteMatch(match.route, match.match, match.layout ?? layout),
97
- ]),
98
- ),
99
- ),
100
- provideContext: (environment) =>
101
- RouteMatcher(new Map(Array.from(routes).map(([k, v]) => [k, v.provideContext(environment)]))),
102
- provideService: (tag, service) =>
103
- RouteMatcher(
104
- new Map(Array.from(routes).map(([k, v]) => [k, v.provideService(tag, service)])),
105
- ),
106
- provideSomeLayer: (layer) =>
107
- RouteMatcher(new Map(Array.from(routes).map(([k, v]) => [k, v.provideSomeLayer(layer)]))),
108
- notFound: <R2, E2, R3, E3>(
109
- f: (path: string) => Fx.Fx<R2, E2, html.Renderable>,
110
- options: FallbackOptions<R3, E3> = {},
111
- ) =>
112
- Router.withFx((router) =>
113
- Fx.gen(function* ($) {
114
- const { outlet } = yield* $(Router)
115
- const { environment } = yield* $(RenderContext)
116
- // Create stable references to the route matchers
117
- const matchers = Array.from(routes.values()).map(
118
- (v) => [v, runRouteMatch(router, v)] as const,
119
- )
120
-
121
- const renderFallback = Fx.switchMap(f)(router.currentPath)
122
-
123
- let previousFiber: Fiber.RuntimeFiber<any, any> | undefined
124
- let previousLayout: Fx.Fx<any, any, html.Renderable> | undefined
125
- let previousRender: Fx.Fx<any, any, html.Renderable> | undefined
126
-
127
- const samplePreviousValues = () => ({
128
- fiber: previousFiber,
129
- layout: previousLayout,
130
- render: previousRender,
131
- })
132
-
133
- // This function helps us to ensure shared layouts are only rendered once
134
- // and the outlet content is changed
135
- const verifyShouldRerender = (
136
- render: Fx.Fx<any, any, html.Renderable>,
137
- layout?: Fx.Fx<any, any, html.Renderable>,
138
- ): Effect.Effect<any, any, Option.Option<Fx.Fx<any, any, html.Renderable>>> =>
139
- Effect.gen(function* ($) {
140
- const previous = samplePreviousValues()
141
-
142
- // Update the previous values
143
- previousRender = render
144
- previousLayout = layout
145
-
146
- // Skip rerendering if the render function is the same
147
- if (previous.render === render && previous.layout === layout) {
148
- return Option.none()
149
- }
150
-
151
- // Interrupt the previous fiber if it exists
152
- if (previous.render !== render && previous.fiber) {
153
- yield* $(Fiber.interrupt(previous.fiber))
154
- previousFiber = undefined
155
- }
156
-
157
- // If we have a layout, we need to render it and use the route outlet.
158
- if (layout) {
159
- // Render into the route outlet
160
- if (previous.render !== render) {
161
- previousFiber = yield* $(
162
- pipe(
163
- render,
164
- Fx.observe(outlet.set),
165
- Effect.catchAll((e: Redirect) =>
166
- Redirect.is(e) ? router.currentPath.set(e.path) : Effect.fail(e),
167
- ),
168
- Effect.onError(outlet.error),
169
- Effect.forkDaemon,
170
- ),
171
- )
172
- }
173
-
174
- return Option.some(layout)
175
- }
176
-
177
- // If we don't have a layout, but we did, we need to clear the outlet
178
- if (previous.layout) {
179
- yield* $(outlet.set(null))
180
- }
181
-
182
- // Otherwise use the render function directly
183
- return Option.some(render)
184
- })
185
-
186
- return pipe(
187
- router.currentPath,
188
- environment === 'browser' ? identity : Fx.take(1),
189
- Fx.switchMapEffect((path) =>
190
- Effect.gen(function* ($) {
191
- const currentParams = yield* $(router.route.match(path))
192
-
193
- if (Option.isNone(currentParams)) {
194
- return Option.none()
195
- }
196
-
197
- const matchedPath: string = router.route.make(currentParams.value)
198
- const currentPath =
199
- matchedPath === '/' ? path : path.replace(matchedPath, '') || '/'
200
-
201
- yield* $(Effect.logInfo(`[@typed/router] Matching path: ${currentPath}`))
202
-
203
- // Attempt to find the best match
204
- for (const [match, render] of matchers) {
205
- yield* $(Effect.logInfo(`[@typed/router] Matching against: ${match.route.path}`))
206
-
207
- const result = yield* $(match.route.match(currentPath))
208
-
209
- if (Option.isSome(result)) {
210
- yield* $(Effect.logInfo(`[@typed/router] Matched against: ${match.route.path}`))
211
-
212
- return yield* $(verifyShouldRerender(render, match.layout))
213
- }
214
- }
215
-
216
- yield* $(Effect.logInfo(`[@typed/router] Rendering fallback`))
217
-
218
- // If we didn't find a match, render the not found page
219
- return yield* $(verifyShouldRerender(renderFallback, options.layout))
220
- }),
221
- ),
222
- Fx.compact,
223
- Fx.skipRepeats, // Stable render references are used to avoid mounting the same component twice
224
- Fx.switchLatest,
225
- )
226
- }),
227
- ),
228
- notFoundEffect: (f) => matcher.notFound((path) => Fx.fromEffect(f(path))),
229
- redirectTo: ((route, ...params) =>
230
- matcher.notFound(() => redirectTo.fx(route, ...params))) as RouteMatcher<R, E>['redirectTo'],
231
- run: Fx.suspend(() => matcher.notFound(() => Fx.succeed(null))),
232
- }
233
-
234
- return matcher
235
- }
236
-
237
- export namespace RouteMatcher {
238
- export const empty = RouteMatcher<never, never>(new Map())
239
-
240
- export const concat = <R, E, R2, E2>(
241
- matcher: RouteMatcher<R, E>,
242
- matcher2: RouteMatcher<R2, E2>,
243
- ): RouteMatcher<R | R2, E | E2> => RouteMatcher(new Map([...matcher.routes, ...matcher2.routes]))
244
- }
245
-
246
- export const { matchFx, match, matchEffect } = RouteMatcher<never, never>(new Map())
247
-
248
- function runRouteMatch<R, E, P extends string>(
249
- router: Router,
250
- { route, match }: RouteMatch<R, E, P>,
251
- ): Fx.Fx<R, E, html.Renderable> {
252
- return Fx.gen(function* ($) {
253
- const env = yield* $(Effect.context<R>())
254
- const nestedRouter = router.define(route)
255
- const params = pipe(nestedRouter.params, Fx.provideContext(env))
256
- const render = pipe(
257
- match(params as unknown as Fx.Fx<never, never, Path.ParamsOf<P>>),
258
- Router.provideFx(nestedRouter as Router),
259
- Fx.provideContext(env),
260
- )
261
-
262
- return render
263
- })
264
- }