valyrian.js 7.2.12 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/flux-store/index.d.ts +32 -0
- package/dist/flux-store/index.d.ts.map +1 -0
- package/dist/flux-store/index.js +267 -0
- package/dist/flux-store/index.js.map +7 -0
- package/dist/flux-store/index.min.js +1 -0
- package/dist/flux-store/index.min.js.map +1 -0
- package/dist/flux-store/index.mjs +246 -0
- package/dist/flux-store/index.mjs.map +7 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +35 -51
- package/dist/hooks/index.js.map +3 -3
- package/dist/hooks/index.min.js +1 -0
- package/dist/hooks/index.min.js.map +1 -0
- package/dist/hooks/index.mjs +36 -52
- package/dist/hooks/index.mjs.map +3 -3
- package/dist/index.d.ts +18 -14
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +107 -88
- package/dist/index.js.map +3 -3
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.mjs +107 -88
- package/dist/index.mjs.map +3 -3
- package/dist/native-store/index.d.ts +14 -0
- package/dist/native-store/index.d.ts.map +1 -0
- package/dist/native-store/index.js +103 -0
- package/dist/native-store/index.js.map +7 -0
- package/dist/native-store/index.min.js +1 -0
- package/dist/native-store/index.min.js.map +1 -0
- package/dist/native-store/index.mjs +82 -0
- package/dist/native-store/index.mjs.map +7 -0
- package/dist/node/index.d.ts +1 -1
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +125 -10
- package/dist/node/index.js.map +4 -4
- package/dist/node/index.mjs +125 -10
- package/dist/node/index.mjs.map +4 -4
- package/dist/node/node.sw.js +152 -0
- package/dist/node/utils/icons.d.ts +4 -5
- package/dist/node/utils/icons.d.ts.map +1 -1
- package/dist/node/utils/inline.d.ts +1 -1
- package/dist/node/utils/inline.d.ts.map +1 -1
- package/dist/node/utils/node.sw.js +152 -0
- package/dist/node/utils/session-storage.d.ts +22 -0
- package/dist/node/utils/session-storage.d.ts.map +1 -0
- package/dist/node/utils/sw.d.ts.map +1 -1
- package/dist/node/utils/tree-adapter.d.ts +5 -1
- package/dist/node/utils/tree-adapter.d.ts.map +1 -1
- package/dist/pulse-store/index.d.ts +16 -0
- package/dist/pulse-store/index.d.ts.map +1 -0
- package/dist/pulse-store/index.js +143 -0
- package/dist/pulse-store/index.js.map +7 -0
- package/dist/pulse-store/index.min.js +1 -0
- package/dist/pulse-store/index.min.js.map +1 -0
- package/dist/pulse-store/index.mjs +122 -0
- package/dist/pulse-store/index.mjs.map +7 -0
- package/dist/request/index.d.ts +11 -11
- package/dist/request/index.d.ts.map +1 -1
- package/dist/request/index.js +63 -84
- package/dist/request/index.js.map +2 -2
- package/dist/request/index.min.js +1 -0
- package/dist/request/index.min.js.map +1 -0
- package/dist/request/index.mjs +63 -84
- package/dist/request/index.mjs.map +2 -2
- package/dist/router/index.d.ts +36 -33
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +247 -96
- package/dist/router/index.js.map +3 -3
- package/dist/router/index.min.js +1 -0
- package/dist/router/index.min.js.map +1 -0
- package/dist/router/index.mjs +247 -96
- package/dist/router/index.mjs.map +3 -3
- package/dist/signals/index.d.ts +6 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/index.js +92 -0
- package/dist/signals/index.js.map +7 -0
- package/dist/signals/index.min.js +1 -0
- package/dist/signals/index.min.js.map +1 -0
- package/dist/signals/index.mjs +71 -0
- package/dist/signals/index.mjs.map +7 -0
- package/dist/suspense/index.d.ts +6 -0
- package/dist/suspense/index.d.ts.map +1 -0
- package/dist/suspense/index.js +67 -0
- package/dist/suspense/index.js.map +7 -0
- package/dist/suspense/index.min.js +1 -0
- package/dist/suspense/index.min.js.map +1 -0
- package/dist/suspense/index.mjs +46 -0
- package/dist/suspense/index.mjs.map +7 -0
- package/dist/sw/index.min.js +1 -0
- package/dist/sw/index.min.js.map +1 -0
- package/dist/translate/index.d.ts +19 -0
- package/dist/translate/index.d.ts.map +1 -0
- package/dist/translate/index.js +150 -0
- package/dist/translate/index.js.map +7 -0
- package/dist/translate/index.min.js +1 -0
- package/dist/translate/index.min.js.map +1 -0
- package/dist/translate/index.mjs +129 -0
- package/dist/translate/index.mjs.map +7 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/deep-freeze.d.ts +3 -0
- package/dist/utils/deep-freeze.d.ts.map +1 -0
- package/dist/utils/getter-setter.d.ts +3 -0
- package/dist/utils/getter-setter.d.ts.map +1 -0
- package/dist/utils/has-changed.d.ts +2 -0
- package/dist/utils/has-changed.d.ts.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +138 -0
- package/dist/utils/index.js.map +7 -0
- package/dist/utils/index.min.js +1 -0
- package/dist/utils/index.min.js.map +1 -0
- package/dist/utils/index.mjs +115 -0
- package/dist/utils/index.mjs.map +7 -0
- package/lib/flux-store/index.ts +312 -0
- package/lib/hooks/index.ts +39 -57
- package/lib/index.ts +135 -118
- package/lib/native-store/index.ts +106 -0
- package/lib/node/index.ts +3 -1
- package/lib/node/utils/icons.ts +4 -4
- package/lib/node/utils/inline.ts +2 -0
- package/lib/node/utils/node.sw.js +152 -0
- package/lib/node/utils/session-storage.ts +117 -0
- package/lib/node/utils/sw.ts +34 -10
- package/lib/node/utils/tree-adapter.ts +19 -1
- package/lib/pulse-store/index.ts +188 -0
- package/lib/request/index.ts +92 -122
- package/lib/router/index.ts +353 -164
- package/lib/signals/index.ts +98 -0
- package/lib/suspense/index.ts +57 -0
- package/lib/translate/index.ts +156 -0
- package/lib/utils/deep-freeze.ts +54 -0
- package/lib/utils/getter-setter.ts +40 -0
- package/lib/utils/has-changed.ts +43 -0
- package/lib/utils/index.ts +3 -0
- package/package.json +40 -57
- package/tsconfig.json +5 -4
- package/dist/dataset/index.d.ts +0 -24
- package/dist/dataset/index.d.ts.map +0 -1
- package/dist/dataset/index.js +0 -178
- package/dist/dataset/index.js.map +0 -7
- package/dist/dataset/index.mjs +0 -157
- package/dist/dataset/index.mjs.map +0 -7
- package/dist/node/node.sw.tpl +0 -133
- package/dist/node/utils/node.sw.tpl +0 -133
- package/dist/proxy-signal/index.d.ts +0 -23
- package/dist/proxy-signal/index.d.ts.map +0 -1
- package/dist/proxy-signal/index.js +0 -138
- package/dist/proxy-signal/index.js.map +0 -7
- package/dist/proxy-signal/index.mjs +0 -117
- package/dist/proxy-signal/index.mjs.map +0 -7
- package/dist/signal/index.d.ts +0 -9
- package/dist/signal/index.d.ts.map +0 -1
- package/dist/signal/index.js +0 -76
- package/dist/signal/index.js.map +0 -7
- package/dist/signal/index.mjs +0 -55
- package/dist/signal/index.mjs.map +0 -7
- package/dist/store/index.d.ts +0 -16
- package/dist/store/index.d.ts.map +0 -1
- package/dist/store/index.js +0 -93
- package/dist/store/index.js.map +0 -7
- package/dist/store/index.mjs +0 -72
- package/dist/store/index.mjs.map +0 -7
- package/lib/dataset/index.ts +0 -193
- package/lib/node/utils/node.sw.tpl +0 -133
- package/lib/proxy-signal/index.ts +0 -187
- package/lib/signal/index.ts +0 -86
- package/lib/store/index.ts +0 -101
package/lib/router/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
1
2
|
/* eslint-disable no-use-before-define */
|
|
2
3
|
import {
|
|
3
4
|
Component,
|
|
@@ -13,19 +14,19 @@ import {
|
|
|
13
14
|
v
|
|
14
15
|
} from "valyrian.js";
|
|
15
16
|
|
|
16
|
-
interface Request {
|
|
17
|
+
export interface Request {
|
|
17
18
|
params: Record<string, any>;
|
|
18
19
|
query: Record<string, any>;
|
|
19
20
|
url: string;
|
|
20
21
|
path: string;
|
|
21
22
|
matches: string[];
|
|
22
23
|
// eslint-disable-next-line no-unused-vars
|
|
23
|
-
redirect: (path: string
|
|
24
|
+
redirect: (path: string) => Promise<string | void>;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
interface Middleware {
|
|
27
|
+
export interface Middleware {
|
|
27
28
|
// eslint-disable-next-line no-unused-vars
|
|
28
|
-
(req: Request,
|
|
29
|
+
(req: Request, err?: any):
|
|
29
30
|
| Promise<any | Component | POJOComponent | VnodeComponentInterface>
|
|
30
31
|
| any
|
|
31
32
|
| Component
|
|
@@ -35,39 +36,13 @@ interface Middleware {
|
|
|
35
36
|
|
|
36
37
|
interface Middlewares extends Array<Middleware> {}
|
|
37
38
|
|
|
38
|
-
interface Path {
|
|
39
|
-
method: string;
|
|
40
|
-
path: string;
|
|
41
|
-
middlewares: Middlewares;
|
|
42
|
-
params: string[];
|
|
43
|
-
regexp: RegExp;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface RouterInterface {
|
|
47
|
-
paths: Path[];
|
|
48
|
-
container: Element | string | null;
|
|
49
|
-
query: Record<string, string | number>;
|
|
50
|
-
options: Record<string, any>;
|
|
51
|
-
url: string;
|
|
52
|
-
path: string;
|
|
53
|
-
params: Record<string, string | number | any>;
|
|
54
|
-
matches: string[];
|
|
55
|
-
pathPrefix: string;
|
|
56
|
-
// eslint-disable-next-line no-unused-vars
|
|
57
|
-
add(method: string, ...args: Middlewares): Router;
|
|
58
|
-
// eslint-disable-next-line no-unused-vars
|
|
59
|
-
use(...args: (string | Middleware | Router)[]): Router;
|
|
60
|
-
|
|
61
|
-
routes(): string[];
|
|
62
|
-
// eslint-disable-next-line no-unused-vars
|
|
63
|
-
go(path: string, parentComponent?: Component | POJOComponent | VnodeComponentInterface): Promise<string | void>;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
39
|
interface RedirectFunction {
|
|
67
|
-
// eslint-disable-next-line no-unused-vars
|
|
68
40
|
(
|
|
41
|
+
// eslint-disable-next-line no-unused-vars
|
|
69
42
|
path: string,
|
|
43
|
+
// eslint-disable-next-line no-unused-vars
|
|
70
44
|
parentComponent?: Component | POJOComponent | VnodeComponentInterface,
|
|
45
|
+
// eslint-disable-next-line no-unused-vars
|
|
71
46
|
preventPushState?: boolean
|
|
72
47
|
): Promise<string | void>;
|
|
73
48
|
}
|
|
@@ -88,132 +63,123 @@ function getPathWithoutLastSlash(path: string) {
|
|
|
88
63
|
return pathWithoutLastSlash;
|
|
89
64
|
}
|
|
90
65
|
|
|
91
|
-
const addPath = ({
|
|
92
|
-
router,
|
|
93
|
-
method,
|
|
94
|
-
path,
|
|
95
|
-
middlewares
|
|
96
|
-
}: {
|
|
97
|
-
router: Router;
|
|
98
|
-
method: string;
|
|
99
|
-
path: string;
|
|
100
|
-
middlewares: Middleware[];
|
|
101
|
-
}): void => {
|
|
102
|
-
if (!method || !path || !Array.isArray(middlewares) || middlewares.length === 0) {
|
|
103
|
-
throw new Error(`Invalid route input: ${method} ${path} ${middlewares}`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Trim trailing slashes from the path
|
|
107
|
-
const realpath = path.replace(/(\S)(\/+)$/, "$1");
|
|
108
|
-
|
|
109
|
-
// Find the express-like params in the path
|
|
110
|
-
const params = (realpath.match(/:(\w+)?/gi) || [])
|
|
111
|
-
// Set the names of the params found
|
|
112
|
-
.map((param) => param.slice(1));
|
|
113
|
-
|
|
114
|
-
// Generate a regular expression to match the path
|
|
115
|
-
const regexpPath = "^" + realpath.replace(/:(\w+)/gi, "([^\\/\\s]+)") + "$";
|
|
116
|
-
|
|
117
|
-
router.paths.push({
|
|
118
|
-
method,
|
|
119
|
-
path: realpath,
|
|
120
|
-
middlewares: flat(middlewares),
|
|
121
|
-
params,
|
|
122
|
-
regexp: new RegExp(regexpPath, "i")
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
66
|
// Parse a query string into an object
|
|
127
|
-
function parseQuery(queryParts?: string): Record<string,
|
|
128
|
-
// Split the query string into an array of name-value pairs
|
|
67
|
+
function parseQuery(queryParts?: string): Record<string, any> {
|
|
129
68
|
const parts = queryParts ? queryParts.split("&") : [];
|
|
130
|
-
const query: Record<string,
|
|
69
|
+
const query: Record<string, any> = {};
|
|
131
70
|
|
|
132
|
-
// Iterate over the name-value pairs and add them to the query object
|
|
133
71
|
for (const nameValue of parts) {
|
|
134
72
|
const [name, value] = nameValue.split("=", 2);
|
|
135
|
-
query[name] =
|
|
73
|
+
query[name] =
|
|
74
|
+
isNaN(Number(value)) === false ? Number(value) : value === "true" ? true : value === "false" ? false : value;
|
|
136
75
|
}
|
|
137
76
|
|
|
138
77
|
return query;
|
|
139
78
|
}
|
|
140
79
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
80
|
+
interface RouteNode {
|
|
81
|
+
segment: string;
|
|
82
|
+
children: Map<string, RouteNode>;
|
|
83
|
+
middlewares?: Middlewares;
|
|
84
|
+
paramKey?: string;
|
|
85
|
+
isDynamic: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
class RouteTree {
|
|
89
|
+
root: RouteNode = { segment: "", children: new Map(), isDynamic: false };
|
|
146
90
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
91
|
+
addRoute(path: string, middlewares: Middlewares) {
|
|
92
|
+
const segments = path === "/" ? [path] : path.split("/").filter(Boolean); // Divide the path into segments
|
|
93
|
+
let currentNode = this.root;
|
|
150
94
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
match.shift();
|
|
95
|
+
for (const segment of segments) {
|
|
96
|
+
const isDynamic = segment.startsWith(":");
|
|
97
|
+
const key = isDynamic ? ":" : segment; // If the segment is dynamic, use ":" as key
|
|
155
98
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
99
|
+
if (!currentNode.children.has(key)) {
|
|
100
|
+
currentNode.children.set(key, {
|
|
101
|
+
segment: segment,
|
|
102
|
+
children: new Map(),
|
|
103
|
+
isDynamic: isDynamic,
|
|
104
|
+
paramKey: isDynamic ? segment.slice(1) : undefined
|
|
105
|
+
});
|
|
159
106
|
}
|
|
160
107
|
|
|
161
|
-
|
|
162
|
-
|
|
108
|
+
currentNode = currentNode.children.get(key)!;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
currentNode.middlewares = middlewares; // Assign the middlewares to the last node
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Search for a route in the tree
|
|
115
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
116
|
+
findRoute(path: string): { middlewares?: Middlewares; params: Record<string, string> } | null {
|
|
117
|
+
const pathWithoutLastSlash = getPathWithoutLastSlash(path);
|
|
118
|
+
const segments =
|
|
119
|
+
pathWithoutLastSlash === "/" ? [pathWithoutLastSlash] : pathWithoutLastSlash.split("/").filter(Boolean);
|
|
120
|
+
let currentNode: RouteNode | null = this.root;
|
|
121
|
+
const params: Record<string, string> = {};
|
|
122
|
+
|
|
123
|
+
const wildcardMiddlewares: Middlewares = []; // Middlewares for wildcard routes
|
|
124
|
+
const segmentsLength = segments.length;
|
|
163
125
|
|
|
164
|
-
|
|
165
|
-
|
|
126
|
+
for (let i = 0; i < segmentsLength; i++) {
|
|
127
|
+
if (!currentNode) {
|
|
166
128
|
break;
|
|
167
129
|
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
130
|
|
|
171
|
-
|
|
172
|
-
router.matches = matches;
|
|
131
|
+
const segment = segments[i];
|
|
173
132
|
|
|
174
|
-
|
|
175
|
-
}
|
|
133
|
+
let found = false;
|
|
176
134
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
params: router.params,
|
|
184
|
-
query: router.query,
|
|
185
|
-
url: router.url,
|
|
186
|
-
path: router.path,
|
|
187
|
-
matches: router.matches,
|
|
188
|
-
redirect: (path: string, parentComponent?: Component | POJOComponent | VnodeComponentInterface) => {
|
|
189
|
-
router.go(path, parentComponent);
|
|
190
|
-
// Return false to stop the middleware chain
|
|
191
|
-
return false;
|
|
192
|
-
}
|
|
193
|
-
};
|
|
135
|
+
for (const [key, child] of currentNode.children) {
|
|
136
|
+
if (key === segment) {
|
|
137
|
+
currentNode = child;
|
|
138
|
+
found = true;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
194
141
|
|
|
195
|
-
|
|
196
|
-
|
|
142
|
+
if (segment !== ".*" && key === ":") {
|
|
143
|
+
currentNode = child;
|
|
144
|
+
params[child.paramKey!] = segment;
|
|
145
|
+
found = true;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
197
148
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
149
|
+
if (key === ".*" && !found) {
|
|
150
|
+
wildcardMiddlewares.push(...(child.middlewares || []));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
202
153
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
154
|
+
if (!found) {
|
|
155
|
+
if (currentNode.children.has(".*")) {
|
|
156
|
+
return { middlewares: wildcardMiddlewares, params };
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
206
160
|
}
|
|
207
161
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
162
|
+
// Add the wildcard middlewares to the current node middlewares
|
|
163
|
+
const allMiddlewares = [...wildcardMiddlewares, ...(currentNode.middlewares || [])];
|
|
164
|
+
|
|
165
|
+
// If there are no middlewares, return null
|
|
166
|
+
if (allMiddlewares.length === 0) {
|
|
167
|
+
return null;
|
|
211
168
|
}
|
|
169
|
+
|
|
170
|
+
// If there are middlewares, return them
|
|
171
|
+
return { middlewares: allMiddlewares, params };
|
|
212
172
|
}
|
|
213
173
|
}
|
|
214
174
|
|
|
215
|
-
export class
|
|
216
|
-
|
|
175
|
+
export const RouterError = class RouterError extends Error {
|
|
176
|
+
status: number | undefined = 500;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
type RouteParams = string | Middleware | Router | (string | Middleware | Router | RouteParams)[];
|
|
180
|
+
|
|
181
|
+
export class Router {
|
|
182
|
+
private routeTree = new RouteTree();
|
|
217
183
|
container: Element | string | null = null;
|
|
218
184
|
query: Record<string, string | number> = {};
|
|
219
185
|
options: Record<string, any> = {};
|
|
@@ -223,54 +189,82 @@ export class Router implements RouterInterface {
|
|
|
223
189
|
matches: string[] = [];
|
|
224
190
|
pathPrefix: string = "";
|
|
225
191
|
|
|
192
|
+
private errorHandlers: Map<number | string | Error | "generic", Middlewares> = new Map();
|
|
193
|
+
|
|
226
194
|
constructor(pathPrefix: string = "") {
|
|
227
195
|
this.pathPrefix = pathPrefix;
|
|
228
196
|
}
|
|
229
197
|
|
|
230
|
-
add(
|
|
231
|
-
const
|
|
232
|
-
addPath({ router: this, method: "add", path: pathWithoutLastSlash, middlewares });
|
|
233
|
-
return this;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
use(...middlewares: Middlewares | Router[] | string[]): Router {
|
|
198
|
+
add(...args: RouteParams[]): Router {
|
|
199
|
+
const flatArgs = flat(args);
|
|
237
200
|
const path = getPathWithoutLastSlash(
|
|
238
|
-
`${this.pathPrefix}${typeof
|
|
201
|
+
`${this.pathPrefix}${typeof flatArgs[0] === "string" ? flatArgs.shift() : "/.*"}`
|
|
239
202
|
);
|
|
240
203
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
continue;
|
|
204
|
+
// If the first argument is a Router, add all its routes
|
|
205
|
+
if (flatArgs.length === 1 && flatArgs[0] instanceof Router) {
|
|
206
|
+
const subrouter = flatArgs[0] as Router;
|
|
207
|
+
for (const subroute of subrouter.routes()) {
|
|
208
|
+
const subroutePath = `${path}${subroute}`;
|
|
209
|
+
this.routeTree.addRoute(subroutePath, subrouter.routeTree.findRoute(subroute)!.middlewares || []);
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
// Verify that no middlewares are added when a subrouter is added
|
|
213
|
+
if (flatArgs.some((item) => item instanceof Router)) {
|
|
214
|
+
throw new RouterError("You cannot add middlewares when adding a subrouter.");
|
|
253
215
|
}
|
|
254
216
|
|
|
255
|
-
|
|
256
|
-
|
|
217
|
+
// Verify that all middlewares are functions
|
|
218
|
+
if (flatArgs.some((item) => typeof item !== "function")) {
|
|
219
|
+
throw new RouterError("All middlewares must be functions.");
|
|
257
220
|
}
|
|
221
|
+
|
|
222
|
+
this.routeTree.addRoute(path, flatArgs as Middlewares);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return this;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
catch(...args: (number | string | Error | typeof Error | Middleware)[]): Router {
|
|
229
|
+
const condition =
|
|
230
|
+
typeof args[0] === "number" || typeof args[0] === "string" || args[0].name.includes("Error")
|
|
231
|
+
? (args.shift() as number | string | Error)
|
|
232
|
+
: "generic";
|
|
233
|
+
|
|
234
|
+
if (typeof condition !== "number" && typeof condition !== "string" && !condition.name.includes("Error")) {
|
|
235
|
+
throw new RouterError("The condition must be a number, string or an instance of Error.");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Verify that all middlewares are functions
|
|
239
|
+
if (args.some((item) => typeof item !== "function")) {
|
|
240
|
+
throw new RouterError("All middlewares must be functions.");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let handlers = this.errorHandlers.get(condition);
|
|
244
|
+
if (!handlers) {
|
|
245
|
+
handlers = [];
|
|
246
|
+
this.errorHandlers.set(condition, handlers);
|
|
258
247
|
}
|
|
259
248
|
|
|
249
|
+
handlers.push(...(args as Middlewares));
|
|
260
250
|
return this;
|
|
261
251
|
}
|
|
262
252
|
|
|
263
253
|
routes(): string[] {
|
|
264
|
-
return this.
|
|
254
|
+
return this.getAllRoutes(this.routeTree.root, "");
|
|
265
255
|
}
|
|
266
256
|
|
|
257
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
267
258
|
async go(
|
|
268
259
|
path: string,
|
|
269
|
-
parentComponent?: Component | POJOComponent | VnodeComponentInterface
|
|
270
|
-
preventPushState = false
|
|
260
|
+
parentComponent?: Component | POJOComponent | VnodeComponentInterface
|
|
271
261
|
): Promise<string | void> {
|
|
272
262
|
if (!path) {
|
|
273
|
-
|
|
263
|
+
return this.handleError(new RouterError("The URL is empty."), parentComponent);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (/%[^0-9A-Fa-f]{2}/.test(path)) {
|
|
267
|
+
return this.handleError(new RouterError(`The URL ${path} is malformed.`));
|
|
274
268
|
}
|
|
275
269
|
|
|
276
270
|
const constructedPath = getPathWithoutLastSlash(`${this.pathPrefix}${path}`);
|
|
@@ -278,15 +272,46 @@ export class Router implements RouterInterface {
|
|
|
278
272
|
this.url = constructedPath;
|
|
279
273
|
this.query = parseQuery(parts[1]);
|
|
280
274
|
|
|
281
|
-
const
|
|
282
|
-
|
|
275
|
+
const finalPath = parts[0].replace(/(.+)\/$/, "$1").split("#")[0];
|
|
276
|
+
this.path = path;
|
|
277
|
+
|
|
278
|
+
let route = this.routeTree.findRoute(finalPath);
|
|
279
|
+
|
|
280
|
+
if (!route || !route.middlewares) {
|
|
281
|
+
// If the route is not found, search for a wildcard route
|
|
282
|
+
const finalPathParts = finalPath.split("/"); // Divide the path into segments
|
|
283
|
+
|
|
284
|
+
while (finalPathParts.length > 0) {
|
|
285
|
+
finalPathParts.pop(); // Remove the last segment
|
|
286
|
+
const wildcardRoute = this.routeTree.findRoute(finalPathParts.join("/") + "/.*"); // Search for a wildcard route
|
|
287
|
+
if (wildcardRoute) {
|
|
288
|
+
route = wildcardRoute;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// If no route is found, return a 404 error
|
|
294
|
+
if (!route || !route.middlewares) {
|
|
295
|
+
const error = new RouterError(`The URL ${constructedPath} was not found in the router's registered paths.`);
|
|
296
|
+
(error as any).status = 404;
|
|
297
|
+
return this.handleError(error, parentComponent);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const { middlewares, params } = route;
|
|
302
|
+
this.params = params;
|
|
303
|
+
|
|
304
|
+
let component = await this.searchComponent(middlewares, parentComponent);
|
|
283
305
|
|
|
284
306
|
if (component === false) {
|
|
285
307
|
return;
|
|
286
308
|
}
|
|
287
309
|
|
|
288
310
|
if (!component) {
|
|
289
|
-
|
|
311
|
+
return this.handleError(
|
|
312
|
+
new RouterError(`The URL ${constructedPath} did not return a valid component.`),
|
|
313
|
+
parentComponent
|
|
314
|
+
);
|
|
290
315
|
}
|
|
291
316
|
|
|
292
317
|
if (isComponent(parentComponent) || isVnodeComponent(parentComponent)) {
|
|
@@ -299,7 +324,7 @@ export class Router implements RouterInterface {
|
|
|
299
324
|
}
|
|
300
325
|
}
|
|
301
326
|
|
|
302
|
-
if (!isNodeJs &&
|
|
327
|
+
if (!isNodeJs && window.location.pathname + window.location.search !== constructedPath) {
|
|
303
328
|
window.history.pushState(null, "", constructedPath);
|
|
304
329
|
}
|
|
305
330
|
|
|
@@ -310,23 +335,188 @@ export class Router implements RouterInterface {
|
|
|
310
335
|
|
|
311
336
|
getOnClickHandler(url: string) {
|
|
312
337
|
return (e: MouseEvent) => {
|
|
338
|
+
if (e.button !== 0 || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || e.defaultPrevented) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
313
342
|
if (typeof url === "string" && url.length > 0) {
|
|
314
343
|
this.go(url);
|
|
315
344
|
}
|
|
316
345
|
e.preventDefault();
|
|
317
346
|
};
|
|
318
347
|
}
|
|
348
|
+
|
|
349
|
+
private getAllRoutes(node: RouteNode, prefix: string): string[] {
|
|
350
|
+
const routes: string[] = [];
|
|
351
|
+
|
|
352
|
+
for (const [key, child] of node.children) {
|
|
353
|
+
// eslint-disable-next-line sonarjs/no-nested-template-literals
|
|
354
|
+
const newPrefix = `${prefix}/${child.isDynamic ? `:${child.paramKey}` : key}`.replace(/\/$/, "");
|
|
355
|
+
if (child.middlewares) {
|
|
356
|
+
routes.push(newPrefix);
|
|
357
|
+
}
|
|
358
|
+
routes.push(...this.getAllRoutes(child, newPrefix));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return routes;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
private createRequest(): Request {
|
|
365
|
+
return {
|
|
366
|
+
params: this.params,
|
|
367
|
+
query: this.query,
|
|
368
|
+
url: this.url,
|
|
369
|
+
path: this.path,
|
|
370
|
+
matches: this.matches,
|
|
371
|
+
redirect: (path: string) => this.go(path)
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private getErrorConditionMiddlewares(error: any): Middlewares | false {
|
|
376
|
+
// Search first for class and name errors
|
|
377
|
+
for (const [condition, middlewares] of this.errorHandlers) {
|
|
378
|
+
if (
|
|
379
|
+
typeof condition !== "number" &&
|
|
380
|
+
typeof condition !== "string" &&
|
|
381
|
+
error instanceof (condition as any) &&
|
|
382
|
+
error.name === condition.name
|
|
383
|
+
) {
|
|
384
|
+
return middlewares;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// then for code errors
|
|
389
|
+
for (const [condition, middlewares] of this.errorHandlers) {
|
|
390
|
+
if (typeof condition === "number" && (error.status === condition || error.code === condition)) {
|
|
391
|
+
return middlewares;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// and then for message errors
|
|
396
|
+
for (const [condition, middlewares] of this.errorHandlers) {
|
|
397
|
+
if (typeof condition === "string" && (error.name === condition || error.message.includes(condition))) {
|
|
398
|
+
return middlewares;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// If no specific error handler is found, return the generic one
|
|
403
|
+
return this.errorHandlers.get("generic") || false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
407
|
+
private async handleError(
|
|
408
|
+
error: Error,
|
|
409
|
+
parentComponent?: Component | POJOComponent | VnodeComponentInterface
|
|
410
|
+
): Promise<void | string> {
|
|
411
|
+
const request: Request = this.createRequest();
|
|
412
|
+
let component = null;
|
|
413
|
+
const middlewares = this.getErrorConditionMiddlewares(error);
|
|
414
|
+
|
|
415
|
+
// If no error handler is found, throw the error
|
|
416
|
+
if (middlewares === false) {
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
let response;
|
|
421
|
+
try {
|
|
422
|
+
for (const middleware of middlewares) {
|
|
423
|
+
response = await middleware(request, error);
|
|
424
|
+
|
|
425
|
+
// If the response is a component or vnode, return it for rendering
|
|
426
|
+
if (response !== undefined && (isComponent(response) || isVnodeComponent(response))) {
|
|
427
|
+
component = response;
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// If the response is false, stop the middleware chain
|
|
432
|
+
if (response === false) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
} catch (err) {
|
|
437
|
+
// If an error occurs during the error handling, we handle it recursively
|
|
438
|
+
(err as Error).cause = error;
|
|
439
|
+
|
|
440
|
+
let errorCauseCount = 0;
|
|
441
|
+
while ((err as Error).cause) {
|
|
442
|
+
errorCauseCount++;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (errorCauseCount > 20) {
|
|
446
|
+
throw new RouterError("Too many error causes. Possible circular error handling.");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return this.handleError(err as Error, parentComponent);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (component) {
|
|
453
|
+
// If there is an error middleware that returns a component, we return it
|
|
454
|
+
if (isComponent(parentComponent) || isVnodeComponent(parentComponent)) {
|
|
455
|
+
const childComponent = isVnodeComponent(component) ? component : v(component as Component, {});
|
|
456
|
+
if (isVnodeComponent(parentComponent)) {
|
|
457
|
+
parentComponent.children.push(childComponent);
|
|
458
|
+
component = parentComponent;
|
|
459
|
+
} else {
|
|
460
|
+
component = v(parentComponent, {}, childComponent) as VnodeComponentInterface;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// If we are in the browser, we update the URL
|
|
465
|
+
if (!isNodeJs && window.location.pathname + window.location.search !== this.url) {
|
|
466
|
+
window.history.pushState(null, "", this.url);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// If there is a container, we mount the component
|
|
470
|
+
if (this.container) {
|
|
471
|
+
return mount(this.container, component);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// If there is no component to render, we throw the error
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private async searchComponent(
|
|
480
|
+
middlewares: Middlewares,
|
|
481
|
+
parentComponent?: Component | POJOComponent | VnodeComponentInterface
|
|
482
|
+
) {
|
|
483
|
+
const request: Request = this.createRequest();
|
|
484
|
+
|
|
485
|
+
let response;
|
|
486
|
+
|
|
487
|
+
for (const middleware of middlewares) {
|
|
488
|
+
try {
|
|
489
|
+
response = await middleware(request);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
return this.handleError(error as Error, parentComponent);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// If the response is a component or vnode, return it for rendering
|
|
495
|
+
if (response !== undefined && (isComponent(response) || isVnodeComponent(response))) {
|
|
496
|
+
return response;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// If the response is false, stop the middleware chain
|
|
500
|
+
if (response === false) {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return response;
|
|
506
|
+
}
|
|
319
507
|
}
|
|
320
508
|
|
|
321
509
|
let localRedirect: RedirectFunction;
|
|
322
510
|
|
|
323
|
-
export function redirect(
|
|
511
|
+
export async function redirect(
|
|
324
512
|
url: string,
|
|
325
513
|
parentComponent?: Component | POJOComponent | VnodeComponentInterface,
|
|
326
514
|
preventPushState = false
|
|
327
515
|
): Promise<string | void> {
|
|
328
516
|
if (!localRedirect) {
|
|
329
|
-
|
|
517
|
+
// eslint-disable-next-line no-console
|
|
518
|
+
console.warn("Redirect function is not initialized. Please mount the router first.");
|
|
519
|
+
return;
|
|
330
520
|
}
|
|
331
521
|
return localRedirect(url, parentComponent, preventPushState);
|
|
332
522
|
}
|
|
@@ -338,14 +528,13 @@ export function mountRouter(elementContainer: string | any, router: Router): voi
|
|
|
338
528
|
if (!isNodeJs) {
|
|
339
529
|
function onPopStateGoToRoute(): void {
|
|
340
530
|
const pathWithoutPrefix = getPathWithoutPrefix(document.location.pathname, router.pathPrefix);
|
|
341
|
-
(router as unknown as Router).go(pathWithoutPrefix
|
|
531
|
+
(router as unknown as Router).go(pathWithoutPrefix);
|
|
342
532
|
}
|
|
343
533
|
window.addEventListener("popstate", onPopStateGoToRoute, false);
|
|
344
534
|
onPopStateGoToRoute();
|
|
345
535
|
}
|
|
346
536
|
|
|
347
|
-
directive("route", (vnode: VnodeWithDom): void => {
|
|
348
|
-
const url = vnode.props["v-route"];
|
|
537
|
+
directive("route", (url: string, vnode: VnodeWithDom): void => {
|
|
349
538
|
setAttribute("href", url, vnode);
|
|
350
539
|
setAttribute("onclick", router.getOnClickHandler(url), vnode);
|
|
351
540
|
});
|