@esportsplus/routing 0.0.8 → 0.0.10

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.
Files changed (65) hide show
  1. package/build/hash.d.ts +70 -0
  2. package/build/hash.js +75 -0
  3. package/build/index.d.ts +6 -92
  4. package/build/index.js +6 -8
  5. package/build/middleware/dispatch.d.ts +5 -0
  6. package/build/middleware/dispatch.js +7 -0
  7. package/build/middleware/factory.d.ts +3 -3
  8. package/build/middleware/factory.js +13 -13
  9. package/build/middleware/index.d.ts +24 -47
  10. package/build/middleware/index.js +5 -4
  11. package/build/middleware/match.d.ts +10 -0
  12. package/build/middleware/match.js +23 -0
  13. package/build/router/index.d.ts +36 -0
  14. package/build/router/index.js +182 -0
  15. package/build/router/node.d.ts +17 -0
  16. package/build/router/node.js +93 -0
  17. package/build/router/path.d.ts +14 -0
  18. package/build/router/path.js +21 -0
  19. package/build/slugify.d.ts +2 -0
  20. package/build/slugify.js +3 -0
  21. package/build/symbols.d.ts +4 -0
  22. package/build/symbols.js +4 -0
  23. package/build/types.d.ts +12 -26
  24. package/build/types.js +2 -1
  25. package/package.json +2 -3
  26. package/readme.md +1 -0
  27. package/src/hash.ts +106 -0
  28. package/src/index.ts +4 -6
  29. package/src/middleware/dispatch.ts +12 -0
  30. package/src/middleware/factory.ts +10 -9
  31. package/src/middleware/index.ts +4 -3
  32. package/src/middleware/match.ts +41 -0
  33. package/src/router/index.ts +237 -0
  34. package/src/router/node.ts +141 -0
  35. package/src/router/path.ts +29 -0
  36. package/src/slugify.ts +4 -0
  37. package/src/symbols.ts +8 -0
  38. package/src/types.ts +9 -26
  39. package/tsconfig.json +5 -19
  40. package/build/group.d.ts +0 -3
  41. package/build/group.js +0 -8
  42. package/build/listener.d.ts +0 -4
  43. package/build/listener.js +0 -13
  44. package/build/middleware/common/dispatch.d.ts +0 -3
  45. package/build/middleware/common/dispatch.js +0 -11
  46. package/build/middleware/common/index.d.ts +0 -29
  47. package/build/middleware/common/index.js +0 -3
  48. package/build/middleware/common/match.d.ts +0 -3
  49. package/build/middleware/common/match.js +0 -12
  50. package/build/middleware/factory-backup.d.ts +0 -3
  51. package/build/middleware/factory-backup.js +0 -11
  52. package/build/redirect.d.ts +0 -2
  53. package/build/redirect.js +0 -10
  54. package/build/routes.d.ts +0 -20
  55. package/build/routes.js +0 -45
  56. package/build/url.d.ts +0 -30
  57. package/build/url.js +0 -22
  58. package/src/group.ts +0 -11
  59. package/src/listener.ts +0 -24
  60. package/src/middleware/common/dispatch.ts +0 -17
  61. package/src/middleware/common/index.ts +0 -5
  62. package/src/middleware/common/match.ts +0 -24
  63. package/src/redirect.ts +0 -16
  64. package/src/routes.ts +0 -68
  65. package/src/url.ts +0 -30
