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.
Files changed (168) hide show
  1. package/README.md +6 -6
  2. package/dist/flux-store/index.d.ts +32 -0
  3. package/dist/flux-store/index.d.ts.map +1 -0
  4. package/dist/flux-store/index.js +267 -0
  5. package/dist/flux-store/index.js.map +7 -0
  6. package/dist/flux-store/index.min.js +1 -0
  7. package/dist/flux-store/index.min.js.map +1 -0
  8. package/dist/flux-store/index.mjs +246 -0
  9. package/dist/flux-store/index.mjs.map +7 -0
  10. package/dist/hooks/index.d.ts.map +1 -1
  11. package/dist/hooks/index.js +35 -51
  12. package/dist/hooks/index.js.map +3 -3
  13. package/dist/hooks/index.min.js +1 -0
  14. package/dist/hooks/index.min.js.map +1 -0
  15. package/dist/hooks/index.mjs +36 -52
  16. package/dist/hooks/index.mjs.map +3 -3
  17. package/dist/index.d.ts +18 -14
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +107 -88
  20. package/dist/index.js.map +3 -3
  21. package/dist/index.min.js +1 -1
  22. package/dist/index.min.js.map +1 -1
  23. package/dist/index.mjs +107 -88
  24. package/dist/index.mjs.map +3 -3
  25. package/dist/native-store/index.d.ts +14 -0
  26. package/dist/native-store/index.d.ts.map +1 -0
  27. package/dist/native-store/index.js +103 -0
  28. package/dist/native-store/index.js.map +7 -0
  29. package/dist/native-store/index.min.js +1 -0
  30. package/dist/native-store/index.min.js.map +1 -0
  31. package/dist/native-store/index.mjs +82 -0
  32. package/dist/native-store/index.mjs.map +7 -0
  33. package/dist/node/index.d.ts +1 -1
  34. package/dist/node/index.d.ts.map +1 -1
  35. package/dist/node/index.js +125 -10
  36. package/dist/node/index.js.map +4 -4
  37. package/dist/node/index.mjs +125 -10
  38. package/dist/node/index.mjs.map +4 -4
  39. package/dist/node/node.sw.js +152 -0
  40. package/dist/node/utils/icons.d.ts +4 -5
  41. package/dist/node/utils/icons.d.ts.map +1 -1
  42. package/dist/node/utils/inline.d.ts +1 -1
  43. package/dist/node/utils/inline.d.ts.map +1 -1
  44. package/dist/node/utils/node.sw.js +152 -0
  45. package/dist/node/utils/session-storage.d.ts +22 -0
  46. package/dist/node/utils/session-storage.d.ts.map +1 -0
  47. package/dist/node/utils/sw.d.ts.map +1 -1
  48. package/dist/node/utils/tree-adapter.d.ts +5 -1
  49. package/dist/node/utils/tree-adapter.d.ts.map +1 -1
  50. package/dist/pulse-store/index.d.ts +16 -0
  51. package/dist/pulse-store/index.d.ts.map +1 -0
  52. package/dist/pulse-store/index.js +143 -0
  53. package/dist/pulse-store/index.js.map +7 -0
  54. package/dist/pulse-store/index.min.js +1 -0
  55. package/dist/pulse-store/index.min.js.map +1 -0
  56. package/dist/pulse-store/index.mjs +122 -0
  57. package/dist/pulse-store/index.mjs.map +7 -0
  58. package/dist/request/index.d.ts +11 -11
  59. package/dist/request/index.d.ts.map +1 -1
  60. package/dist/request/index.js +63 -84
  61. package/dist/request/index.js.map +2 -2
  62. package/dist/request/index.min.js +1 -0
  63. package/dist/request/index.min.js.map +1 -0
  64. package/dist/request/index.mjs +63 -84
  65. package/dist/request/index.mjs.map +2 -2
  66. package/dist/router/index.d.ts +36 -33
  67. package/dist/router/index.d.ts.map +1 -1
  68. package/dist/router/index.js +247 -96
  69. package/dist/router/index.js.map +3 -3
  70. package/dist/router/index.min.js +1 -0
  71. package/dist/router/index.min.js.map +1 -0
  72. package/dist/router/index.mjs +247 -96
  73. package/dist/router/index.mjs.map +3 -3
  74. package/dist/signals/index.d.ts +6 -0
  75. package/dist/signals/index.d.ts.map +1 -0
  76. package/dist/signals/index.js +92 -0
  77. package/dist/signals/index.js.map +7 -0
  78. package/dist/signals/index.min.js +1 -0
  79. package/dist/signals/index.min.js.map +1 -0
  80. package/dist/signals/index.mjs +71 -0
  81. package/dist/signals/index.mjs.map +7 -0
  82. package/dist/suspense/index.d.ts +6 -0
  83. package/dist/suspense/index.d.ts.map +1 -0
  84. package/dist/suspense/index.js +67 -0
  85. package/dist/suspense/index.js.map +7 -0
  86. package/dist/suspense/index.min.js +1 -0
  87. package/dist/suspense/index.min.js.map +1 -0
  88. package/dist/suspense/index.mjs +46 -0
  89. package/dist/suspense/index.mjs.map +7 -0
  90. package/dist/sw/index.min.js +1 -0
  91. package/dist/sw/index.min.js.map +1 -0
  92. package/dist/translate/index.d.ts +19 -0
  93. package/dist/translate/index.d.ts.map +1 -0
  94. package/dist/translate/index.js +150 -0
  95. package/dist/translate/index.js.map +7 -0
  96. package/dist/translate/index.min.js +1 -0
  97. package/dist/translate/index.min.js.map +1 -0
  98. package/dist/translate/index.mjs +129 -0
  99. package/dist/translate/index.mjs.map +7 -0
  100. package/dist/tsconfig.tsbuildinfo +1 -1
  101. package/dist/utils/deep-freeze.d.ts +3 -0
  102. package/dist/utils/deep-freeze.d.ts.map +1 -0
  103. package/dist/utils/getter-setter.d.ts +3 -0
  104. package/dist/utils/getter-setter.d.ts.map +1 -0
  105. package/dist/utils/has-changed.d.ts +2 -0
  106. package/dist/utils/has-changed.d.ts.map +1 -0
  107. package/dist/utils/index.d.ts +4 -0
  108. package/dist/utils/index.d.ts.map +1 -0
  109. package/dist/utils/index.js +138 -0
  110. package/dist/utils/index.js.map +7 -0
  111. package/dist/utils/index.min.js +1 -0
  112. package/dist/utils/index.min.js.map +1 -0
  113. package/dist/utils/index.mjs +115 -0
  114. package/dist/utils/index.mjs.map +7 -0
  115. package/lib/flux-store/index.ts +312 -0
  116. package/lib/hooks/index.ts +39 -57
  117. package/lib/index.ts +135 -118
  118. package/lib/native-store/index.ts +106 -0
  119. package/lib/node/index.ts +3 -1
  120. package/lib/node/utils/icons.ts +4 -4
  121. package/lib/node/utils/inline.ts +2 -0
  122. package/lib/node/utils/node.sw.js +152 -0
  123. package/lib/node/utils/session-storage.ts +117 -0
  124. package/lib/node/utils/sw.ts +34 -10
  125. package/lib/node/utils/tree-adapter.ts +19 -1
  126. package/lib/pulse-store/index.ts +188 -0
  127. package/lib/request/index.ts +92 -122
  128. package/lib/router/index.ts +353 -164
  129. package/lib/signals/index.ts +98 -0
  130. package/lib/suspense/index.ts +57 -0
  131. package/lib/translate/index.ts +156 -0
  132. package/lib/utils/deep-freeze.ts +54 -0
  133. package/lib/utils/getter-setter.ts +40 -0
  134. package/lib/utils/has-changed.ts +43 -0
  135. package/lib/utils/index.ts +3 -0
  136. package/package.json +40 -57
  137. package/tsconfig.json +5 -4
  138. package/dist/dataset/index.d.ts +0 -24
  139. package/dist/dataset/index.d.ts.map +0 -1
  140. package/dist/dataset/index.js +0 -178
  141. package/dist/dataset/index.js.map +0 -7
  142. package/dist/dataset/index.mjs +0 -157
  143. package/dist/dataset/index.mjs.map +0 -7
  144. package/dist/node/node.sw.tpl +0 -133
  145. package/dist/node/utils/node.sw.tpl +0 -133
  146. package/dist/proxy-signal/index.d.ts +0 -23
  147. package/dist/proxy-signal/index.d.ts.map +0 -1
  148. package/dist/proxy-signal/index.js +0 -138
  149. package/dist/proxy-signal/index.js.map +0 -7
  150. package/dist/proxy-signal/index.mjs +0 -117
  151. package/dist/proxy-signal/index.mjs.map +0 -7
  152. package/dist/signal/index.d.ts +0 -9
  153. package/dist/signal/index.d.ts.map +0 -1
  154. package/dist/signal/index.js +0 -76
  155. package/dist/signal/index.js.map +0 -7
  156. package/dist/signal/index.mjs +0 -55
  157. package/dist/signal/index.mjs.map +0 -7
  158. package/dist/store/index.d.ts +0 -16
  159. package/dist/store/index.d.ts.map +0 -1
  160. package/dist/store/index.js +0 -93
  161. package/dist/store/index.js.map +0 -7
  162. package/dist/store/index.mjs +0 -72
  163. package/dist/store/index.mjs.map +0 -7
  164. package/lib/dataset/index.ts +0 -193
  165. package/lib/node/utils/node.sw.tpl +0 -133
  166. package/lib/proxy-signal/index.ts +0 -187
  167. package/lib/signal/index.ts +0 -86
  168. package/lib/store/index.ts +0 -101
