summaryrefslogtreecommitdiff
path: root/packages/web-util/src/utils/route.ts
blob: 4f8a020f69c2a47f4e9e0f9083a3e94de5466d25 (plain)
1
2
3
4
5
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/*
 This file is part of GNU Taler
 (C) 2022-2024 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU General Public License as published by the Free Software
 Foundation; either version 3, or (at your option) any later version.

 GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

 You should have received a copy of the GNU General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */

declare const __location: unique symbol;
/**
 * special string that defined a location in the application
 *
 * this help to prevent wrong path
 */
export type AppLocation = string & {
  [__location]: true;
};

export type EmptyObject = Record<string, never>;

export function urlPattern<
  T extends Record<string, string | undefined> = EmptyObject,
>(pattern: RegExp, reverse: (p: T) => string): RouteDefinition<T> {
  const url = reverse as (p: T) => AppLocation;
  return {
    pattern: new RegExp(pattern),
    url,
  };
}

/**
 * defines a location in the app
 *
 * pattern: how a string will trigger this location
 * url(): how a state serialize to a location
 */

export type ObjectOf<T> = Record<string, T> | EmptyObject;

export type RouteDefinition<
  T extends ObjectOf<string | undefined> = EmptyObject,
> = {
  pattern: RegExp;
  url: (p: T) => AppLocation;
};

const nullRountDef = {
  pattern: new RegExp(/.*/),
  url: () => "" as AppLocation,
};
export function buildNullRoutDefinition<
  T extends ObjectOf<string>,
>(): RouteDefinition<T> {
  return nullRountDef;
}

/**
 * Search path in the pageList
 * get the values from the path found
 * add params from searchParams
 *
 * @param path
 * @param params
 */
export function findMatch<T extends ObjectOf<RouteDefinition>>(
  pagesMap: T,
  pageList: Array<keyof T>,
  path: string,
  params: Record<string, string>,
): Location<T> | undefined {
  for (let idx = 0; idx < pageList.length; idx++) {
    const name = pageList[idx];
    const found = pagesMap[name].pattern.exec(path);
    if (found !== null) {
      const values = {} as Record<string, unknown>;

      Object.entries(params).forEach(([key, value]) => {
        values[key] = value;
      });

      if (found.groups !== undefined) {
        Object.entries(found.groups).forEach(([key, value]) => {
          values[key] = value;
        });
      }

      // @ts-expect-error values is a map string which is equivalent to the RouteParamsType
      return { name, parent: pagesMap, values };
    }
  }
  return undefined;
}

/**
 * get the type of the params of a location
 *
 */
type RouteParamsType<
  RouteType,
  Key extends keyof RouteType,
> = RouteType[Key] extends RouteDefinition<infer ParamType> ? ParamType : never;

/**
 * Helps to create a map of a type with the key
 */
type MapKeyValue<Type> = {
  [Key in keyof Type]: Key extends string
  ? {
    parent: Type;
    name: Key;
    values: RouteParamsType<Type, Key>;
  }
  : never;
};

/**
 * create a enumeration of value of a mapped type
 */
type EnumerationOf<T> = T[keyof T];

export type Location<T> = EnumerationOf<MapKeyValue<T>>;