@@ -0,0 +1,93 @@
1
+ import { PLACEHOLDER, STATIC, WILDCARD } from "../symbols";
2
+ class Node {
3
+ children = null;
4
+ parent = null;
5
+ path = null;
6
+ property = null;
7
+ route = null;
8
+ type = null;
9
+ constructor(parent = null) {
10
+ this.parent = parent;
11
+ }
12
+ add(path, route) {
13
+ let node = this, segments = path.split('/'), type = STATIC, unnamed = 0;
14
+ 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();
20
+ }
21
+ node.children.set(segment, (child = new Node(node)));
22
+ if (symbol === ':') {
23
+ child.property = segment.slice(1) || `${unnamed++}`;
24
+ node.children.set(PLACEHOLDER, child);
25
+ type = null;
26
+ }
27
+ else if (symbol === '*') {
28
+ child.property = segment.slice(2) || `${unnamed++}`;
29
+ node.children.set(WILDCARD, child);
30
+ type = null;
31
+ }
32
+ }
33
+ node = child;
34
+ }
35
+ node.path = path;
36
+ node.route = route;
37
+ node.type = type;
38
+ return node;
39
+ }
40
+ find(path) {
41
+ let node = this, parameters = {}, segments = path.split('/'), wildcard = null;
42
+ for (let i = 0, n = segments.length; i < n; i++) {
43
+ let segment = segments[i], wc = node.children?.get(WILDCARD);
44
+ if (wc) {
45
+ wildcard = {
46
+ node: wc,
47
+ value: segments.slice(i).join('/')
48
+ };
49
+ }
50
+ let next = node.children?.get(segment);
51
+ if (next) {
52
+ node = next;
53
+ }
54
+ else {
55
+ node = node.children?.get(PLACEHOLDER);
56
+ if (!node) {
57
+ break;
58
+ }
59
+ parameters[node.property] = segment;
60
+ }
61
+ }
62
+ if ((!node || !node.route) && wildcard) {
63
+ node = wildcard.node;
64
+ parameters[node.property] = wildcard.value;
65
+ }
66
+ if (!node) {
67
+ return {};
68
+ }
69
+ return {
70
+ parameters,
71
+ route: node.route
72
+ };
73
+ }
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
+ let parent = node.parent;
84
+ if (parent && parent.children) {
85
+ parent.children.delete(segments[segments.length - 1]);
86
+ parent.children.delete(WILDCARD);
87
+ parent.children.delete(PLACEHOLDER);
88
+ }
89
+ }
90
+ return node;
91
+ }
92
+ }
93
+ export { Node };
@@ -0,0 +1,14 @@
1
+ declare const normalize: (path: string) => string;
2
+ declare const radixkey: (path: string, { method, subdomain }?: {
3
+ method?: string | null | undefined;
4
+ subdomain?: string | null | undefined;
5
+ }) => string;
6
+ declare const _default: {
7
+ normalize: (path: string) => string;
8
+ radixkey: (path: string, { method, subdomain }?: {
9
+ method?: string | null | undefined;
10
+ subdomain?: string | null | undefined;
11
+ }) => string;
12
+ };
13
+ export default _default;
14
+ export { normalize, radixkey };
@@ -0,0 +1,21 @@
1
+ const normalize = (path) => {
2
+ if (path[0] !== '/') {
3
+ path = `/${path}`;
4
+ }
5
+ if (path.endsWith('/')) {
6
+ path = path.slice(0, -1);
7
+ }
8
+ return path || '/';
9
+ };
10
+ const radixkey = (path, { method, subdomain } = {}) => {
11
+ let prefix = '';
12
+ if (subdomain) {
13
+ prefix = subdomain + ' ';
14
+ }
15
+ if (method) {
16
+ prefix += method + ' ';
17
+ }
18
+ return prefix.toUpperCase() + normalize(path);
19
+ };
20
+ export default { normalize, radixkey };
21
+ export { normalize, radixkey };
@@ -0,0 +1,2 @@
1
+ declare const _default: (value: string) => string;
2
+ export default _default;
@@ -0,0 +1,3 @@
1
+ export default (value) => {
2
+ return value.replace(/\W+/g, '-').replace(/[-]+$/, '').toLowerCase();
3
+ };
@@ -0,0 +1,4 @@
1
+ declare const PLACEHOLDER = 0;
2
+ declare const STATIC = 1;
3
+ declare const WILDCARD = 2;
4
+ export { PLACEHOLDER, STATIC, WILDCARD };
@@ -0,0 +1,4 @@
1
+ const PLACEHOLDER = 0;
2
+ const STATIC = 1;
3
+ const WILDCARD = 2;
4
+ export { PLACEHOLDER, STATIC, WILDCARD };
package/build/types.d.ts CHANGED
@@ -1,26 +1,12 @@
1
- import { parse } from './url';
2
- import routes from './routes';
3
- type Group = {
4
- middleware: Middleware[];
5
- name: string;
6
- path: string;
7
- subdomain: string;
8
- };
9
- type Middleware = (request: Request, next: Next) => unknown;
10
- type Next = (request: Request) => unknown;
11
- type Request = ReturnType<typeof parse>;
12
- type Responder = (request: Request) => Promise<unknown> | unknown;
13
- type Routes = {
14
- add: typeof routes.add;
15
- group: typeof routes.group;
16
- routes: typeof routes.routes;
17
- subdomains: typeof routes.subdomains;
18
- };
19
- type Route = {
20
- middleware: Middleware[];
21
- name: string;
22
- path?: string;
23
- responder: Responder;
24
- subdomain?: string;
25
- };
26
- export { Group, Middleware, Next, Request, Responder, Route, Routes };
1
+ import { Route, Router } from './router';
2
+ type Middleware = <T>(request: T, next: Next) => unknown;
3
+ type Next = <T>(request: T) => unknown;
4
+ type Options = {
5
+ middleware?: Middleware[];
6
+ name?: string;
7
+ path?: string;
8
+ responder: Responder;
9
+ subdomain?: string;
10
+ };
11
+ type Responder = <T>(request: T) => Promise<unknown> | unknown;
12
+ export { Middleware, Next, Options, Responder, Route, Router };
package/build/types.js CHANGED
@@ -1 +1,2 @@
1
- export {};
1
+ import { Route, Router } from './router';
2
+ export { Route, Router };
package/package.json CHANGED
@@ -2,8 +2,7 @@
2
2
  "author": "ICJR",