@@ -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, parentComponent?: Component | POJOComponent | VnodeComponentInterface) => false;
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, res?: any):
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, 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, 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] = value || "";
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
- // Search for middlewares that match a given path
142
- function searchMiddlewares(router: RouterInterface, path: string): Middlewares {
143
- const middlewares: Middlewares = [];
144
- const params: Record<string, any> = {};
145
- const matches = [];
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
- // Search for middlewares
148
- for (const item of router.paths) {
149
- const match = item.regexp.exec(path);
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
- // If we found middlewares
152
- if (Array.isArray(match)) {
153
- middlewares.push(...item.middlewares);
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
- // Parse params
157
- for (const [index, key] of item.params.entries()) {
158
- params[key] = match[index];
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
- // Add remaining matches to the array
162
- matches.push(...match);
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
- if (item.method === "add") {
165
- router.path = getPathWithoutPrefix(item.path, router.pathPrefix);
126
+ for (let i = 0; i < segmentsLength; i++) {
127
+ if (!currentNode) {
166
128
  break;
167
129
  }
168
- }
169
- }
170
130
 
171
- router.params = params;
172
- router.matches = matches;
131
+ const segment = segments[i];
173
132
 
174
- return middlewares;
175
- }
133
+ let found = false;
176
134
 
177
- async function searchComponent(
178
- router: RouterInterface,
179
- middlewares: Middlewares
180
- ): Promise<Component | VnodeComponentInterface | false | void> {
181
- // Define request object with default values
182
- const request: Request = {
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
- // Initialize response variable
196
- let response;
142
+ if (segment !== ".*" && key === ":") {
143
+ currentNode = child;
144
+ params[child.paramKey!] = segment;
145
+ found = true;
146
+ break;
147
+ }
197
148
 
198
- // Iterate through middlewares
199
- for (const middleware of middlewares) {
200
- // Invoke middleware and update response
201
- response = await middleware(request, response);
149
+ if (key === ".*" && !found) {
150
+ wildcardMiddlewares.push(...(child.middlewares || []));
151
+ }
152
+ }
202
153
 
203
- // Return response if it's a valid component
204
- if (response !== undefined && (isComponent(response) || isVnodeComponent(response))) {
205
- return response;
154
+ if (!found) {
155
+ if (currentNode.children.has(".*")) {
156
+ return { middlewares: wildcardMiddlewares, params };
157
+ }
158
+ return null;
159
+ }
206
160
  }
207
161
 
208
- // Return false if response is explicitly false to stop the middleware chain
209
- if (response === false) {
210
- return false;
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 Router implements RouterInterface {
216
- paths: Path[] = [];
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(path: string, ...middlewares: Middlewares): Router {
231
- const pathWithoutLastSlash = getPathWithoutLastSlash(`${this.pathPrefix}${path}`);
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 middlewares[0] === "string" ? middlewares.shift() : "/"}`
201
+ `${this.pathPrefix}${typeof flatArgs[0] === "string" ? flatArgs.shift() : "/.*"}`
239
202
  );
240
203
 
241
- for (const item of middlewares) {
242
- if (item instanceof Router) {
243
- const subrouter = item as Router;
244
- for (const subpath of subrouter.paths) {
245
- addPath({
246
- router: this,
247
- method: subpath.method,
248
- path: `${path}${subpath.path}`.replace(/^\/\//, "/"),
249
- middlewares: subpath.middlewares
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
- if (typeof item === "function") {
256
- addPath({ router: this, method: "use", path: `${path}.*`, middlewares: [item as Middleware] });
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.paths.filter((path) => path.method === "add").map((path) => path.path);
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
- throw new Error("router.url.required");
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 middlewares = searchMiddlewares(this as RouterInterface, parts[0].replace(/(.+)\/$/, "$1").split("#")[0]);
282
- let component = await searchComponent(this as RouterInterface, middlewares);
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
- throw new Error(`The url ${constructedPath} requested wasn't found`);
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 && !preventPushState) {
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
- throw new Error("router.redirect.not.found");
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, undefined, true);
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
  });