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