3
3
  "description": "Routing",
4
4
  "devDependencies": {
5
- "tsc-alias": "^1.8.2",
6
- "typescript": "^4.9.4"
5
+ "@esportsplus/rspack": "^0.0.15"
7
6
  },
8
7
  "main": "./build/index.js",
9
8
  "name": "@esportsplus/routing",
@@ -15,5 +14,5 @@
15
14
  "prepublishOnly": "npm run build"
16
15
  },
17
16
  "types": "./build/index.d.ts",
18
- "version": "0.0.8"
17
+ "version": "0.0.10"
19
18
  }
package/readme.md ADDED
@@ -0,0 +1 @@
1
+ https://github.com/unjs/radix3
package/src/hash.ts ADDED
@@ -0,0 +1,106 @@
1
+ import { Router } from './types';
2
+
3
+
4
+ let cache: {
5
+ factory: typeof request;
6
+ state: Record<PropertyKey, unknown>;
7
+ }[] = [],
8
+ registered = false;
9
+
10
+
11
+ function update() {
12
+ for (let i = 0, n = cache.length; i < n; i++) {
13
+ let { factory, state } = cache[i],
14
+ values = factory();
15
+
16
+ for (let key in values) {
17
+ state[key] = values[key as keyof typeof values];
18
+ }
19
+ }
20
+ }
21
+
22
+
23
+ const back = () => window.history.back();
24
+
25
+ const forward = () => window.history.forward();
26
+
27
+ const listener = {
28
+ register: (factory: typeof cache[0]['factory'], state: typeof cache[0]['state']) => {
29
+ cache.push({ factory, state });
30
+
31
+ if (!registered) {
32
+ registered = true;
33
+ update();
34
+
35
+ window.addEventListener('popstate', update);
36
+ }
37
+
38
+ return () => {
39
+ listener.remove(state);
40
+ };
41
+ },
42
+ remove: (state: typeof cache[0]['state']) => {
43
+ for (let i = 0, n = cache.length; i < n; i++) {
44
+ if (cache[i].state !== state) {
45
+ continue;
46
+ }
47
+
48
+ cache[i] = cache[n - 1];
49
+ cache.pop();
50
+
51
+ if (cache.length === 0) {
52
+ window.removeEventListener('popstate', update);
53
+ }
54
+ return;
55
+ }
56
+ }
57
+ };
58
+
59
+ const factory = {
60
+ redirect: (router: Router) => {
61
+ return (path: string, { state, values }: { state?: Record<PropertyKey, unknown>; values?: unknown[] }) => {
62
+ if (path.startsWith('http://') || path.startsWith('https://')) {
63
+ return window.location.replace(path);
64
+ }
65
+
66
+ let uri = router.uri(path, values || []);
67
+
68
+ if (uri[0] === '/') {
69
+ uri = '#' + uri;
70
+ }
71
+
72
+ window.history.pushState(state || {}, '', uri);
73
+ };
74
+ },
75
+ uri: (router: Router) => {
76
+ return (path: string, values: unknown[] = []) => {
77
+ let uri = router.uri(path, values || []);
78
+
79
+ if (uri[0] === '/') {
80
+ uri = '#' + uri;
81
+ }
82
+
83
+ return uri;
84
+ };
85
+ }
86
+ };
87
+
88
+ const request = (url: string = window?.location?.href || '') => {
89
+ let { hash, hostname, href, origin, port, protocol } = new URL( url ),
90
+ path = hash?.replace('#/', '/')?.split('?') || ['/', ''];
91
+
92
+ return {
93
+ href,
94
+ hostname,
95
+ method: 'GET',
96
+ origin,
97
+ path: path[0],
98
+ port,
99
+ protocol,
100
+ query: Object.fromEntries( (new URLSearchParams(path[1])).entries() )
101
+ };
102
+ };
103
+
104
+
105
+ export default { back, factory, forward, listener, request };
106
+ export { back, factory, forward, listener, request };
package/src/index.ts CHANGED
@@ -1,10 +1,8 @@
1
+ import hash from './hash';
1
2
  import middleware from './middleware';
