@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 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 data = new URL(window.location?.href || ''), path = data.hash ? data.hash.slice(1).split('?') : ['/', ''], request = {
13
- href: data.href,
14
- hostname: data.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: data.origin,
16
+ origin: location.origin,
17
17
  path: path[0],
18
- port: data.port,
19
- protocol: data.protocol,
18
+ port: location.port,
19
+ protocol: location.protocol,
20
20
  query: {}
21
21
  };
22
22
  if (path[1]) {
23
- for (let [key, value] of (new URLSearchParams(path[1])).entries()) {
24
- request.query[key] = value;
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
- for (let i = 0, n = router.subdomains.length; i < n; i++) {
32
- if (!request.hostname.startsWith(router.subdomains[i])) {
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 = router.subdomains[i];
37
+ subdomain = subdomains[i];
36
38
  break;
37
39
  }
38
40
  }
@@ -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,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[0] !== '/') {
6
- path = '/' + path;
7
- }
8
- if (path.at(-1) === '/') {
9
- path = path.slice(0, -1);
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
- route.pipeline.add(value[i]);
24
+ pipeline.add(value[i]);
22
25
  }
23
26
  }
24
27
  else if (key === 'responder') {
25
- route.pipeline.add(value);
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
- constructor() {
39
- this.root = new Node();
40
- }
41
- add(radixkey, route) {
42
- if (radixkey.indexOf(':') === -1 || this.root.add(radixkey, route).type === STATIC) {
43
- if (this.static[radixkey]) {
44
- 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`);
45
48
  }
46
- this.static[radixkey] = route;
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 key = radixkey(method, path, subdomain);
86
- if (key in this.static) {
87
- return {
88
- route: this.static[key]
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 this.root.find(key);
96
+ return bucket.root.find(path);
92
97
  }
93
98
  on(methods, options) {
94
99
  let route = this.route(options);
95
- if (route.name) {
96
- if (this.routes[route.name]) {
97
- 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`);
98
104
  }
99
- this.routes[route.name] = route;
105
+ this.routes[name] = route;
100
106
  }
101
- if (route.path) {
107
+ if (path) {
102
108
  for (let i = 0, n = methods.length; i < n; i++) {
103
- let key = radixkey(methods[i], route.path, route.subdomain);
104
- if (key.indexOf('?:') !== -1) {
105
- let segments = key.split('?:'), url = '';
106
- for (let i = 0, n = segments.length; i < n; i++) {
107
- 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);
108
116
  }
109
117
  }
110
118
  else {
111
- this.add(key, route);
119
+ this.add(method, path, route);
112
120
  }
113
121
  }
114
122
  }
115
- if (route.subdomain) {
116
- if (!this.subdomains) {
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
  }
@@ -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
@@ -2,8 +2,8 @@
2
2
  "author": "ICJR",
3
3
  "dependencies": {
4
4
  "@esportsplus/pipeline": "^1.1.5",
5
- "@esportsplus/reactivity": "^0.12.3",
6
- "@esportsplus/utilities": "^0.21.1"
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.1.31",
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 data = new URL( window.location?.href || '' ),
20
- path = data.hash ? data.hash.slice(1).split('?') : ['/', ''],
20
+ let hash = location.hash || '#/',
21
+ path = hash ? hash.slice(1).split('?') : ['/', ''],
21
22
  request = {
22
- href: data.href,
23
- hostname: data.hostname,
23
+ href: location.href,
24
+ hostname: location.hostname,
24
25
  method: 'GET',
25
- origin: data.origin,
26
+ origin: location.origin,
26
27
  path: path[0],
27
- port: data.port,
28
- protocol: data.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
- for (let [key, value] of (new URLSearchParams(path[1])).entries()) {
34
- request.query[key] = value;
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
- for (let i = 0, n = router.subdomains.length; i < n; i++) {
44
- if (!request.hostname.startsWith(router.subdomains[i])) {
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 = router.subdomains[i];
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 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,36 +1,40 @@
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
- if (path[0] !== '/') {
9
- path = '/' + path;
10
- }
12
+ if (path) {
13
+ if (path[0] !== '/') {
14
+ path = '/' + path;
15
+ }
11
16
 
12
- if (path.at(-1) === '/') {
13
- path = path.slice(0, -1);
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
- route.pipeline.add(value[i]);
33
+ pipeline.add(value[i]);
30
34
  }
31
35
  }
32
36
  else if (key === 'responder') {
33
- route.pipeline.add(value);
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
- constructor() {
52
- this.root = new Node();
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
- private add(radixkey: string, route: Route<T>) {
57
- if (radixkey.indexOf(':') === -1 || this.root.add(radixkey, route).type === STATIC) {
58
- if (this.static[radixkey]) {
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
- this.static[radixkey] = route;
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 key = radixkey(method, path, subdomain);
117
+ let bucket = this.bucket[ key(method, subdomain) ];
115
118
 
116
- if (key in this.static) {
117
- return {
118
- route: this.static[key] as Readonly<Route<T>>
119
- };
119
+ if (!bucket) {
120
+ return {};
120
121
  }
121
122
 
122
- 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);
123
130
  }
124
131
 
125
132
  on(methods: string[], options: RouteOptions<T>) {
126
133
  let route = this.route(options);
127
134
 
128
- if (route.name) {
129
- if (this.routes[route.name]) {
130
- 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`);
131
142
  }
132
143
 
133
- this.routes[route.name] = route;
144
+ this.routes[name] = route;
134
145
  }
135
146
 
136
- if (route.path) {
147
+ if (path) {
137
148
  for (let i = 0, n = methods.length; i < n; i++) {
138
- 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];
139
154
 
140
- if (key.indexOf('?:') !== -1) {
141
- let segments = key.split('?:'),
142
- url = '';
155
+ this.add(method, url, route);
143
156
 
144
- for (let i = 0, n = segments.length; i < n; i++) {
145
- 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);
146
160
  }
147
161
  }
148
162
  else {
149
- this.add(key, route);
163
+ this.add(method, path, route);
150
164
  }
151
165
  }
152
166
  }
153
167
 
154
- if (route.subdomain) {
155
- if (!this.subdomains) {
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;
@@ -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
- };