@esportsplus/routing 0.1.31 → 0.2.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/build/browser.js +14 -12
- package/build/constants.d.ts +2 -2
- package/build/constants.js +2 -2
- package/build/index.d.ts +0 -1
- package/build/index.js +0 -1
- package/build/router/index.d.ts +5 -3
- package/build/router/index.js +47 -44
- package/build/router/node.d.ts +4 -3
- package/build/router/node.js +40 -53
- package/package.json +6 -4
- package/src/browser.ts +20 -13
- package/src/constants.ts +2 -2
- package/src/index.ts +0 -1
- package/src/router/index.ts +58 -49
- package/src/router/node.ts +51 -70
- package/src/types.ts +6 -1
- package/build/slugify.d.ts +0 -2
- package/build/slugify.js +0 -3
- package/src/slugify.ts +0 -4
package/build/browser.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { effect, reactive, root } from '@esportsplus/reactivity';
|
|
2
2
|
import { next } from '@esportsplus/pipeline';
|
|
3
3
|
import factory from './router/index.js';
|
|
4
|
-
let cache = [];
|
|
4
|
+
let cache = [], location = window.location;
|
|
5
5
|
function back() {
|
|
6
6
|
window.history.back();
|
|
7
7
|
}
|
|
@@ -9,30 +9,32 @@ function forward() {
|
|
|
9
9
|
window.history.forward();
|
|
10
10
|
}
|
|
11
11
|
function href() {
|
|
12
|
-
let
|
|
13
|
-
href:
|
|
14
|
-
hostname:
|
|
12
|
+
let hash = location.hash || '#/', path = hash ? hash.slice(1).split('?') : ['/', ''], request = {
|
|
13
|
+
href: location.href,
|
|
14
|
+
hostname: location.hostname,
|
|
15
15
|
method: 'GET',
|
|
16
|
-
origin:
|
|
16
|
+
origin: location.origin,
|
|
17
17
|
path: path[0],
|
|
18
|
-
port:
|
|
19
|
-
protocol:
|
|
18
|
+
port: location.port,
|
|
19
|
+
protocol: location.protocol,
|
|
20
20
|
query: {}
|
|
21
21
|
};
|
|
22
22
|
if (path[1]) {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
let query = request.query, params = new URLSearchParams(path[1]);
|
|
24
|
+
for (let [key, value] of params.entries()) {
|
|
25
|
+
query[key] = value;
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
return request;
|
|
28
29
|
}
|
|
29
30
|
function match(request, router, subdomain) {
|
|
30
31
|
if (router.subdomains !== null) {
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
let hostname = request.hostname, subdomains = router.subdomains;
|
|
33
|
+
for (let i = 0, n = subdomains.length; i < n; i++) {
|
|
34
|
+
if (!hostname.startsWith(subdomains[i])) {
|
|
33
35
|
continue;
|
|
34
36
|
}
|
|
35
|
-
subdomain =
|
|
37
|
+
subdomain = subdomains[i];
|
|
36
38
|
break;
|
|
37
39
|
}
|
|
38
40
|
}
|
package/build/constants.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ declare const ON_DELETE: string[];
|
|
|
2
2
|
declare const ON_GET: string[];
|
|
3
3
|
declare const ON_POST: string[];
|
|
4
4
|
declare const ON_PUT: string[];
|
|
5
|
-
declare const
|
|
5
|
+
declare const PARAMETER = 0;
|
|
6
6
|
declare const STATIC = 1;
|
|
7
7
|
declare const WILDCARD = 2;
|
|
8
|
-
export { ON_DELETE, ON_GET, ON_POST, ON_PUT,
|
|
8
|
+
export { ON_DELETE, ON_GET, ON_POST, ON_PUT, PARAMETER, STATIC, WILDCARD };
|
package/build/constants.js
CHANGED
|
@@ -2,7 +2,7 @@ const ON_DELETE = ['DELETE'];
|
|
|
2
2
|
const ON_GET = ['GET'];
|
|
3
3
|
const ON_POST = ['POST'];
|
|
4
4
|
const ON_PUT = ['PUT'];
|
|
5
|
-
const
|
|
5
|
+
const PARAMETER = 0;
|
|
6
6
|
const STATIC = 1;
|
|
7
7
|
const WILDCARD = 2;
|
|
8
|
-
export { ON_DELETE, ON_GET, ON_POST, ON_PUT,
|
|
8
|
+
export { ON_DELETE, ON_GET, ON_POST, ON_PUT, PARAMETER, STATIC, WILDCARD };
|
package/build/index.d.ts
CHANGED
package/build/index.js
CHANGED
package/build/router/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Name, Options, Route, RouteOptions } from '../types.js';
|
|
2
2
|
import { Node } from './node.js';
|
|
3
|
+
declare function key(method: string, subdomain?: string | null): string;
|
|
3
4
|
declare class Router<T> {
|
|
5
|
+
bucket: Record<ReturnType<typeof key>, {
|
|
6
|
+
root: Node<T>;
|
|
7
|
+
static: Record<string, Route<T>>;
|
|
8
|
+
}>;
|
|
4
9
|
groups: Options<T>[];
|
|
5
|
-
root: Node<T>;
|
|
6
10
|
routes: Record<Name, Route<T>>;
|
|
7
|
-
static: Record<Name, Route<T>>;
|
|
8
11
|
subdomains: string[] | null;
|
|
9
|
-
constructor();
|
|
10
12
|
private add;
|
|
11
13
|
private route;
|
|
12
14
|
delete(options: RouteOptions<T>): this;
|
package/build/router/index.js
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
1
1
|
import { ON_DELETE, ON_GET, ON_POST, ON_PUT, STATIC } from '../constants.js';
|
|
2
2
|
import { Node } from './node.js';
|
|
3
3
|
import pipeline from '@esportsplus/pipeline';
|
|
4
|
+
function key(method, subdomain) {
|
|
5
|
+
return (method + (subdomain ? subdomain + ' ' : '')).toUpperCase();
|
|
6
|
+
}
|
|
4
7
|
function normalize(path) {
|
|
5
|
-
if (path
|
|
6
|
-
path
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
path
|
|
8
|
+
if (path) {
|
|
9
|
+
if (path[0] !== '/') {
|
|
10
|
+
path = '/' + path;
|
|
11
|
+
}
|
|
12
|
+
if (path.length > 1 && path[path.length - 1] === '/') {
|
|
13
|
+
path = path.slice(0, -1);
|
|
14
|
+
}
|
|
10
15
|
}
|
|
11
16
|
return path || '/';
|
|
12
17
|
}
|
|
13
|
-
function radixkey(method, path, subdomain) {
|
|
14
|
-
return ((subdomain ? subdomain + ' ' : '') + method).toUpperCase() + ' ' + normalize(path);
|
|
15
|
-
}
|
|
16
18
|
function set(route, options) {
|
|
19
|
+
let pipeline = route.pipeline;
|
|
17
20
|
for (let key in options) {
|
|
18
21
|
let value = options[key];
|
|
19
22
|
if (key === 'middleware') {
|
|
20
23
|
for (let i = 0, n = value.length; i < n; i++) {
|
|
21
|
-
|
|
24
|
+
pipeline.add(value[i]);
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
else if (key === 'responder') {
|
|
25
|
-
|
|
28
|
+
pipeline.add(value);
|
|
26
29
|
}
|
|
27
30
|
else {
|
|
28
31
|
route[key] = (route[key] || '') + value;
|
|
@@ -30,20 +33,20 @@ function set(route, options) {
|
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
class Router {
|
|
36
|
+
bucket = {};
|
|
33
37
|
groups = [];
|
|
34
|
-
root;
|
|
35
38
|
routes = {};
|
|
36
|
-
static = {};
|
|
37
39
|
subdomains = null;
|
|
38
|
-
|
|
39
|
-
this.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
add(method, path, route) {
|
|
41
|
+
let bucket = this.bucket[key(method, route.subdomain)] ??= {
|
|
42
|
+
root: new Node(),
|
|
43
|
+
static: {}
|
|
44
|
+
};
|
|
45
|
+
if (path.indexOf(':') === -1 || bucket.root.add(path, route).type === STATIC) {
|
|
46
|
+
if (path in bucket.static) {
|
|
47
|
+
throw new Error(`Routing: static path '${path}' is already in use`);
|
|
45
48
|
}
|
|
46
|
-
|
|
49
|
+
bucket.static[path] = route;
|
|
47
50
|
}
|
|
48
51
|
return this;
|
|
49
52
|
}
|
|
@@ -82,43 +85,43 @@ class Router {
|
|
|
82
85
|
};
|
|
83
86
|
}
|
|
84
87
|
match(method, path, subdomain) {
|
|
85
|
-
let
|
|
86
|
-
if (
|
|
87
|
-
return {
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
let bucket = this.bucket[key(method, subdomain)];
|
|
89
|
+
if (!bucket) {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
path = normalize(path);
|
|
93
|
+
if (path in bucket.static) {
|
|
94
|
+
return { route: bucket.static[path] };
|
|
90
95
|
}
|
|
91
|
-
return
|
|
96
|
+
return bucket.root.find(path);
|
|
92
97
|
}
|
|
93
98
|
on(methods, options) {
|
|
94
99
|
let route = this.route(options);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
100
|
+
let name = route.name, path = route.path, subdomain = route.subdomain;
|
|
101
|
+
if (name) {
|
|
102
|
+
if (this.routes[name]) {
|
|
103
|
+
throw new Error(`Routing: '${name}' is already in use`);
|
|
98
104
|
}
|
|
99
|
-
this.routes[
|
|
105
|
+
this.routes[name] = route;
|
|
100
106
|
}
|
|
101
|
-
if (
|
|
107
|
+
if (path) {
|
|
102
108
|
for (let i = 0, n = methods.length; i < n; i++) {
|
|
103
|
-
let
|
|
104
|
-
if (
|
|
105
|
-
let segments =
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
let method = methods[i];
|
|
110
|
+
if (path.indexOf('?:') !== -1) {
|
|
111
|
+
let segments = path.split('?:'), url = segments[0];
|
|
112
|
+
this.add(method, url, route);
|
|
113
|
+
for (let i = 1; i < segments.length; i++) {
|
|
114
|
+
url += '/:' + segments[i];
|
|
115
|
+
this.add(method, url, route);
|
|
108
116
|
}
|
|
109
117
|
}
|
|
110
118
|
else {
|
|
111
|
-
this.add(
|
|
119
|
+
this.add(method, path, route);
|
|
112
120
|
}
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
this.subdomains = [route.subdomain];
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
this.subdomains.push(route.subdomain);
|
|
121
|
-
}
|
|
123
|
+
if (subdomain) {
|
|
124
|
+
(this.subdomains ??= []).push(subdomain.toLowerCase());
|
|
122
125
|
}
|
|
123
126
|
return this;
|
|
124
127
|
}
|
package/build/router/node.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { Route } from './index.js';
|
|
2
2
|
declare class Node<T> {
|
|
3
|
-
children: Map<string | number, Node<T>> | null;
|
|
4
3
|
parent: Node<T> | null;
|
|
5
4
|
path: string | null;
|
|
6
|
-
property: string | null;
|
|
7
5
|
route: Route<T> | null;
|
|
6
|
+
static: Map<string | number, Node<T>> | null;
|
|
8
7
|
type: number | null;
|
|
8
|
+
name: string | null;
|
|
9
|
+
parameter: Node<T> | null;
|
|
10
|
+
wildcard: Node<T> | null;
|
|
9
11
|
constructor(parent?: Node<T>['parent']);
|
|
10
12
|
add(path: string, route: Route<T>): Node<T>;
|
|
11
13
|
find(path: string): {
|
|
12
14
|
parameters?: Readonly<Record<PropertyKey, unknown>>;
|
|
13
15
|
route?: Readonly<Route<T>>;
|
|
14
16
|
};
|
|
15
|
-
remove(path: string): void;
|
|
16
17
|
}
|
|
17
18
|
export { Node };
|
package/build/router/node.js
CHANGED
|
@@ -1,36 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PARAMETER, STATIC, WILDCARD } from '../constants.js';
|
|
2
2
|
class Node {
|
|
3
|
-
children = null;
|
|
4
3
|
parent = null;
|
|
5
4
|
path = null;
|
|
6
|
-
property = null;
|
|
7
5
|
route = null;
|
|
6
|
+
static = null;
|
|
8
7
|
type = null;
|
|
8
|
+
name = null;
|
|
9
|
+
parameter = null;
|
|
10
|
+
wildcard = null;
|
|
9
11
|
constructor(parent = null) {
|
|
10
12
|
this.parent = parent;
|
|
11
13
|
}
|
|
12
14
|
add(path, route) {
|
|
13
15
|
let node = this, segments = path.split('/'), type = STATIC, unnamed = 0;
|
|
14
16
|
for (let i = 0, n = segments.length; i < n; i++) {
|
|
15
|
-
let
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
node.
|
|
17
|
+
let segment = segments[i], symbol = segment[0];
|
|
18
|
+
if (symbol === ':') {
|
|
19
|
+
if (!node.parameter) {
|
|
20
|
+
node.parameter = new Node(node);
|
|
21
|
+
node.parameter.name = (segment.slice(1) || unnamed++).toString();
|
|
20
22
|
}
|
|
21
|
-
node
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
node = node.parameter;
|
|
24
|
+
type = PARAMETER;
|
|
25
|
+
}
|
|
26
|
+
else if (symbol === '*') {
|
|
27
|
+
if (!node.wildcard) {
|
|
28
|
+
node.wildcard = new Node(node);
|
|
29
|
+
node.wildcard.name = (segment.slice(2) || unnamed++).toString();
|
|
26
30
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
node = node.wildcard;
|
|
32
|
+
type = WILDCARD;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
let next = node.static?.get(segment);
|
|
36
|
+
if (!next) {
|
|
37
|
+
next = new Node(node);
|
|
38
|
+
(node.static ??= new Map()).set(segment, next);
|
|
31
39
|
}
|
|
40
|
+
node = next;
|
|
32
41
|
}
|
|
33
|
-
node = child;
|
|
34
42
|
}
|
|
35
43
|
node.path = path;
|
|
36
44
|
node.route = route;
|
|
@@ -38,56 +46,35 @@ class Node {
|
|
|
38
46
|
return node;
|
|
39
47
|
}
|
|
40
48
|
find(path) {
|
|
41
|
-
let node = this, parameters
|
|
49
|
+
let node = this, parameters, segments = path.split('/'), wildcard;
|
|
42
50
|
for (let i = 0, n = segments.length; i < n; i++) {
|
|
43
|
-
let segment = segments[i]
|
|
44
|
-
if (
|
|
51
|
+
let segment = segments[i];
|
|
52
|
+
if (node.wildcard) {
|
|
45
53
|
wildcard = {
|
|
46
|
-
node:
|
|
54
|
+
node: node.wildcard,
|
|
47
55
|
value: segments.slice(i).join('/')
|
|
48
56
|
};
|
|
49
57
|
}
|
|
50
|
-
let next = node.
|
|
58
|
+
let next = node.static?.get(segment);
|
|
51
59
|
if (next) {
|
|
52
60
|
node = next;
|
|
61
|
+
continue;
|
|
53
62
|
}
|
|
54
|
-
|
|
55
|
-
node =
|
|
56
|
-
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
parameters[node.property] = segment;
|
|
63
|
+
if (!node.parameter) {
|
|
64
|
+
node = undefined;
|
|
65
|
+
break;
|
|
60
66
|
}
|
|
67
|
+
node = node.parameter;
|
|
68
|
+
(parameters ??= {})[node.name] = segment;
|
|
61
69
|
}
|
|
62
|
-
if ((node === undefined || node.route === null) && wildcard
|
|
70
|
+
if ((node === undefined || node.route === null) && wildcard) {
|
|
63
71
|
node = wildcard.node;
|
|
64
|
-
parameters[node.
|
|
65
|
-
}
|
|
66
|
-
if (!node) {
|
|
67
|
-
return {};
|
|
72
|
+
(parameters ??= {})[node.name] = wildcard.value;
|
|
68
73
|
}
|
|
69
74
|
return {
|
|
70
75
|
parameters,
|
|
71
|
-
route: node
|
|
76
|
+
route: node?.route || undefined
|
|
72
77
|
};
|
|
73
78
|
}
|
|
74
|
-
remove(path) {
|
|
75
|
-
let node = this, segments = path.split('/');
|
|
76
|
-
for (let i = 0, n = segments.length; i < n; i++) {
|
|
77
|
-
node = node.children?.get(segments[i]);
|
|
78
|
-
if (!node) {
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
if (node.children?.size) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
let parent = node.parent;
|
|
86
|
-
if (parent && parent.children) {
|
|
87
|
-
parent.children.delete(segments[segments.length - 1]);
|
|
88
|
-
parent.children.delete(WILDCARD);
|
|
89
|
-
parent.children.delete(PLACEHOLDER);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
79
|
}
|
|
93
80
|
export { Node };
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"author": "ICJR",
|
|
3
3
|
"dependencies": {
|
|
4
4
|
"@esportsplus/pipeline": "^1.1.5",
|
|
5
|
-
"@esportsplus/reactivity": "^0.12.
|
|
6
|
-
"@esportsplus/utilities": "^0.
|
|
5
|
+
"@esportsplus/reactivity": "^0.12.4",
|
|
6
|
+
"@esportsplus/utilities": "^0.22.0"
|
|
7
7
|
},
|
|
8
8
|
"devDependencies": {
|
|
9
9
|
"@esportsplus/typescript": "^0.9.2"
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
"name": "@esportsplus/routing",
|
|
13
13
|
"private": false,
|
|
14
14
|
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
15
16
|
"types": "./build/index.d.ts",
|
|
16
|
-
"version": "0.
|
|
17
|
+
"version": "0.2.0",
|
|
17
18
|
"scripts": {
|
|
18
19
|
"build": "tsc && tsc-alias",
|
|
19
|
-
"-": "-"
|
|
20
|
+
"-": "-",
|
|
21
|
+
"bench": "node bench/index.mjs"
|
|
20
22
|
}
|
|
21
23
|
}
|
package/src/browser.ts
CHANGED
|
@@ -4,7 +4,8 @@ import { next } from '@esportsplus/pipeline';
|
|
|
4
4
|
import factory from './router';
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
let cache: Request<any>[] = []
|
|
7
|
+
let cache: Request<any>[] = [],
|
|
8
|
+
location = window.location;
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
function back() {
|
|
@@ -16,22 +17,25 @@ function forward() {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
function href<T>() {
|
|
19
|
-
let
|
|
20
|
-
path =
|
|
20
|
+
let hash = location.hash || '#/',
|
|
21
|
+
path = hash ? hash.slice(1).split('?') : ['/', ''],
|
|
21
22
|
request = {
|
|
22
|
-
href:
|
|
23
|
-
hostname:
|
|
23
|
+
href: location.href,
|
|
24
|
+
hostname: location.hostname,
|
|
24
25
|
method: 'GET',
|
|
25
|
-
origin:
|
|
26
|
+
origin: location.origin,
|
|
26
27
|
path: path[0],
|
|
27
|
-
port:
|
|
28
|
-
protocol:
|
|
28
|
+
port: location.port,
|
|
29
|
+
protocol: location.protocol,
|
|
29
30
|
query: {} as Record<PropertyKey, unknown>
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
if (path[1]) {
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
let query = request.query,
|
|
35
|
+
params = new URLSearchParams(path[1]);
|
|
36
|
+
|
|
37
|
+
for (let [key, value] of params.entries()) {
|
|
38
|
+
query[key] = value;
|
|
35
39
|
}
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -40,12 +44,15 @@ function href<T>() {
|
|
|
40
44
|
|
|
41
45
|
function match<T>(request: Request<T>, router: Router<T>, subdomain?: string) {
|
|
42
46
|
if (router.subdomains !== null) {
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
let hostname = request.hostname,
|
|
48
|
+
subdomains = router.subdomains;
|
|
49
|
+
|
|
50
|
+
for (let i = 0, n = subdomains.length; i < n; i++) {
|
|
51
|
+
if (!hostname.startsWith(subdomains[i])) {
|
|
45
52
|
continue;
|
|
46
53
|
}
|
|
47
54
|
|
|
48
|
-
subdomain =
|
|
55
|
+
subdomain = subdomains[i];
|
|
49
56
|
break;
|
|
50
57
|
}
|
|
51
58
|
}
|
package/src/constants.ts
CHANGED
|
@@ -7,11 +7,11 @@ const ON_POST = ['POST'];
|
|
|
7
7
|
const ON_PUT = ['PUT'];
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const PARAMETER = 0;
|
|
11
11
|
|
|
12
12
|
const STATIC = 1;
|
|
13
13
|
|
|
14
14
|
const WILDCARD = 2;
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
export { ON_DELETE, ON_GET, ON_POST, ON_PUT,
|
|
17
|
+
export { ON_DELETE, ON_GET, ON_POST, ON_PUT, PARAMETER, STATIC, WILDCARD };
|
package/src/index.ts
CHANGED
package/src/router/index.ts
CHANGED
|
@@ -1,36 +1,40 @@
|
|
|
1
|
-
import { ON_DELETE, ON_GET, ON_POST, ON_PUT, STATIC } from '
|
|
2
|
-
import { Name, Options, Request, Route, RouteOptions } from '
|
|
1
|
+
import { ON_DELETE, ON_GET, ON_POST, ON_PUT, STATIC } from '../constants';
|
|
2
|
+
import { Name, Options, Request, Route, RouteOptions } from '../types';
|
|
3
3
|
import { Node } from './node';
|
|
4
4
|
import pipeline from '@esportsplus/pipeline';
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
function key(method: string, subdomain?: string | null) {
|
|
8
|
+
return (method + (subdomain ? subdomain + ' ' : '')).toUpperCase();
|
|
9
|
+
}
|
|
10
|
+
|
|
7
11
|
function normalize(path: string) {
|
|
8
|
-
if (path
|
|
9
|
-
path
|
|
10
|
-
|
|
12
|
+
if (path) {
|
|
13
|
+
if (path[0] !== '/') {
|
|
14
|
+
path = '/' + path;
|
|
15
|
+
}
|
|
11
16
|
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
if (path.length > 1 && path[path.length - 1] === '/') {
|
|
18
|
+
path = path.slice(0, -1);
|
|
19
|
+
}
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
return path || '/';
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
function radixkey(method: string, path: string, subdomain?: string | null) {
|
|
20
|
-
return ((subdomain ? subdomain + ' ' : '') + method).toUpperCase() + ' ' + normalize(path);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
25
|
function set<T>(route: Route<T>, options: Options<T> | RouteOptions<T>) {
|
|
26
|
+
let pipeline = route.pipeline;
|
|
27
|
+
|
|
24
28
|
for (let key in options) {
|
|
25
29
|
let value = options[key as keyof typeof options] as any;
|
|
26
30
|
|
|
27
31
|
if (key === 'middleware') {
|
|
28
32
|
for (let i = 0, n = value.length; i < n; i++) {
|
|
29
|
-
|
|
33
|
+
pipeline.add(value[i]);
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
else if (key === 'responder') {
|
|
33
|
-
|
|
37
|
+
pipeline.add(value);
|
|
34
38
|
}
|
|
35
39
|
else {
|
|
36
40
|
// @ts-ignore
|
|
@@ -41,25 +45,24 @@ function set<T>(route: Route<T>, options: Options<T> | RouteOptions<T>) {
|
|
|
41
45
|
|
|
42
46
|
|
|
43
47
|
class Router<T> {
|
|
48
|
+
bucket: Record<ReturnType<typeof key>, { root: Node<T>, static: Record<string, Route<T>> }> = {};
|
|
44
49
|
groups: Options<T>[] = [];
|
|
45
|
-
root: Node<T>;
|
|
46
50
|
routes: Record<Name, Route<T>> = {};
|
|
47
|
-
static: Record<Name, Route<T>> = {};
|
|
48
51
|
subdomains: string[] | null = null;
|
|
49
52
|
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
private add(method: string, path: string, route: Route<T>) {
|
|
55
|
+
let bucket = this.bucket[ key(method, route.subdomain) ] ??= {
|
|
56
|
+
root: new Node(),
|
|
57
|
+
static: {}
|
|
58
|
+
};
|
|
55
59
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
throw new Error(`Routing: static path '${radixkey}' is already in use`);
|
|
60
|
+
if (path.indexOf(':') === -1 || bucket.root.add(path, route).type === STATIC) {
|
|
61
|
+
if (path in bucket.static) {
|
|
62
|
+
throw new Error(`Routing: static path '${path}' is already in use`);
|
|
60
63
|
}
|
|
61
64
|
|
|
62
|
-
|
|
65
|
+
bucket.static[path] = route;
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
return this;
|
|
@@ -111,53 +114,59 @@ class Router<T> {
|
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
match(method: string, path: string, subdomain?: string | null) {
|
|
114
|
-
let
|
|
117
|
+
let bucket = this.bucket[ key(method, subdomain) ];
|
|
115
118
|
|
|
116
|
-
if (
|
|
117
|
-
return {
|
|
118
|
-
route: this.static[key] as Readonly<Route<T>>
|
|
119
|
-
};
|
|
119
|
+
if (!bucket) {
|
|
120
|
+
return {};
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
path = normalize(path);
|
|
124
|
+
|
|
125
|
+
if (path in bucket.static) {
|
|
126
|
+
return { route: bucket.static[path] };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return bucket.root.find(path);
|
|
123
130
|
}
|
|
124
131
|
|
|
125
132
|
on(methods: string[], options: RouteOptions<T>) {
|
|
126
133
|
let route = this.route(options);
|
|
127
134
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
let name = route.name,
|
|
136
|
+
path = route.path,
|
|
137
|
+
subdomain = route.subdomain;
|
|
138
|
+
|
|
139
|
+
if (name) {
|
|
140
|
+
if (this.routes[name]) {
|
|
141
|
+
throw new Error(`Routing: '${name}' is already in use`);
|
|
131
142
|
}
|
|
132
143
|
|
|
133
|
-
this.routes[
|
|
144
|
+
this.routes[name] = route;
|
|
134
145
|
}
|
|
135
146
|
|
|
136
|
-
if (
|
|
147
|
+
if (path) {
|
|
137
148
|
for (let i = 0, n = methods.length; i < n; i++) {
|
|
138
|
-
let
|
|
149
|
+
let method = methods[i];
|
|
150
|
+
|
|
151
|
+
if (path.indexOf('?:') !== -1) {
|
|
152
|
+
let segments = path.split('?:'),
|
|
153
|
+
url = segments[0];
|
|
139
154
|
|
|
140
|
-
|
|
141
|
-
let segments = key.split('?:'),
|
|
142
|
-
url = '';
|
|
155
|
+
this.add(method, url, route);
|
|
143
156
|
|
|
144
|
-
for (let i =
|
|
145
|
-
|
|
157
|
+
for (let i = 1; i < segments.length; i++) {
|
|
158
|
+
url += '/:' + segments[i];
|
|
159
|
+
this.add(method, url, route);
|
|
146
160
|
}
|
|
147
161
|
}
|
|
148
162
|
else {
|
|
149
|
-
this.add(
|
|
163
|
+
this.add(method, path, route);
|
|
150
164
|
}
|
|
151
165
|
}
|
|
152
166
|
}
|
|
153
167
|
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
this.subdomains = [route.subdomain];
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
this.subdomains.push(route.subdomain);
|
|
160
|
-
}
|
|
168
|
+
if (subdomain) {
|
|
169
|
+
(this.subdomains ??= []).push( subdomain.toLowerCase() );
|
|
161
170
|
}
|
|
162
171
|
|
|
163
172
|
return this;
|
package/src/router/node.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PARAMETER, STATIC, WILDCARD } from '../constants';
|
|
2
2
|
import { Route } from './index';
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Node<T> {
|
|
6
|
-
children: Map<string | number, Node<T>> | null = null;
|
|
7
6
|
parent: Node<T> | null = null;
|
|
8
7
|
path: string | null = null;
|
|
9
|
-
property: string | null = null;
|
|
10
8
|
route: Route<T> | null = null;
|
|
9
|
+
static: Map<string | number, Node<T>> | null = null;
|
|
11
10
|
type: number | null = null;
|
|
12
11
|
|
|
12
|
+
// Parameter or Wildcard parameter name
|
|
13
|
+
name: string | null = null;
|
|
14
|
+
parameter: Node<T> | null = null;
|
|
15
|
+
wildcard: Node<T> | null = null;
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
constructor(parent: Node<T>['parent'] = null) {
|
|
15
19
|
this.parent = parent;
|
|
@@ -23,33 +27,40 @@ class Node<T> {
|
|
|
23
27
|
unnamed = 0;
|
|
24
28
|
|
|
25
29
|
for (let i = 0, n = segments.length; i < n; i++) {
|
|
26
|
-
let
|
|
30
|
+
let segment = segments[i],
|
|
31
|
+
symbol = segment[0];
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
// Named name
|
|
34
|
+
if (symbol === ':') {
|
|
35
|
+
if (!node.parameter) {
|
|
36
|
+
node.parameter = new Node<T>(node);
|
|
37
|
+
node.parameter.name = (segment.slice(1) || unnamed++).toString();
|
|
38
|
+
}
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
node = node.parameter;
|
|
41
|
+
type = PARAMETER;
|
|
42
|
+
}
|
|
43
|
+
// "*:" Wildcard name
|
|
44
|
+
else if (symbol === '*') {
|
|
45
|
+
if (!node.wildcard) {
|
|
46
|
+
node.wildcard = new Node<T>(node);
|
|
47
|
+
node.wildcard.name = (segment.slice(2) || unnamed++).toString();
|
|
34
48
|
}
|
|
35
49
|
|
|
36
|
-
node
|
|
50
|
+
node = node.wildcard;
|
|
51
|
+
type = WILDCARD;
|
|
52
|
+
}
|
|
53
|
+
// Static name
|
|
54
|
+
else {
|
|
55
|
+
let next: Node<T> | undefined = node.static?.get(segment);
|
|
37
56
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
node.children.set(PLACEHOLDER, child);
|
|
42
|
-
type = null;
|
|
57
|
+
if (!next) {
|
|
58
|
+
next = new Node<T>(node);
|
|
59
|
+
(node.static ??= new Map()).set(segment, next);
|
|
43
60
|
}
|
|
44
|
-
// "*:" Wildcard property
|
|
45
|
-
else if (symbol === '*') {
|
|
46
|
-
child.property = (segment.slice(2) || unnamed++).toString();
|
|
47
|
-
node.children.set(WILDCARD, child);
|
|
48
|
-
type = null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
61
|
|
|
52
|
-
|
|
62
|
+
node = next;
|
|
63
|
+
}
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
node.path = path;
|
|
@@ -64,77 +75,47 @@ class Node<T> {
|
|
|
64
75
|
route?: Readonly<Route<T>>;
|
|
65
76
|
} {
|
|
66
77
|
let node: Node<T> | undefined = this,
|
|
67
|
-
parameters: Record<PropertyKey, unknown>
|
|
78
|
+
parameters: Record<PropertyKey, unknown> | undefined,
|
|
68
79
|
segments = path.split('/'),
|
|
69
|
-
wildcard: { node: Node<T>, value: string } |
|
|
80
|
+
wildcard: { node: Node<T>, value: string } | undefined;
|
|
70
81
|
|
|
71
82
|
for (let i = 0, n = segments.length; i < n; i++) {
|
|
72
|
-
let segment = segments[i]
|
|
73
|
-
wc = node.children?.get(WILDCARD);
|
|
83
|
+
let segment = segments[i];
|
|
74
84
|
|
|
75
|
-
if (
|
|
85
|
+
if (node.wildcard) {
|
|
76
86
|
wildcard = {
|
|
77
|
-
node:
|
|
87
|
+
node: node.wildcard,
|
|
78
88
|
value: segments.slice(i).join('/')
|
|
79
89
|
};
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
// Exact matches take precedence over
|
|
83
|
-
let next: Node<T> | undefined = node.
|
|
92
|
+
// Exact matches take precedence over parameters
|
|
93
|
+
let next: Node<T> | undefined = node.static?.get(segment) as Node<T> | undefined;
|
|
84
94
|
|
|
85
95
|
if (next) {
|
|
86
96
|
node = next;
|
|
97
|
+
continue;
|
|
87
98
|
}
|
|
88
|
-
else {
|
|
89
|
-
node = node.children?.get(PLACEHOLDER);
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
parameters[ node.property! ] = segment;
|
|
100
|
+
if (!node.parameter) {
|
|
101
|
+
node = undefined;
|
|
102
|
+
break;
|
|
96
103
|
}
|
|
97
|
-
}
|
|
98
104
|
|
|
99
|
-
|
|
100
|
-
node =
|
|
101
|
-
parameters[ node.property! ] = wildcard.value;
|
|
105
|
+
node = node.parameter;
|
|
106
|
+
(parameters ??= {})[node.name!] = segment;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
|
-
if (
|
|
105
|
-
|
|
109
|
+
if ((node === undefined || node.route === null) && wildcard) {
|
|
110
|
+
node = wildcard.node;
|
|
111
|
+
(parameters ??= {})[ node.name! ] = wildcard.value;
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
return {
|
|
109
115
|
parameters,
|
|
110
|
-
route: node
|
|
116
|
+
route: node?.route || undefined
|
|
111
117
|
};
|
|
112
118
|
}
|
|
113
|
-
|
|
114
|
-
remove(path: string) {
|
|
115
|
-
let node: Node<T> | undefined = this,
|
|
116
|
-
segments = path.split('/');
|
|
117
|
-
|
|
118
|
-
for (let i = 0, n = segments.length; i < n; i++) {
|
|
119
|
-
node = node.children?.get( segments[i] );
|
|
120
|
-
|
|
121
|
-
if (!node) {
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (node.children?.size) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
let parent = node.parent;
|
|
131
|
-
|
|
132
|
-
if (parent && parent.children) {
|
|
133
|
-
parent.children.delete( segments[segments.length - 1] );
|
|
134
|
-
parent.children.delete(WILDCARD);
|
|
135
|
-
parent.children.delete(PLACEHOLDER);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
119
|
}
|
|
139
120
|
|
|
140
121
|
|
package/src/types.ts
CHANGED
package/build/slugify.d.ts
DELETED
package/build/slugify.js
DELETED
package/src/slugify.ts
DELETED