2
- import redirect from './redirect';
3
- import listener from './listener';
4
- import routes from './routes';
5
- import url from './url';
3
+ import router from './router';
4
+ import slugify from './slugify';
6
5
 
7
6
 
8
- export default { listener, middleware, redirect, routes, url };
9
- export { listener, middleware, redirect, routes, url };
7
+ export { hash, middleware, router, slugify };
10
8
  export * from './types';
@@ -0,0 +1,12 @@
1
+ import { Router } from '~/types';
2
+
3
+
4
+ export default (request: { data: ReturnType<Router['match']> }) => {
5
+ let { route } = request.data;
6
+
7
+ if (!route) {
8
+ throw new Error(`Routing: route dispatching failed, route is undefined!`);
9
+ }
10
+
11
+ return route.dispatcher(request);
12
+ };
@@ -1,21 +1,22 @@
1
- import { Middleware, Next, Request } from '~/types';
1
+ import { Middleware, Next } from '~/types';
2
2
 
3
3
 
4
4
  function error() {
5
- throw new Error('Request middleware did not return a responder');
5
+ throw new Error('Routing: request middleware did not return a responder');
6
6
  }
7
7
 
8
8
 
9
- export default (...m: Middleware[]) => {
10
- let middleware: Next[] = [];
9
+ // TODO: Use '@esportsplus/middleware'
10
+ export default (...middleware: Middleware[]) => {
11
+ let stack: Next[] = [];
11
12
 
12
- for (let i = 0, n = m.length; i < n; i++) {
13
- middleware[i] = (request: Request) => m[i](request, middleware[i + 1] || error);
13
+ for (let i = 0, n = middleware.length; i < n; i++) {
14
+ stack[i] = <T>(request: T) => middleware[i](request, stack[i + 1] || error);
14
15
  }
15
16
 
16
- if (!middleware.length) {
17
- throw new Error('Request middleware has not been defined');
17
+ if (!stack.length) {
18
+ throw new Error('Routing: request middleware has not been defined');
18
19
  }
19
20
 
20
- return async (request: Request) => await middleware[0](request);
21
+ return <T>(request: T) => stack[0](request);
21
22
  };
@@ -1,6 +1,7 @@
1
- import common from './common';
1
+ import dispatch from './dispatch';
2
2
  import factory from './factory';
3
+ import match from './match';
3
4
 
4
5
 
5
- export default { common, factory };
6
- export { common, factory };
6
+ export default { dispatch, factory, match };
7
+ export { dispatch, factory, match };
@@ -0,0 +1,41 @@
1
+ import { Next, Router } from '~/types';
2
+
3
+
4
+ type Request = {
5
+ data: ReturnType<Router['match']>;
6
+ hostname: string;
7
+ method: string;
8
+ path: string;
9
+ subdomain?: string;
10
+ };
11
+
12
+
13
+ export default (router: Router, spa = false) => {
14
+ let subdomain: string | null = null;
15
+
16
+ return (request: Request, next: Next) => {
17
+ if ((typeof request.subdomain !== 'string' && !spa) || subdomain === null) {
18
+ if (router.subdomains) {
19
+ for (let i = 0, n = router.subdomains.length; i < n; i++) {
20
+ if (!request.hostname.startsWith(router.subdomains[i])) {
21
+ continue;
22
+ }
23
+
24
+ subdomain = router.subdomains[i];
25
+ break;
26
+ }
27
+ }
28
+
29
+ if (subdomain === null) {
30
+ subdomain = '';
31
+ }
32
+ }
33
+
34
+ let { parameters, route } = router.match(request.method, request.path, request.subdomain || subdomain);
35
+
36
+ request.data.parameters = parameters;
37
+ request.data.route = route;
38
+
39
+ return next(request);
40
+ };
41
+ };
@@ -0,0 +1,237 @@
1
+ import { STATIC } from "~/symbols";
2
+ import { Middleware, Responder, Options } from '~/types';
3
+ import { Node } from './node';
4
+ import { normalize, radixkey } from './path';
5
+ import factory from '~/middleware/factory';
6
+
7
+
8
+ let { isArray } = Array;
9
+
10
+
11
+ function set(route: Route, key: keyof Route, value?: any) {
12
+ if (!value) {
13
+ return;
14
+ }
15
+
16
+ if (!route[key]) {
17
+ (route[key] as any) = value;
18
+ }
19
+ else if (typeof value === 'string') {
20
+ if (typeof route[key] === 'string') {
21
+ (route[key] as string) += value;
22
+ }
23
+ }
24
+ else if (isArray(value)) {
25
+ if (isArray(route[key])) {
26
+ (route[key] as any[]).push( ...value );
27
+ }
28
+ }
29
+ }
30
+
31
+
32
+ class Route {
33
+ dispatch: ReturnType<typeof factory> | null = null;
34
+ name: string | null = null;
35
+ path: string | null = null;
36
+ responder: Responder;
37
+ stack: Middleware[] | null = null;
38
+ subdomain: string | null = null;
39
+
40
+
41
+ constructor(responder: Responder) {
42
+ this.responder = responder;
43
+ }
44
+
45
+
46
+ get dispatcher() {
47
+ if (this.dispatch === null) {
48
+ if (!this.stack?.length) {
49
+ this.dispatch = <T>(request: T) => this.responder(request);
50
+ }
51
+ else {
52
+ this.dispatch = factory(...this.stack, (request => this.responder(request)));
53
+ }
54
+ }
55
+
56
+ return this.dispatch;
57
+ }
58
+ }
59
+
60
+ class Router {
61
+ groups: Omit<Options, 'responder'>[] = [];
62
+ root: Node;
63
+ routes: Record<string, Route> = {};
64
+ static: Record<string, Route> = {};
65
+ subdomains: string[] | null = null;
66
+
67
+
68
+ constructor() {
69
+ this.root = new Node();
70
+ }
71
+
72
+
73
+ private add(radixkey: string, route: Route) {
74
+ let node = this.root.add(radixkey, route);
75
+
76
+ if (node.type === STATIC) {
77
+ if (this.static[radixkey]) {
78
+ throw new Error(`Routing: static path '${radixkey}' is already in use`);
79
+ }
80
+
81
+ this.static[radixkey] = route;
82
+ }
83
+ }
84
+
85
+ private route({ middleware, name, path, responder, subdomain }: Options) {
86
+ let route = new Route(responder);
87
+
88
+ for (let i = 0, n = this.groups.length; i < n; i++) {
89
+ let { middleware, name, path, subdomain } = this.groups[i];
90
+
91
+ set(route, 'name', name);
92
+ set(route, 'path', path);
93
+ set(route, 'stack', middleware);
94
+ set(route, 'subdomain', subdomain);
95
+ }
96
+
97
+ set(route, 'name', name);
98
+ set(route, 'path', path);
99
+ set(route, 'stack', middleware);
100
+ set(route, 'subdomain', subdomain);
101
+
102
+ if (route.path) {
103
+ route.path = normalize(route.path);
104
+ }
105
+
106
+ if (route.subdomain === 'www') {
107
+ route.subdomain = '';
108
+ }
109
+
110
+ return route;
111
+ }
112
+
113
+
114
+ delete(options: Options) {
115
+ this.on(['DELETE'], options);
116
+ return this;
117
+ }
118
+
119
+ get(options: Options) {
120
+ this.on(['GET'], options);
121
+ return this;
122
+ }
123
+
124
+ group(options: Router['groups'][0]) {
125
+ return {
126
+ routes: (fn: (router: Router) => void) => {
127
+ this.groups.push(options);
128
+ fn(this);
129
+ this.groups.pop();
130
+ }
131
+ }
132
+ }
133
+
134
+ match(method: string, path: string, subdomain?: string | null): ReturnType<Node['find']> {
135
+ let key = radixkey(path, { method, subdomain });
136
+
137
+ if (this.static[key]) {
138
+ return {
139
+ route: this.static[key]
140
+ };
141
+ }
142
+
143
+ return this.root.find(key);
144
+ }
145
+
146
+ on(methods: string[], options: Options) {
147
+ let route = this.route(options);
148
+
149
+ if (route.name) {
150
+ if (this.routes[route.name]) {
151
+ throw new Error(`Routing: '${route.name}' is already in use`);
152
+ }
153
+
154
+ this.routes[route.name] = route;
155
+ }
156
+
157
+ if (route.path) {
158
+ for (let i = 0, n = methods.length; i < n; i++) {
159
+ let key = radixkey(route.path, {
160
+ method: methods[i],
161
+ subdomain: route.subdomain
162
+ });
163
+
164
+ if (key.indexOf('?:') !== -1) {
165
+ let segments = key.split('?:'),
166
+ url = '';
167
+
168
+ for (let i = 0, n = segments.length; i < n; i++) {
169
+ this.add((url += (i > 0 ? '/:' : '/') + segments[i]), route);
170
+ }
171
+ }
172
+ else {
173
+ this.add(key, route);
174
+ }
175
+ }
176
+ }
177
+
178
+ if (route.subdomain) {
179
+ if (!this.subdomains) {
180
+ this.subdomains = [route.subdomain];
181
+ }
182
+ else {
183
+ this.subdomains.push(route.subdomain);
184
+ }
185
+ }
186
+ }
187
+
188
+ post(options: Options) {
189
+ this.on(['POST'], options);
190
+ return this;
191
+ }
192
+
193
+ put(options: Options) {
194
+ this.on(['PUT'], options);
195
+ return this;
196
+ }
197
+
198
+ uri(name: string, values: unknown[] = []) {
199
+ let path = this.routes?.[name]?.path;
200
+
201
+ if (!path) {
202
+ throw new Error(`Routing: route name '${name}' does not exist or it does not provide a path`);
203
+ }
204
+
205
+ let resolved = [] as typeof values,
206
+ segments = path.split('/');
207
+
208
+ for (let i = 0, n = segments.length; i < n; i++) {
209
+ let segment = segments[i],
210
+ symbol = segment[0];
211
+
212
+ if (symbol === ':') {
213
+ resolved.push(values[i]);
214
+ }
215
+ else if (symbol === '?') {
216
+ if (values[i] === undefined) {
217
+ break;
218
+ }
219
+
220
+ resolved.push(values[i]);
221
+ }
222
+ else if (symbol === '*') {
223
+ resolved.push( ...values.slice(i) );
224
+ break;
225
+ }
226
+ else {
227
+ resolved.push(segment);
228
+ }
229
+ }
230
+
231
+ return resolved.join('/');
232
+ }
233
+ }
234
+
235
+
236
+ export default (...args: ConstructorParameters<typeof Router>) => new Router(...args);
237
+ export { Router, Route };