forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {type Route, type RouteParams} from './types'
2
3export class Router<T extends Record<string, any>> {
4 routes: [string, Route][] = []
5 constructor(description: Record<keyof T, string | string[]>) {
6 for (const [screen, pattern] of Object.entries(description)) {
7 if (typeof pattern === 'string') {
8 this.routes.push([screen, createRoute(pattern)])
9 } else {
10 pattern.forEach(subPattern => {
11 this.routes.push([screen, createRoute(subPattern)])
12 })
13 }
14 }
15 }
16
17 matchName(name: keyof T | (string & {})): Route | undefined {
18 for (const [screenName, route] of this.routes) {
19 if (screenName === name) {
20 return route
21 }
22 }
23 }
24
25 matchPath(path: string): [string, RouteParams] {
26 let name = 'NotFound'
27 let params: RouteParams = {}
28 for (const [screenName, route] of this.routes) {
29 const res = route.match(path)
30 if (res) {
31 name = screenName
32 params = res.params
33 break
34 }
35 }
36 return [name, params]
37 }
38}
39
40function createRoute(pattern: string): Route {
41 const pathParamNames: Set<string> = new Set()
42 let matcherReInternal = pattern.replace(/:([\w]+)/g, (_m, name) => {
43 pathParamNames.add(name)
44 return `(?<${name}>[^/]+)`
45 })
46 const matcherRe = new RegExp(`^${matcherReInternal}([?]|$)`, 'i')
47 return {
48 match(path) {
49 const {pathname, searchParams} = new URL(path, 'http://throwaway.com')
50 const addedParams = Object.fromEntries(searchParams.entries())
51
52 const res = matcherRe.exec(pathname)
53 if (res) {
54 return {params: Object.assign(addedParams, res.groups || {})}
55 }
56 return undefined
57 },
58 build(params = {}) {
59 const str = pattern.replace(
60 /:([\w]+)/g,
61 (_m, name) => params[encodeURIComponent(name)] || 'undefined',
62 )
63
64 let hasQp = false
65 const qp = new URLSearchParams()
66 for (const paramName in params) {
67 if (!pathParamNames.has(paramName)) {
68 qp.set(paramName, params[paramName])
69 hasQp = true
70 }
71 }
72
73 return str + (hasQp ? `?${qp.toString()}` : '')
74 },
75 }
76}