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