@typed/router 0.31.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 -69
- 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 -135
- 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 -282
- package/src/RouteGuard.ts +0 -217
- package/src/RouteMatch.ts +0 -104
package/src/CurrentRoute.ts
CHANGED
|
@@ -1,332 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* @since 1.0.0
|
|
32
|
-
*/
|
|
33
|
-
export function makeCurrentRoute<R extends Route.Route.Any>(
|
|
34
|
-
route: R,
|
|
35
|
-
parent: Option.Option<CurrentRoute> = Option.none()
|
|
36
|
-
): CurrentRoute {
|
|
37
|
-
return {
|
|
38
|
-
route,
|
|
39
|
-
parent
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @since 1.0.0
|
|
45
|
-
*/
|
|
46
|
-
export function layer<R extends Route.Route.Any>(
|
|
47
|
-
route: R,
|
|
48
|
-
parent: Option.Option<CurrentRoute> = Option.none()
|
|
49
|
-
): Layer.Layer<CurrentRoute> {
|
|
50
|
-
return CurrentRoute.layer(makeCurrentRoute(route, parent))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* @since 1.0.0
|
|
55
|
-
*/
|
|
56
|
-
export const CurrentParams: RefSubject.Filtered<
|
|
57
|
-
Readonly<Record<string, string | ReadonlyArray<string>>>,
|
|
58
|
-
never,
|
|
59
|
-
Navigation.Navigation | CurrentRoute
|
|
60
|
-
> = RefSubject.filteredFromTag(
|
|
61
|
-
Navigation.Navigation,
|
|
62
|
-
(nav) =>
|
|
63
|
-
RefSubject.filterMapEffect(
|
|
64
|
-
nav.currentEntry,
|
|
65
|
-
(e) => CurrentRoute.with(({ route }) => route.match(Navigation.getCurrentPathFromUrl(e.url)))
|
|
66
|
-
)
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* @since 1.0.0
|
|
71
|
-
*/
|
|
72
|
-
export const withCurrentRoute: {
|
|
73
|
-
<R extends Route.Route.Any>(
|
|
74
|
-
route: R
|
|
75
|
-
): <A, E, R>(
|
|
76
|
-
effect: Effect.Effect<A, E, R>
|
|
77
|
-
) => Effect.Effect<A, E, Exclude<R, CurrentRoute>>
|
|
78
|
-
|
|
79
|
-
<A, E, R, R_ extends Route.Route.Any>(
|
|
80
|
-
effect: Effect.Effect<A, E, R>,
|
|
81
|
-
route: R_
|
|
82
|
-
): Effect.Effect<A, E, Exclude<R, CurrentRoute>>
|
|
83
|
-
} = dual(
|
|
84
|
-
2,
|
|
85
|
-
<A, E, R, R_ extends Route.Route.Any>(
|
|
86
|
-
effect: Effect.Effect<A, E, R>,
|
|
87
|
-
route: R_
|
|
88
|
-
): Effect.Effect<A, E, Exclude<R, CurrentRoute>> =>
|
|
89
|
-
Effect.contextWithEffect((ctx) => {
|
|
90
|
-
const parent = Context.getOption(ctx, CurrentRoute)
|
|
91
|
-
|
|
92
|
-
if (Option.isNone(parent)) {
|
|
93
|
-
return pipe(effect, CurrentRoute.provide(makeCurrentRoute(route)))
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return pipe(
|
|
97
|
-
effect,
|
|
98
|
-
CurrentRoute.provide(
|
|
99
|
-
makeCurrentRoute(parent.value.route.concat(route), parent)
|
|
100
|
-
)
|
|
101
|
-
)
|
|
102
|
-
})
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
const makeHref_ = (
|
|
106
|
-
currentPath: string,
|
|
107
|
-
currentRoute: Route.Route.Any,
|
|
108
|
-
route: Route.Route.Any,
|
|
109
|
-
params: {} = {}
|
|
110
|
-
): Option.Option<string> => {
|
|
111
|
-
const currentMatch = currentRoute.match(currentPath)
|
|
112
|
-
if (Option.isNone(currentMatch)) return Option.none()
|
|
113
|
-
|
|
114
|
-
const fullRoute = currentRoute.concat(route)
|
|
115
|
-
const fullParams = { ...currentMatch.value, ...params }
|
|
116
|
-
|
|
117
|
-
return Option.some(fullRoute.interpolate(fullParams as any))
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* @since 1.0.0
|
|
122
|
-
*/
|
|
123
|
-
export function makeHref<const R extends Route.Route.Any>(
|
|
124
|
-
route: R,
|
|
125
|
-
...[params]: Route.Route.ParamsList<R>
|
|
126
|
-
): RefSubject.Filtered<string, never, Navigation.Navigation | CurrentRoute>
|
|
127
|
-
export function makeHref<const R extends Route.Route.Any>(
|
|
128
|
-
route: R,
|
|
129
|
-
params: Route.Route.Params<R>
|
|
130
|
-
): RefSubject.Filtered<string, never, Navigation.Navigation | CurrentRoute>
|
|
131
|
-
|
|
132
|
-
export function makeHref<const R extends Route.Route.Any>(
|
|
133
|
-
route: R,
|
|
134
|
-
...[params]: Route.Route.ParamsList<R>
|
|
135
|
-
): RefSubject.Filtered<string, never, Navigation.Navigation | CurrentRoute> {
|
|
136
|
-
return RefSubject.filterMapEffect(Navigation.CurrentPath, (currentPath) =>
|
|
137
|
-
Effect.map(
|
|
138
|
-
CurrentRoute,
|
|
139
|
-
(currentRoute): Option.Option<string> => makeHref_(currentPath, currentRoute.route, route, params)
|
|
140
|
-
))
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const isActive_ = (
|
|
144
|
-
currentPath: string,
|
|
145
|
-
currentRoute: Route.Route.Any,
|
|
146
|
-
route: Route.Route.Any,
|
|
147
|
-
params: any = {}
|
|
148
|
-
): boolean => {
|
|
149
|
-
const currentMatch = currentRoute.match(currentPath)
|
|
150
|
-
if (Option.isNone(currentMatch)) return false
|
|
151
|
-
|
|
152
|
-
const fullRoute = currentRoute.concat(route)
|
|
153
|
-
const fullParams = { ...currentMatch.value, ...params }
|
|
154
|
-
const href: string = fullRoute.interpolate(fullParams as any)
|
|
155
|
-
const [currentPathname, currentQuery] = splitByQuery(currentPath)
|
|
156
|
-
const [hrefPathname, hrefQuery] = splitByQuery(href)
|
|
157
|
-
|
|
158
|
-
return (
|
|
159
|
-
(fullRoute.routeOptions.end
|
|
160
|
-
? currentPathname === hrefPathname
|
|
161
|
-
: currentPathname.startsWith(hrefPathname)) &&
|
|
162
|
-
compareQueries(currentQuery, hrefQuery)
|
|
163
|
-
)
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import * as ServiceMap from "effect/ServiceMap";
|
|
4
|
+
import { Navigation } from "@typed/navigation/Navigation";
|
|
5
|
+
import { Parse, type Route } from "./Route.js";
|
|
6
|
+
|
|
7
|
+
export interface CurrentRouteTree {
|
|
8
|
+
readonly route: Route<string, any>;
|
|
9
|
+
readonly parent?: CurrentRouteTree | undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class CurrentRoute extends ServiceMap.Service<CurrentRoute, CurrentRouteTree>()(
|
|
13
|
+
"@typed/router/CurrentRoute",
|
|
14
|
+
{
|
|
15
|
+
make: Effect.map(Navigation.base, (base) => ({ route: Parse(base) })),
|
|
16
|
+
},
|
|
17
|
+
) {
|
|
18
|
+
static readonly Default = Layer.effect(CurrentRoute, CurrentRoute.make);
|
|
19
|
+
|
|
20
|
+
static readonly extend = (route: Route.Any) =>
|
|
21
|
+
Layer.unwrap(
|
|
22
|
+
Effect.gen(function* () {
|
|
23
|
+
const services = yield* Effect.services<never>();
|
|
24
|
+
const parent = ServiceMap.getOrUndefined(services, CurrentRoute);
|
|
25
|
+
return Layer.succeed(CurrentRoute, {
|
|
26
|
+
route,
|
|
27
|
+
parent,
|
|
28
|
+
});
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
164
31
|
}
|
|
165
|
-
|
|
166
|
-
function compareQueries(currentQuery: string, hrefQuery: string) {
|
|
167
|
-
// if hrefQuery is empty, it means that the href is a pathname only
|
|
168
|
-
if (!hrefQuery) return true
|
|
169
|
-
// if currentQuery is empty, there is no match at this point
|
|
170
|
-
if (!currentQuery) return false
|
|
171
|
-
// if the queries are equal, there is a match
|
|
172
|
-
if (currentQuery === hrefQuery) return true
|
|
173
|
-
|
|
174
|
-
const currentQueryParams = new URLSearchParams(currentQuery)
|
|
175
|
-
const hrefQueryParams = new URLSearchParams(hrefQuery)
|
|
176
|
-
|
|
177
|
-
for (const key of hrefQueryParams.keys()) {
|
|
178
|
-
const a = currentQueryParams.getAll(key).sort()
|
|
179
|
-
const b = hrefQueryParams.getAll(key).sort()
|
|
180
|
-
|
|
181
|
-
if (a.length !== b.length || !b.every((bx, i) => a[i] === bx)) return false
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return true
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function splitByQuery(path: string) {
|
|
188
|
-
const queryIndex = path.indexOf("?")
|
|
189
|
-
if (queryIndex > -1) {
|
|
190
|
-
const pathname = path.slice(0, queryIndex)
|
|
191
|
-
const query = path.slice(queryIndex + 1).trim()
|
|
192
|
-
return [pathname, query] as const
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return [path, ""] as const
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* @since 1.0.0
|
|
200
|
-
*/
|
|
201
|
-
export function isActive<R extends Route.Route.Any>(
|
|
202
|
-
route: R,
|
|
203
|
-
...[params]: Route.Route.ParamsList<R>
|
|
204
|
-
): RefSubject.Computed<boolean, never, Navigation.Navigation | CurrentRoute>
|
|
205
|
-
export function isActive<R extends Route.Route.Any>(
|
|
206
|
-
route: R,
|
|
207
|
-
params: Route.Route.Params<R>
|
|
208
|
-
): RefSubject.Computed<boolean, never, Navigation.Navigation | CurrentRoute>
|
|
209
|
-
export function isActive<R extends Route.Route.Any>(
|
|
210
|
-
route: R,
|
|
211
|
-
...[params]: Route.Route.ParamsList<R>
|
|
212
|
-
): RefSubject.Computed<boolean, never, Navigation.Navigation | CurrentRoute> {
|
|
213
|
-
return RefSubject.mapEffect(
|
|
214
|
-
Navigation.CurrentPath,
|
|
215
|
-
(currentPath) =>
|
|
216
|
-
CurrentRoute.with((currentRoute): boolean => isActive_(currentPath, currentRoute.route, route, params))
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* @since 1.0.0
|
|
222
|
-
*/
|
|
223
|
-
export function decode<R extends Route.Route.Any>(
|
|
224
|
-
route: R
|
|
225
|
-
): Fx.RefSubject.Filtered<
|
|
226
|
-
Route.Route.Type<R>,
|
|
227
|
-
Route.RouteDecodeError<R>,
|
|
228
|
-
Navigation.Navigation | CurrentRoute | Route.Route.Context<R>
|
|
229
|
-
> {
|
|
230
|
-
return RefSubject.filteredFromTag(
|
|
231
|
-
Navigation.Navigation,
|
|
232
|
-
(nav) =>
|
|
233
|
-
RefSubject.filterMapEffect(
|
|
234
|
-
nav.currentEntry,
|
|
235
|
-
(e) =>
|
|
236
|
-
Effect.flatMap(CurrentRoute, ({ route: parent }) =>
|
|
237
|
-
Effect.optionFromOptional(
|
|
238
|
-
Route.decode(
|
|
239
|
-
parent.concat(route) as R,
|
|
240
|
-
Navigation.getCurrentPathFromUrl(e.url)
|
|
241
|
-
)
|
|
242
|
-
))
|
|
243
|
-
)
|
|
244
|
-
)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* @since 1.0.0
|
|
249
|
-
*/
|
|
250
|
-
export const browser: Layer.Layer<CurrentRoute, never, Document.Document> = CurrentRoute.layer(
|
|
251
|
-
Effect.gen(function*() {
|
|
252
|
-
const document = yield* Document.Document
|
|
253
|
-
const base = document.querySelector("base")
|
|
254
|
-
const baseHref = base ? getBasePathname(base.href) : "/"
|
|
255
|
-
|
|
256
|
-
return {
|
|
257
|
-
route: Route.parse(baseHref),
|
|
258
|
-
parent: Option.none()
|
|
259
|
-
}
|
|
260
|
-
})
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
function getBasePathname(base: string): string {
|
|
264
|
-
try {
|
|
265
|
-
const url = new URL(base)
|
|
266
|
-
return url.pathname
|
|
267
|
-
} catch {
|
|
268
|
-
return base
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* @since 1.0.0
|
|
274
|
-
*/
|
|
275
|
-
export const server = (base: string = "/"): Layer.Layer<CurrentRoute> =>
|
|
276
|
-
CurrentRoute.layer({ route: Route.parse(base), parent: Option.none() })
|
|
277
|
-
|
|
278
|
-
const getSearchParams = (destination: Navigation.Destination) => destination.url.searchParams
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* @since 1.0.0
|
|
282
|
-
*/
|
|
283
|
-
export const CurrentSearchParams: RefSubject.Computed<
|
|
284
|
-
URLSearchParams,
|
|
285
|
-
never,
|
|
286
|
-
Navigation.Navigation
|
|
287
|
-
> = RefSubject.map(Navigation.CurrentEntry, getSearchParams)
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* @since 1.0.0
|
|
291
|
-
*/
|
|
292
|
-
export const CurrentState = RefSubject.computedFromTag(
|
|
293
|
-
Navigation.Navigation,
|
|
294
|
-
(n) => RefSubject.map(n.currentEntry, (e) => e.state)
|
|
295
|
-
)
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* @since 1.0.0
|
|
299
|
-
*/
|
|
300
|
-
export type NavigateOptions<R extends Route.Route.Any> = Route.Route.ParamsAreOptional<R> extends true ? [
|
|
301
|
-
options?: Navigation.NavigateOptions & {
|
|
302
|
-
readonly params?: Route.Route.Params<R> | undefined
|
|
303
|
-
} & {
|
|
304
|
-
readonly relative?: boolean
|
|
305
|
-
}
|
|
306
|
-
]
|
|
307
|
-
: [
|
|
308
|
-
options: Navigation.NavigateOptions & {
|
|
309
|
-
readonly params: Route.Route.Params<R>
|
|
310
|
-
} & {
|
|
311
|
-
readonly relative?: boolean
|
|
312
|
-
}
|
|
313
|
-
]
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* @since 1.0.0
|
|
317
|
-
*/
|
|
318
|
-
export const navigate = <R extends Route.Route.Any>(
|
|
319
|
-
route: R,
|
|
320
|
-
...[options]: NavigateOptions<R>
|
|
321
|
-
): Effect.Effect<
|
|
322
|
-
Navigation.Destination,
|
|
323
|
-
Navigation.NavigationError | Cause.NoSuchElementException,
|
|
324
|
-
Navigation.Navigation | CurrentRoute
|
|
325
|
-
> =>
|
|
326
|
-
Effect.gen(function*(_) {
|
|
327
|
-
const params = options?.params ?? ({} as Route.Route.Params<R>)
|
|
328
|
-
const path = options?.relative === false
|
|
329
|
-
? route.interpolate(params)
|
|
330
|
-
: yield* _(makeHref<R>(route, params))
|
|
331
|
-
return yield* _(Navigation.navigate(path))
|
|
332
|
-
})
|