@esportsplus/routing 0.0.49 → 0.1.1

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.
@@ -1,330 +0,0 @@
1
- import { METHOD_NAME_ALL, PATH_ERROR } from './constants';
2
- import { Trie } from './trie';
3
- import { HandlerMap, HandlerMetadata, Matcher, Method, ParameterMap, ParameterMetadata, Path, Result, StaticMap } from './types';
4
-
5
-
6
- const NULL_MATCHER = [/^$/, [], Object.create(null)] as Matcher<any>;
7
-
8
-
9
- let empty: any[] = [],
10
- wildcardCache: Record<Path, RegExp> = Object.create(null);
11
-
12
-
13
- function buildMatcherFromPreprocessedRoutes<T>(routes: [Path, HandlerMap<T>[]][]): Matcher<T> {
14
- if (routes.length === 0) {
15
- return NULL_MATCHER;
16
- }
17
-
18
- let handlerMetadata: HandlerMetadata<T>[] = [],
19
- routesWithStaticPathFlag = routes
20
- .map(
21
- (route) => [!/\*|\/:/.test(route[0]), ...route] as [boolean, Path, HandlerMap<T>[]]
22
- )
23
- .sort(([isStaticA, pathA], [isStaticB, pathB]) =>
24
- isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length
25
- ),
26
- staticMap: StaticMap<T> = Object.create(null),
27
- trie = new Trie();
28
-
29
- for (let i = 0, j = -1, n = routesWithStaticPathFlag.length; i < n; i++) {
30
- let [ validatePathOnly, path, handlers ] = routesWithStaticPathFlag[i];
31
-
32
- if (validatePathOnly) {
33
- staticMap[path] = [ handlers.map(([h]) => [h, Object.create(null)]), empty ]
34
- }
35
- else {
36
- j++
37
- }
38
-
39
- let parameterMetadata: ParameterMetadata;
40
-
41
- try {
42
- parameterMetadata = trie.insert(path, j, validatePathOnly)
43
- }
44
- catch (e) {
45
- throw e === PATH_ERROR ? new Error(`Routing: '${path}' is not supported`) : e
46
- }
47
-
48
- if (validatePathOnly) {
49
- continue
50
- }
51
-
52
- handlerMetadata[j] = handlers.map(([h, i]) => {
53
- let paramIndexMap: ParameterMap = Object.create(null);
54
-
55
- i -= 1;
56
-
57
- for (; i >= 0; i--) {
58
- let [key, value] = parameterMetadata[i];
59
-
60
- paramIndexMap[key] = value;
61
- }
62
-
63
- return [h, paramIndexMap];
64
- })
65
- }
66
-
67
- let [regexp, handlerReplacementMap, parameterReplacementMap] = trie.build();
68
-
69
- for (let i = 0, n = handlerMetadata.length; i < n; i++) {
70
- let metadata = handlerMetadata[i];
71
-
72
- for (let j = 0, n = metadata.length; j < n; j++) {
73
- let map = metadata[j]?.[1];
74
-
75
- if (!map) {
76
- continue;
77
- }
78
-
79
- for (let key in map) {
80
- map[key] = parameterReplacementMap[ map[key] ];
81
- }
82
- }
83
- }
84
-
85
- let handlerMap: HandlerMetadata<T>[] = [];
86
-
87
- for (let i = 0, n = handlerReplacementMap.length; i < n; i++) {
88
- handlerMap[i] = handlerMetadata[handlerReplacementMap[i]];
89
- }
90
-
91
- return [regexp, handlerMap, staticMap] as Matcher<T>;
92
- }
93
-
94
- function buildWildcardRegExp(path: Path): RegExp {
95
- return (wildcardCache[path] ??= new RegExp(
96
- path === '*'
97
- ? ''
98
- : `^${path.replace(/\/\*$|([.\\+*[^\]$()])/g, (_, char) =>
99
- char ? `\\${char}` : '(?:|/.*)'
100
- )}$`
101
- ))
102
- }
103
-
104
- function clearWildcardCache() {
105
- wildcardCache = Object.create(null);
106
- }
107
-
108
- function findMiddleware<T>(middleware: Record<string, T[]> | undefined, path: Path): T[] | undefined {
109
- if (!middleware) {
110
- return undefined;
111
- }
112
-
113
- let keys = Object.keys(middleware).sort((a, b) => b.length - a.length);
114
-
115
- for (let i = 0, n = keys.length; i < n; i++) {
116
- if (buildWildcardRegExp(keys[i]).test(path)) {
117
- return [...middleware[keys[i]]];
118
- }
119
- }
120
-
121
- return undefined;
122
- }
123
-
124
- // If path is `/api/animals/:type?` [`/api/animals`, `/api/animals/:type`] else null
125
- function findOptionalParameters(path: Path): string[] | null {
126
- if (!path.match(/\:.+\?$/)) {
127
- return null
128
- }
129
-
130
- let base = '',
131
- results: string[] = [],
132
- segments = path.split('/');
133
-
134
- for (let i = 0, n = segments.length; i < n; i++) {
135
- let segment = segments[i];
136
-
137
- if (segment !== '' && !/\:/.test(segment)) {
138
- base += '/' + segment;
139
- }
140
- else if (/\:/.test(segment)) {
141
- if (/\?/.test(segment)) {
142
- if (results.length === 0 && base === '') {
143
- results.push('/');
144
- }
145
- else {
146
- results.push(base);
147
- }
148
-
149
- base += '/' + segment.replace('?', '');
150
- results.push(base);
151
- }
152
- else {
153
- base += '/' + segment;
154
- }
155
- }
156
- }
157
-
158
- return results.filter((v, i, a) => a.indexOf(v) === i);
159
- }
160
-
161
-
162
- class Router<T> {
163
- middleware?: Record<Method, Record<Path, HandlerMap<T>[]>>
164
- routes?: Record<Method, Record<Path, HandlerMap<T>[]>>
165
-
166
-
167
- constructor() {
168
- this.middleware = {
169
- [METHOD_NAME_ALL]: Object.create(null)
170
- };
171
- this.routes = {
172
- [METHOD_NAME_ALL]: Object.create(null)
173
- };
174
- }
175
-
176
-
177
- private buildAllMatchers(): Record<Method, Matcher<T> | null> {
178
- let matchers: Record<Method, Matcher<T> | null> = Object.create(null);
179
-
180
- for (let method in this.middleware) {
181
- matchers[method] ||= this.buildMatcher(method);
182
- }
183
-
184
- for (let method in this.routes) {
185
- matchers[method] ||= this.buildMatcher(method);
186
- }
187
-
188
- this.middleware = this.routes = undefined;
189
-
190
- return matchers;
191
- }
192
-
193
- private buildMatcher(method: Method): Matcher<T> | null {
194
- let isAll = method === METHOD_NAME_ALL,
195
- properties = [this.middleware, this.routes],
196
- property,
197
- routes: [Path, HandlerMap<T>[]][] = [];
198
-
199
- while (property = properties.pop()) {
200
- let values = property[method]
201
- ? Object.keys(property[method]).map((path) => [path, property![method][path]])
202
- : [];
203
-
204
- if (values.length !== 0) {
205
- isAll ||= true;
206
- routes.push(...(values as typeof routes));
207
- }
208
- else if (method !== METHOD_NAME_ALL) {
209
- routes.push(
210
- ...(Object.keys(property[METHOD_NAME_ALL]).map((path) => [path, property![METHOD_NAME_ALL][path]]) as typeof routes)
211
- );
212
- }
213
- }
214
-
215
- return isAll === true ? buildMatcherFromPreprocessedRoutes(routes) : null;
216
- }
217
-
218
- add(method: Method, path: Path, handler: T) {
219
- let { middleware, routes } = this;
220
-
221
- if (!middleware || !routes) {
222
- throw new Error('Routing: Cannot add route after matcher has been built.');
223
- }
224
-
225
- if (!middleware[method]) {
226
- let properties = [middleware, routes],
227
- property;
228
-
229
- while (property = properties.pop()) {
230
- let copy = property[METHOD_NAME_ALL],
231
- into = property[method];
232
-
233
- property[method] = Object.create(null);
234
-
235
- for (let path in copy) {
236
- into[path] = [ ...copy[path] ];
237
- }
238
- }
239
- }
240
-
241
- if (path === '/*') {
242
- path = '*';
243
- }
244
-
245
- let parameters = (path.match(/\/:/g) || []).length;
246
-
247
- if (/\*$/.test(path)) {
248
- let regex = buildWildcardRegExp(path);
249
-
250
- if (method === METHOD_NAME_ALL) {
251
- for (let m in middleware) {
252
- middleware[m][path] ||=
253
- findMiddleware(middleware[m], path) ||
254
- findMiddleware(middleware[METHOD_NAME_ALL], path) ||
255
- [];
256
- }
257
- }
258
- else {
259
- middleware[method][path] ||=
260
- findMiddleware(middleware[method], path) ||
261
- findMiddleware(middleware[METHOD_NAME_ALL], path) ||
262
- [];
263
- }
264
-
265
- let properties = [middleware, routes],
266
- property;
267
-
268
- while (property = properties.pop()) {
269
- for (let m in property) {
270
- if (method === METHOD_NAME_ALL || method === m) {
271
- let routes = property[m];
272
-
273
- for (let path in routes) {
274
- regex.test(path) && routes[path].push([handler, parameters]);
275
- }
276
- }
277
- }
278
- }
279
-
280
- return;
281
- }
282
-
283
- let paths = findOptionalParameters(path) || [path];
284
-
285
- for (let i = 0, n = paths.length; i < n; i++) {
286
- let path = paths[i];
287
-
288
- for (let m in routes) {
289
- if (method === METHOD_NAME_ALL || method === m) {
290
- let r = routes[m];
291
-
292
- r[path] ||= [
293
- ...(findMiddleware(middleware[m], path) ||
294
- findMiddleware(middleware[METHOD_NAME_ALL], path) ||
295
- []),
296
- ];
297
- r[path].push([handler, parameters - n + i + 1]);
298
- }
299
- }
300
- }
301
- }
302
-
303
- match(method: Method, path: Path): Result<T> {
304
- clearWildcardCache();
305
-
306
- let matchers = this.buildAllMatchers();
307
-
308
- this.match = (method, path) => {
309
- let matcher = (matchers[method] || matchers[METHOD_NAME_ALL]) as Matcher<T>,
310
- staticMatch = matcher[2][path];
311
-
312
- if (staticMatch) {
313
- return staticMatch;
314
- }
315
-
316
- let match = path.match(matcher[0]);
317
-
318
- if (!match) {
319
- return [[], empty];
320
- }
321
-
322
- return [matcher[1][match.indexOf('', 1)], match];
323
- }
324
-
325
- return this.match(method, path);
326
- }
327
- }
328
-
329
-
330
- export { Router };
@@ -1,184 +0,0 @@
1
- import { PATH_ERROR } from './constants';
2
- import type { Trie } from './trie';
3
- import { ParameterMetadata, Path } from './types';
4
-
5
-
6
- const LABEL_REGEXP = '[^/]+';
7
-
8
- const REGEXP_CHARACTERS = new Set('.\\+*[^]$()');
9
-
10
- const WILDCARD_ONLY_REGEXP = '.*';
11
-
12
- const WILDCARD_TAIL_REGEXP = '(?:|/.*)';
13
-
14
-
15
- // '*' matches to all the trailing paths
16
- const MATCH_WILDCARD_ONLY = ['', '', WILDCARD_ONLY_REGEXP];
17
-
18
- const MATCH_LABEL = ['', '', LABEL_REGEXP];
19
-
20
- // '/path/to/*' is /\/path\/to(?:|/.*)$
21
- const MATCH_WILDCARD_TAIL = ['', '', WILDCARD_TAIL_REGEXP];
22
-
23
-
24
- /**
25
- * Sort order:
26
- * 1. literal
27
- * 2. special pattern (e.g. :label{[0-9]+})
28
- * 3. common label pattern (e.g. :label)
29
- * 4. wildcard
30
- */
31
- function sort(a: Path, b: Path): number {
32
- if (a.length === 1) {
33
- return b.length === 1 ? (a < b ? -1 : 1) : -1;
34
- }
35
- else if (b.length === 1) {
36
- return 1;
37
- }
38
-
39
- if (a === WILDCARD_ONLY_REGEXP || a === WILDCARD_TAIL_REGEXP) {
40
- return 1;
41
- }
42
- else if (b === WILDCARD_ONLY_REGEXP || b === WILDCARD_TAIL_REGEXP) {
43
- return -1;
44
- }
45
-
46
- if (a === LABEL_REGEXP) {
47
- return 1;
48
- }
49
- else if (b === LABEL_REGEXP) {
50
- return -1;
51
- }
52
-
53
- return a.length === b.length ? (a < b ? -1 : 1) : b.length - a.length;
54
- }
55
-
56
-
57
- class Node {
58
- children: Record<Path, Node> = Object.create(null);
59
- index?: number;
60
- slot?: number;
61
-
62
-
63
- buildRegexString(): Path {
64
- let children = this.children,
65
- path,
66
- paths: Path[] = Object.keys(children).sort(sort);
67
-
68
- for (let i = 0, n = paths.length; i < n; i++) {
69
- let child = children[ path = paths[i] ];
70
-
71
- paths[i] = (
72
- typeof child.slot === 'number'
73
- ? `(${path})@${child.slot}`
74
- : REGEXP_CHARACTERS.has(path)
75
- ? `\\${path}`
76
- : path
77
- ) + child.buildRegexString();
78
- }
79
-
80
- if (typeof this.index === 'number') {
81
- paths.unshift(`#${this.index}`);
82
- }
83
-
84
- if (paths.length === 0) {
85
- return '';
86
- }
87
- else if (paths.length === 1) {
88
- return paths[0];
89
- }
90
-
91
- return '(?:' + paths.join('|') + ')';
92
- }
93
-
94
- insert(
95
- tokens: readonly Path[],
96
- index: number,
97
- parameters: ParameterMetadata,
98
- context: Trie['context'],
99
- validatePathOnly: boolean
100
- ): void {
101
- if (tokens.length === 0) {
102
- if (this.index !== undefined) {
103
- throw PATH_ERROR;
104
- }
105
- else if (validatePathOnly) {
106
- return;
107
- }
108
-
109
- this.index = index;
110
- return;
111
- }
112
-
113
- let [token, ...remainingTokens] = tokens,
114
- node,
115
- pattern = token === '*'
116
- ? remainingTokens.length === 0
117
- ? MATCH_WILDCARD_ONLY
118
- : MATCH_LABEL
119
- : token === '/*'
120
- ? MATCH_WILDCARD_TAIL
121
- : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
122
-
123
- if (pattern) {
124
- let name = pattern[1],
125
- path = pattern[2] || LABEL_REGEXP;
126
-
127
- if (name && pattern[2]) {
128
- // (a|b) => (?:a|b)
129
- path = path.replace(/^\((?!\?:)(?=[^)]+\)$)/, '(?:');
130
-
131
- // prefix(?:a|b) is allowed, but prefix(a|b) is not
132
- if (/\((?!\?:)/.test(path)) {
133
- throw PATH_ERROR;
134
- }
135
- }
136
-
137
- node = this.children[path];
138
-
139
- if (!node) {
140
- for (let key in this.children) {
141
- if (key !== WILDCARD_ONLY_REGEXP && key !== WILDCARD_TAIL_REGEXP) {
142
- throw PATH_ERROR;
143
- }
144
- }
145
-
146
- if (validatePathOnly) {
147
- return;
148
- }
149
-
150
- node = this.children[path] = new Node();
151
-
152
- if (name !== '') {
153
- node.slot = context.slot++;
154
- }
155
- }
156
-
157
- if (name !== '' && validatePathOnly === false) {
158
- parameters.push([name, node.slot as number]);
159
- }
160
- }
161
- else {
162
- node = this.children[token];
163
-
164
- if (!node) {
165
- for (let key in this.children) {
166
- if (key.length > 1 && key !== WILDCARD_ONLY_REGEXP && key !== WILDCARD_TAIL_REGEXP) {
167
- throw PATH_ERROR;
168
- }
169
- }
170
-
171
- if (validatePathOnly) {
172
- return;
173
- }
174
-
175
- node = this.children[token] = new Node();
176
- }
177
- }
178
-
179
- node.insert(remainingTokens, index, parameters, context, validatePathOnly);
180
- }
181
- }
182
-
183
-
184
- export { Node };
@@ -1,88 +0,0 @@
1
- import { Node } from './node'
2
- import { Indexes, ParameterMetadata, Path } from './types';
3
-
4
-
5
- const NEVER_MATCH = [/^$/, [], []] as [RegExp, Indexes, Indexes];
6
-
7
-
8
- class Trie {
9
- context = { slot: 0 };
10
- root = new Node();
11
-
12
-
13
- build(): [RegExp, Indexes, Indexes] {
14
- let regex = this.root.buildRegexString();
15
-
16
- if (regex === '') {
17
- return NEVER_MATCH;
18
- }
19
-
20
- let handlers: Indexes = [],
21
- i = 0,
22
- parameters: Indexes = [];
23
-
24
- regex = regex.replace(/#(\d+)|@(\d+)|\.\*\$/g, (_, handler, parameter) => {
25
- if (typeof handler !== 'undefined') {
26
- handlers[++i] = Number(handler);
27
- return '$()';
28
- }
29
- else if (typeof parameter !== 'undefined') {
30
- parameters[Number(parameter)] = ++i;
31
- }
32
-
33
- return '';
34
- })
35
-
36
- return [new RegExp(`^${regex}`), handlers, parameters];
37
- }
38
-
39
- insert(path: Path, index: number, pathErrorCheckOnly: boolean): ParameterMetadata {
40
- let groups: [string, string][] = [],
41
- parameters: ParameterMetadata = [];
42
-
43
- for (let i = 0; ;) {
44
- let replaced = false;
45
-
46
- path = path.replace(/\{[^}]+\}/g, (m) => {
47
- let mark = `@\\${i}`;
48
-
49
- groups[i++] = [mark, m];
50
- replaced = true;
51
-
52
- return mark;
53
- });
54
-
55
- if (!replaced) {
56
- break;
57
- }
58
- }
59
-
60
- /**
61
- * - pattern (:label, :label{0-9]+}, ...)
62
- * - /* wildcard
63
- * - character
64
- */
65
- let tokens = path.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
66
-
67
- for (let i = groups.length - 1; i >= 0; i--) {
68
- let [ mark, replacement ] = groups[i],
69
- token;
70
-
71
- for (let j = tokens.length - 1; j >= 0; j--) {
72
- token = tokens[j];
73
-
74
- if (token.indexOf(mark) !== -1) {
75
- token = token.replace(mark, replacement);
76
- break
77
- }
78
- }
79
- }
80
-
81
- this.root.insert(tokens, index, parameters, this.context, pathErrorCheckOnly);
82
-
83
- return parameters;
84
- }
85
- }
86
-
87
-
88
- export { Trie };
@@ -1,59 +0,0 @@
1
- type HandlerMap<T> = [T, number];
2
-
3
- type HandlerMetadata<T> = [T, ParameterMap][];
4
-
5
- type Indexes = number[];
6
-
7
- type Matcher<T> = [RegExp, HandlerMetadata<T>[], StaticMap<T>];
8
-
9
- type Method = string;
10
-
11
- type ParameterMap = Record<string, number>;
12
-
13
- type ParameterMetadata = [string, number][];
14
-
15
- type Path = string;
16
-
17
- /**
18
- * The result can be in one of two formats:
19
- * 1. An array of handlers with their corresponding parameter index maps, followed by a parameter stash.
20
- * 2. An array of handlers with their corresponding parameter maps.
21
- *
22
- * Example:
23
- *
24
- * [[handler, paramIndexMap][], paramArray]
25
- * ```typescript
26
- * [
27
- * [
28
- * [middlewareA, {}], // '*'
29
- * [funcA, {'id': 0}], // '/user/:id/*'
30
- * [funcB, {'id': 0, 'action': 1}], // '/user/:id/:action'
31
- * ],
32
- * ['123', 'abc']
33
- * ]
34
- * ```
35
- *
36
- * [[handler, params][]]
37
- * ```typescript
38
- * [
39
- * [
40
- * [middlewareA, {}], // '*'
41
- * [funcA, {'id': '123'}], // '/user/:id/*'
42
- * [funcB, {'id': '123', 'action': 'abc'}], // '/user/:id/:action'
43
- * ]
44
- * ]
45
- * ```
46
- */
47
- type Result<T> = [ [T, ParameterMap][], string[] ] | [ [T, Record<string, string>][] ];
48
-
49
- type StaticMap<T> = Record<Path, Result<T>>;
50
-
51
-
52
- export {
53
- HandlerMap, HandlerMetadata,
54
- Indexes,
55
- Matcher, Method,
56
- ParameterMap, ParameterMetadata, Path,
57
- Result,
58
- StaticMap
59
- };