@esmx/router 3.0.0-rc.18 → 3.0.0-rc.20

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 (158) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +70 -0
  3. package/README.zh-CN.md +70 -0
  4. package/dist/error.d.ts +23 -0
  5. package/dist/error.mjs +61 -0
  6. package/dist/increment-id.d.ts +7 -0
  7. package/dist/increment-id.mjs +11 -0
  8. package/dist/index.d.ts +5 -3
  9. package/dist/index.mjs +14 -3
  10. package/dist/index.test.mjs +8 -0
  11. package/dist/location.d.ts +15 -0
  12. package/dist/location.mjs +53 -0
  13. package/dist/location.test.d.ts +8 -0
  14. package/dist/location.test.mjs +370 -0
  15. package/dist/matcher.d.ts +3 -0
  16. package/dist/matcher.mjs +44 -0
  17. package/dist/matcher.test.mjs +1492 -0
  18. package/dist/micro-app.d.ts +18 -0
  19. package/dist/micro-app.dom.test.d.ts +1 -0
  20. package/dist/micro-app.dom.test.mjs +532 -0
  21. package/dist/micro-app.mjs +80 -0
  22. package/dist/navigation.d.ts +43 -0
  23. package/dist/navigation.mjs +143 -0
  24. package/dist/navigation.test.d.ts +1 -0
  25. package/dist/navigation.test.mjs +681 -0
  26. package/dist/options.d.ts +4 -0
  27. package/dist/options.mjs +88 -0
  28. package/dist/route-task.d.ts +40 -0
  29. package/dist/route-task.mjs +75 -0
  30. package/dist/route-task.test.d.ts +1 -0
  31. package/dist/route-task.test.mjs +673 -0
  32. package/dist/route-transition.d.ts +53 -0
  33. package/dist/route-transition.mjs +307 -0
  34. package/dist/route-transition.test.d.ts +1 -0
  35. package/dist/route-transition.test.mjs +146 -0
  36. package/dist/route.d.ts +72 -0
  37. package/dist/route.mjs +194 -0
  38. package/dist/route.test.d.ts +1 -0
  39. package/dist/route.test.mjs +1664 -0
  40. package/dist/router-back.test.d.ts +1 -0
  41. package/dist/router-back.test.mjs +361 -0
  42. package/dist/router-forward.test.d.ts +1 -0
  43. package/dist/router-forward.test.mjs +376 -0
  44. package/dist/router-go.test.d.ts +1 -0
  45. package/dist/router-go.test.mjs +73 -0
  46. package/dist/router-guards-cleanup.test.d.ts +1 -0
  47. package/dist/router-guards-cleanup.test.mjs +437 -0
  48. package/dist/router-link.d.ts +10 -0
  49. package/dist/router-link.mjs +126 -0
  50. package/dist/router-push.test.d.ts +1 -0
  51. package/dist/router-push.test.mjs +115 -0
  52. package/dist/router-replace.test.d.ts +1 -0
  53. package/dist/router-replace.test.mjs +114 -0
  54. package/dist/router-resolve.test.d.ts +1 -0
  55. package/dist/router-resolve.test.mjs +393 -0
  56. package/dist/router-restart-app.dom.test.d.ts +1 -0
  57. package/dist/router-restart-app.dom.test.mjs +616 -0
  58. package/dist/router-window-navigation.test.d.ts +1 -0
  59. package/dist/router-window-navigation.test.mjs +359 -0
  60. package/dist/router.d.ts +109 -102
  61. package/dist/router.mjs +260 -361
  62. package/dist/types.d.ts +246 -0
  63. package/dist/types.mjs +18 -0
  64. package/dist/util.d.ts +26 -0
  65. package/dist/util.mjs +53 -0
  66. package/dist/util.test.d.ts +1 -0
  67. package/dist/util.test.mjs +1020 -0
  68. package/package.json +10 -13
  69. package/src/error.ts +84 -0
  70. package/src/increment-id.ts +12 -0
  71. package/src/index.test.ts +9 -0
  72. package/src/index.ts +54 -3
  73. package/src/location.test.ts +406 -0
  74. package/src/location.ts +96 -0
  75. package/src/matcher.test.ts +1685 -0
  76. package/src/matcher.ts +59 -0
  77. package/src/micro-app.dom.test.ts +708 -0
  78. package/src/micro-app.ts +101 -0
  79. package/src/navigation.test.ts +858 -0
  80. package/src/navigation.ts +195 -0
  81. package/src/options.ts +131 -0
  82. package/src/route-task.test.ts +901 -0
  83. package/src/route-task.ts +105 -0
  84. package/src/route-transition.test.ts +178 -0
  85. package/src/route-transition.ts +425 -0
  86. package/src/route.test.ts +2014 -0
  87. package/src/route.ts +308 -0
  88. package/src/router-back.test.ts +487 -0
  89. package/src/router-forward.test.ts +506 -0
  90. package/src/router-go.test.ts +91 -0
  91. package/src/router-guards-cleanup.test.ts +595 -0
  92. package/src/router-link.ts +235 -0
  93. package/src/router-push.test.ts +140 -0
  94. package/src/router-replace.test.ts +139 -0
  95. package/src/router-resolve.test.ts +475 -0
  96. package/src/router-restart-app.dom.test.ts +783 -0
  97. package/src/router-window-navigation.test.ts +457 -0
  98. package/src/router.ts +289 -470
  99. package/src/types.ts +341 -0
  100. package/src/util.test.ts +1262 -0
  101. package/src/util.ts +116 -0
  102. package/dist/history/abstract.d.ts +0 -29
  103. package/dist/history/abstract.mjs +0 -107
  104. package/dist/history/base.d.ts +0 -79
  105. package/dist/history/base.mjs +0 -275
  106. package/dist/history/html.d.ts +0 -30
  107. package/dist/history/html.mjs +0 -183
  108. package/dist/history/index.d.ts +0 -7
  109. package/dist/history/index.mjs +0 -16
  110. package/dist/matcher/create-matcher.d.ts +0 -5
  111. package/dist/matcher/create-matcher.mjs +0 -218
  112. package/dist/matcher/create-matcher.spec.mjs +0 -0
  113. package/dist/matcher/index.d.ts +0 -1
  114. package/dist/matcher/index.mjs +0 -1
  115. package/dist/task-pipe/index.d.ts +0 -1
  116. package/dist/task-pipe/index.mjs +0 -1
  117. package/dist/task-pipe/task.d.ts +0 -30
  118. package/dist/task-pipe/task.mjs +0 -66
  119. package/dist/types/index.d.ts +0 -694
  120. package/dist/types/index.mjs +0 -6
  121. package/dist/utils/bom.d.ts +0 -5
  122. package/dist/utils/bom.mjs +0 -10
  123. package/dist/utils/encoding.d.ts +0 -48
  124. package/dist/utils/encoding.mjs +0 -44
  125. package/dist/utils/guards.d.ts +0 -9
  126. package/dist/utils/guards.mjs +0 -12
  127. package/dist/utils/index.d.ts +0 -7
  128. package/dist/utils/index.mjs +0 -27
  129. package/dist/utils/path.d.ts +0 -60
  130. package/dist/utils/path.mjs +0 -282
  131. package/dist/utils/path.spec.mjs +0 -27
  132. package/dist/utils/scroll.d.ts +0 -25
  133. package/dist/utils/scroll.mjs +0 -59
  134. package/dist/utils/utils.d.ts +0 -16
  135. package/dist/utils/utils.mjs +0 -11
  136. package/dist/utils/warn.d.ts +0 -2
  137. package/dist/utils/warn.mjs +0 -12
  138. package/src/history/abstract.ts +0 -149
  139. package/src/history/base.ts +0 -408
  140. package/src/history/html.ts +0 -228
  141. package/src/history/index.ts +0 -20
  142. package/src/matcher/create-matcher.spec.ts +0 -3
  143. package/src/matcher/create-matcher.ts +0 -292
  144. package/src/matcher/index.ts +0 -1
  145. package/src/task-pipe/index.ts +0 -1
  146. package/src/task-pipe/task.ts +0 -97
  147. package/src/types/index.ts +0 -858
  148. package/src/utils/bom.ts +0 -14
  149. package/src/utils/encoding.ts +0 -153
  150. package/src/utils/guards.ts +0 -25
  151. package/src/utils/index.ts +0 -27
  152. package/src/utils/path.spec.ts +0 -32
  153. package/src/utils/path.ts +0 -418
  154. package/src/utils/scroll.ts +0 -120
  155. package/src/utils/utils.ts +0 -30
  156. package/src/utils/warn.ts +0 -13
  157. /package/dist/{matcher/create-matcher.spec.d.ts → index.test.d.ts} +0 -0
  158. /package/dist/{utils/path.spec.d.ts → matcher.test.d.ts} +0 -0
@@ -0,0 +1,53 @@
1
+ import { Route } from './route';
2
+ import type { Router } from './router';
3
+ import { RouteType } from './types';
4
+ import type { RouteConfirmHook, RouteConfirmHookResult, RouteLocationInput, RouteNotifyHook } from './types';
5
+ /**
6
+ * Route transition hooks responsible for handling different stages of the navigation process.
7
+ * Each hook is responsible for a specific aspect of route transition.
8
+ */
9
+ export declare const ROUTE_TRANSITION_HOOKS: {
10
+ fallback(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
11
+ override(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
12
+ asyncComponent(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
13
+ beforeLeave(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
14
+ beforeEnter(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
15
+ beforeUpdate(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
16
+ beforeEach(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
17
+ confirm(to: Route, from: Route | null, router: Router): Promise<RouteConfirmHookResult>;
18
+ };
19
+ /**
20
+ * Route type handlers configuration.
21
+ * Maps each route type to its corresponding navigation handler function.
22
+ * These handlers perform the actual navigation operations like updating browser state,
23
+ * managing micro-app updates, and handling different navigation patterns.
24
+ */
25
+ export declare const ROUTE_TYPE_HANDLERS: {
26
+ push(to: Route, from: Route | null, router: Router): void;
27
+ replace(to: Route, from: Route | null, router: Router): void;
28
+ restartApp(to: Route, from: Route | null, router: Router): void;
29
+ pushWindow(to: Route, from: Route | null, router: Router): unknown;
30
+ replaceWindow(to: Route, from: Route | null, router: Router): unknown;
31
+ pushLayer(to: Route, from: Route | null, router: Router): Promise<import("./types").RouteLayerResult>;
32
+ default(to: Route, from: Route | null, router: Router): void;
33
+ };
34
+ /**
35
+ * Route Transition Manager
36
+ * Responsible for managing all route transition logic, including guard execution,
37
+ * task processing, and status updates.
38
+ */
39
+ export declare class RouteTransition {
40
+ private readonly router;
41
+ route: Route | null;
42
+ private _controller;
43
+ readonly guards: {
44
+ beforeEach: RouteConfirmHook[];
45
+ afterEach: RouteNotifyHook[];
46
+ };
47
+ constructor(router: Router);
48
+ beforeEach(guard: RouteConfirmHook): () => void;
49
+ afterEach(guard: RouteNotifyHook): () => void;
50
+ destroy(): void;
51
+ to(toType: RouteType, toInput: RouteLocationInput): Promise<Route>;
52
+ private _runTask;
53
+ }
@@ -0,0 +1,307 @@
1
+ import { Route } from "./route.mjs";
2
+ import {
3
+ RouteTaskController,
4
+ createRouteTask
5
+ } from "./route-task.mjs";
6
+ import { RouteType } from "./types.mjs";
7
+ import {
8
+ isRouteMatched,
9
+ isUrlEqual,
10
+ isValidConfirmHookResult,
11
+ removeFromArray
12
+ } from "./util.mjs";
13
+ export const ROUTE_TRANSITION_HOOKS = {
14
+ async fallback(to, from, router) {
15
+ if (to.matched.length === 0) {
16
+ return router.parsedOptions.fallback;
17
+ }
18
+ },
19
+ async override(to, from, router) {
20
+ var _a, _b;
21
+ const result = await ((_b = (_a = to.config) == null ? void 0 : _a.override) == null ? void 0 : _b.call(_a, to, from, router));
22
+ if (isValidConfirmHookResult(result)) {
23
+ return result;
24
+ }
25
+ },
26
+ async asyncComponent(to, from, router) {
27
+ await Promise.all(
28
+ to.matched.map(async (matched) => {
29
+ const { asyncComponent, component } = matched;
30
+ if (!component && typeof asyncComponent === "function") {
31
+ try {
32
+ const result = await asyncComponent();
33
+ matched.component = result;
34
+ } catch {
35
+ throw new Error(
36
+ `Async component '${matched.compilePath}' is not a valid component.`
37
+ );
38
+ }
39
+ }
40
+ })
41
+ );
42
+ },
43
+ async beforeLeave(to, from, router) {
44
+ if (!(from == null ? void 0 : from.matched.length)) return;
45
+ const leavingRoutes = from.matched.filter(
46
+ (fromRoute) => !to.matched.some((toRoute) => toRoute === fromRoute)
47
+ );
48
+ for (let i = leavingRoutes.length - 1; i >= 0; i--) {
49
+ const route = leavingRoutes[i];
50
+ if (route.beforeLeave) {
51
+ const result = await route.beforeLeave(to, from, router);
52
+ if (isValidConfirmHookResult(result)) {
53
+ return result;
54
+ }
55
+ }
56
+ }
57
+ },
58
+ async beforeEnter(to, from, router) {
59
+ if (!to.matched.length) return;
60
+ const enteringRoutes = to.matched.filter(
61
+ (toRoute) => !(from == null ? void 0 : from.matched.some((fromRoute) => fromRoute === toRoute))
62
+ );
63
+ for (const route of enteringRoutes) {
64
+ if (route.beforeEnter) {
65
+ const result = await route.beforeEnter(to, from, router);
66
+ if (isValidConfirmHookResult(result)) {
67
+ return result;
68
+ }
69
+ }
70
+ }
71
+ },
72
+ async beforeUpdate(to, from, router) {
73
+ if (!isRouteMatched(to, from, "route")) return;
74
+ if (!from || to.matched.length !== from.matched.length) return;
75
+ const isSameRouteSet = to.matched.every(
76
+ (toRoute, index) => toRoute === from.matched[index]
77
+ );
78
+ if (!isSameRouteSet) return;
79
+ if (!isRouteMatched(to, from, "exact")) {
80
+ for (const route of to.matched) {
81
+ if (route.beforeUpdate) {
82
+ const result = await route.beforeUpdate(to, from, router);
83
+ if (isValidConfirmHookResult(result)) {
84
+ return result;
85
+ }
86
+ }
87
+ }
88
+ }
89
+ },
90
+ async beforeEach(to, from, router) {
91
+ const transition = router.transition;
92
+ for (const guard of transition.guards.beforeEach) {
93
+ const result = await guard(to, from, router);
94
+ if (isValidConfirmHookResult(result)) {
95
+ return result;
96
+ }
97
+ }
98
+ },
99
+ async confirm(to, from, router) {
100
+ if (to.confirm) {
101
+ const result = await to.confirm(to, from, router);
102
+ if (isValidConfirmHookResult(result)) {
103
+ return result;
104
+ }
105
+ }
106
+ switch (to.type) {
107
+ case RouteType.push:
108
+ return ROUTE_TYPE_HANDLERS.push;
109
+ case RouteType.replace:
110
+ return ROUTE_TYPE_HANDLERS.replace;
111
+ case RouteType.restartApp:
112
+ return ROUTE_TYPE_HANDLERS.restartApp;
113
+ case RouteType.pushWindow:
114
+ return ROUTE_TYPE_HANDLERS.pushWindow;
115
+ case RouteType.replaceWindow:
116
+ return ROUTE_TYPE_HANDLERS.replaceWindow;
117
+ case RouteType.pushLayer:
118
+ return ROUTE_TYPE_HANDLERS.pushLayer;
119
+ default:
120
+ return ROUTE_TYPE_HANDLERS.default;
121
+ }
122
+ }
123
+ };
124
+ export const ROUTE_TYPE_HANDLERS = {
125
+ push(to, from, router) {
126
+ router.transition.route = to;
127
+ router.microApp._update(router);
128
+ if (!isUrlEqual(to.url, from == null ? void 0 : from.url)) {
129
+ const newState = router.navigation.push(to.state, to.url);
130
+ to.applyNavigationState(newState);
131
+ } else {
132
+ const newState = router.navigation.replace(to.state, to.url);
133
+ to.applyNavigationState(newState);
134
+ }
135
+ },
136
+ replace(to, from, router) {
137
+ router.transition.route = to;
138
+ router.microApp._update(router);
139
+ const newState = router.navigation.replace(to.state, to.url);
140
+ to.applyNavigationState(newState);
141
+ },
142
+ restartApp(to, from, router) {
143
+ router.transition.route = to;
144
+ router.microApp._update(router, true);
145
+ const newState = router.navigation.replace(to.state, to.url);
146
+ to.applyNavigationState(newState);
147
+ },
148
+ pushWindow(to, from, router) {
149
+ return router.parsedOptions.fallback(to, from, router);
150
+ },
151
+ replaceWindow(to, from, router) {
152
+ return router.parsedOptions.fallback(to, from, router);
153
+ },
154
+ async pushLayer(to, from, router) {
155
+ const { promise } = await router.createLayer(to);
156
+ return promise;
157
+ },
158
+ default(to, from, router) {
159
+ router.transition.route = to;
160
+ router.microApp._update(router);
161
+ if (!isUrlEqual(to.url, from == null ? void 0 : from.url)) {
162
+ const newState = router.navigation.replace(to.state, to.url);
163
+ to.applyNavigationState(newState);
164
+ }
165
+ }
166
+ };
167
+ const ROUTE_TRANSITION_PIPELINE = {
168
+ [RouteType.push]: [
169
+ ROUTE_TRANSITION_HOOKS.fallback,
170
+ ROUTE_TRANSITION_HOOKS.override,
171
+ ROUTE_TRANSITION_HOOKS.beforeLeave,
172
+ ROUTE_TRANSITION_HOOKS.beforeEach,
173
+ ROUTE_TRANSITION_HOOKS.beforeUpdate,
174
+ ROUTE_TRANSITION_HOOKS.beforeEnter,
175
+ ROUTE_TRANSITION_HOOKS.asyncComponent,
176
+ ROUTE_TRANSITION_HOOKS.confirm
177
+ ],
178
+ [RouteType.replace]: [
179
+ ROUTE_TRANSITION_HOOKS.fallback,
180
+ ROUTE_TRANSITION_HOOKS.override,
181
+ ROUTE_TRANSITION_HOOKS.beforeLeave,
182
+ ROUTE_TRANSITION_HOOKS.beforeEach,
183
+ ROUTE_TRANSITION_HOOKS.beforeUpdate,
184
+ ROUTE_TRANSITION_HOOKS.beforeEnter,
185
+ ROUTE_TRANSITION_HOOKS.asyncComponent,
186
+ ROUTE_TRANSITION_HOOKS.confirm
187
+ ],
188
+ [RouteType.pushWindow]: [
189
+ ROUTE_TRANSITION_HOOKS.fallback,
190
+ ROUTE_TRANSITION_HOOKS.override,
191
+ // ROUTE_TRANSITION_HOOKS.beforeLeave
192
+ ROUTE_TRANSITION_HOOKS.beforeEach,
193
+ // ROUTE_TRANSITION_HOOKS.beforeUpdate
194
+ // ROUTE_TRANSITION_HOOKS.beforeEnter
195
+ // ROUTE_TRANSITION_HOOKS.asyncComponent
196
+ ROUTE_TRANSITION_HOOKS.confirm
197
+ ],
198
+ [RouteType.replaceWindow]: [
199
+ ROUTE_TRANSITION_HOOKS.fallback,
200
+ ROUTE_TRANSITION_HOOKS.override,
201
+ ROUTE_TRANSITION_HOOKS.beforeLeave,
202
+ ROUTE_TRANSITION_HOOKS.beforeEach,
203
+ // ROUTE_TRANSITION_HOOKS.beforeUpdate
204
+ // ROUTE_TRANSITION_HOOKS.beforeEnter
205
+ // ROUTE_TRANSITION_HOOKS.asyncComponent
206
+ ROUTE_TRANSITION_HOOKS.confirm
207
+ ],
208
+ [RouteType.pushLayer]: [
209
+ ROUTE_TRANSITION_HOOKS.fallback,
210
+ ROUTE_TRANSITION_HOOKS.override,
211
+ // ROUTE_TRANSITION_HOOKS.beforeLeave
212
+ ROUTE_TRANSITION_HOOKS.beforeEach,
213
+ // ROUTE_TRANSITION_HOOKS.beforeUpdate
214
+ // ROUTE_TRANSITION_HOOKS.beforeEnter
215
+ // ROUTE_TRANSITION_HOOKS.asyncComponent
216
+ ROUTE_TRANSITION_HOOKS.confirm
217
+ ],
218
+ [RouteType.restartApp]: [
219
+ ROUTE_TRANSITION_HOOKS.fallback,
220
+ // ROUTE_TRANSITION_HOOKS.override,
221
+ ROUTE_TRANSITION_HOOKS.beforeLeave,
222
+ ROUTE_TRANSITION_HOOKS.beforeEach,
223
+ ROUTE_TRANSITION_HOOKS.beforeUpdate,
224
+ ROUTE_TRANSITION_HOOKS.beforeEnter,
225
+ ROUTE_TRANSITION_HOOKS.asyncComponent,
226
+ ROUTE_TRANSITION_HOOKS.confirm
227
+ ],
228
+ [RouteType.unknown]: [
229
+ ROUTE_TRANSITION_HOOKS.fallback,
230
+ // ROUTE_TRANSITION_HOOKS.override,
231
+ ROUTE_TRANSITION_HOOKS.beforeLeave,
232
+ ROUTE_TRANSITION_HOOKS.beforeEach,
233
+ ROUTE_TRANSITION_HOOKS.beforeUpdate,
234
+ ROUTE_TRANSITION_HOOKS.beforeEnter,
235
+ ROUTE_TRANSITION_HOOKS.asyncComponent,
236
+ ROUTE_TRANSITION_HOOKS.confirm
237
+ ]
238
+ };
239
+ export class RouteTransition {
240
+ router;
241
+ route = null;
242
+ // Task controller for the current transition.
243
+ _controller = null;
244
+ // Guard arrays, responsible for storing navigation guards.
245
+ guards = {
246
+ beforeEach: [],
247
+ afterEach: []
248
+ };
249
+ constructor(router) {
250
+ this.router = router;
251
+ }
252
+ beforeEach(guard) {
253
+ this.guards.beforeEach.push(guard);
254
+ return () => {
255
+ removeFromArray(this.guards.beforeEach, guard);
256
+ };
257
+ }
258
+ afterEach(guard) {
259
+ this.guards.afterEach.push(guard);
260
+ return () => {
261
+ removeFromArray(this.guards.afterEach, guard);
262
+ };
263
+ }
264
+ destroy() {
265
+ var _a;
266
+ (_a = this._controller) == null ? void 0 : _a.abort();
267
+ this._controller = null;
268
+ }
269
+ async to(toType, toInput) {
270
+ const from = this.route;
271
+ const to = await this._runTask(
272
+ new Route({
273
+ options: this.router.parsedOptions,
274
+ toType,
275
+ toInput,
276
+ from: (from == null ? void 0 : from.url) ?? null
277
+ }),
278
+ from
279
+ );
280
+ if (typeof to.handle === "function") {
281
+ to.handleResult = await to.handle(to, from, this.router);
282
+ }
283
+ if (to.handle) {
284
+ for (const guard of this.guards.afterEach) {
285
+ guard(to, from, this.router);
286
+ }
287
+ }
288
+ return to;
289
+ }
290
+ async _runTask(to, from) {
291
+ var _a;
292
+ (_a = this._controller) == null ? void 0 : _a.abort();
293
+ this._controller = new RouteTaskController();
294
+ const taskFunctions = ROUTE_TRANSITION_PIPELINE[to.type] || ROUTE_TRANSITION_PIPELINE[RouteType.unknown];
295
+ const tasks = taskFunctions.map((taskFn) => ({
296
+ name: taskFn.name,
297
+ task: taskFn
298
+ }));
299
+ return createRouteTask({
300
+ to,
301
+ from,
302
+ tasks,
303
+ controller: this._controller,
304
+ router: this.router
305
+ });
306
+ }
307
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,146 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
2
+ import { Router } from "./router.mjs";
3
+ import { RouteType, RouterMode } from "./types.mjs";
4
+ describe("Route Transition Tests", () => {
5
+ let router;
6
+ beforeEach(async () => {
7
+ router = new Router({
8
+ mode: RouterMode.memory,
9
+ base: new URL("http://localhost:3000/"),
10
+ routes: [
11
+ { path: "/", component: () => "Home" },
12
+ { path: "/user/:id", component: () => "User" },
13
+ { path: "/about", component: () => "About" },
14
+ {
15
+ path: "/async",
16
+ asyncComponent: () => Promise.resolve("Async")
17
+ }
18
+ ]
19
+ });
20
+ await router.replace("/");
21
+ });
22
+ afterEach(() => {
23
+ router.destroy();
24
+ });
25
+ describe("Basic transitions", () => {
26
+ test("should successfully transition to new route", async () => {
27
+ const route = await router.push("/user/123");
28
+ expect(route.path).toBe("/user/123");
29
+ expect(route.params.id).toBe("123");
30
+ expect(route.handle).not.toBe(null);
31
+ });
32
+ test("should handle async component loading", async () => {
33
+ const route = await router.push("/async");
34
+ expect(route.path).toBe("/async");
35
+ expect(route.handle).not.toBe(null);
36
+ });
37
+ });
38
+ describe("Error handling", () => {
39
+ test("should handle non-existent routes", async () => {
40
+ const route = await router.push("/non-existent");
41
+ expect(route.matched).toHaveLength(0);
42
+ expect(route.config).toBe(null);
43
+ });
44
+ test("should handle component loading errors", async () => {
45
+ const errorRouter = new Router({
46
+ mode: RouterMode.memory,
47
+ base: new URL("http://localhost:3000/"),
48
+ routes: [
49
+ { path: "/", component: () => "Home" },
50
+ {
51
+ path: "/error",
52
+ asyncComponent: () => Promise.reject(new Error("Loading failed"))
53
+ }
54
+ ]
55
+ });
56
+ await errorRouter.replace("/");
57
+ await expect(errorRouter.push("/error")).rejects.toThrow();
58
+ errorRouter.destroy();
59
+ });
60
+ });
61
+ describe("Concurrent navigation", () => {
62
+ test("should handle concurrent navigation attempts", async () => {
63
+ const promises = Array.from(
64
+ { length: 5 },
65
+ (_, i) => router.push(`/user/${i + 1}`).catch((err) => err)
66
+ );
67
+ const results = await Promise.all(promises);
68
+ const successResults = results.filter((r) => !(r instanceof Error));
69
+ const abortedResults = results.filter((r) => r instanceof Error);
70
+ expect(successResults).toHaveLength(1);
71
+ expect(abortedResults).toHaveLength(4);
72
+ const successResult = successResults[0];
73
+ expect(router.route.path).toBe(successResult.path);
74
+ expect(router.route.params.id).toBe(successResult.params.id);
75
+ });
76
+ });
77
+ describe("Route guards", () => {
78
+ let guardRouter;
79
+ let guardLog;
80
+ beforeEach(async () => {
81
+ guardLog = [];
82
+ guardRouter = new Router({
83
+ mode: RouterMode.memory,
84
+ base: new URL("http://localhost:3000/"),
85
+ routes: [
86
+ { path: "/", component: () => "Home" },
87
+ {
88
+ path: "/protected",
89
+ component: () => "Protected",
90
+ beforeEnter: () => {
91
+ guardLog.push("beforeEnter-protected");
92
+ return false;
93
+ }
94
+ },
95
+ {
96
+ path: "/allowed",
97
+ component: () => "Allowed",
98
+ beforeEnter: () => {
99
+ guardLog.push("beforeEnter-allowed");
100
+ }
101
+ }
102
+ ]
103
+ });
104
+ await guardRouter.replace("/");
105
+ });
106
+ afterEach(() => {
107
+ guardRouter.destroy();
108
+ });
109
+ test("should block navigation when guard returns false", async () => {
110
+ await expect(guardRouter.push("/protected")).rejects.toThrow();
111
+ expect(guardLog).toContain("beforeEnter-protected");
112
+ expect(guardRouter.route.path).toBe("/");
113
+ });
114
+ test("should allow navigation when guard returns true", async () => {
115
+ const route = await guardRouter.push("/allowed");
116
+ expect(guardLog).toContain("beforeEnter-allowed");
117
+ expect(route.path).toBe("/allowed");
118
+ expect(guardRouter.route.path).toBe("/allowed");
119
+ });
120
+ });
121
+ describe("Navigation types", () => {
122
+ test("should correctly set route type for push navigation", async () => {
123
+ const route = await router.push("/user/123");
124
+ expect(route.type).toBe(RouteType.push);
125
+ });
126
+ test("should correctly set route type for replace navigation", async () => {
127
+ const route = await router.replace("/user/123");
128
+ expect(route.type).toBe(RouteType.replace);
129
+ });
130
+ });
131
+ describe("Route parameters and query", () => {
132
+ test("should correctly extract route parameters", async () => {
133
+ const route = await router.push("/user/456");
134
+ expect(route.params.id).toBe("456");
135
+ expect(route.path).toBe("/user/456");
136
+ });
137
+ test("should handle query parameters", async () => {
138
+ const route = await router.push(
139
+ "/user/123?tab=profile&active=true"
140
+ );
141
+ expect(route.params.id).toBe("123");
142
+ expect(route.query.tab).toBe("profile");
143
+ expect(route.query.active).toBe("true");
144
+ });
145
+ });
146
+ });
@@ -0,0 +1,72 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { type RouteConfirmHook, type RouteHandleHook, type RouteHandleResult, type RouteLayerOptions, type RouteLocationInput, type RouteMatchResult, type RouteMeta, type RouteOptions, type RouteParsedConfig, type RouteState, RouteType } from './types';
3
+ /**
4
+ * Configuration for non-enumerable properties in Route class
5
+ * These properties will be hidden during object traversal and serialization
6
+ */
7
+ export declare const NON_ENUMERABLE_PROPERTIES: string[];
8
+ /**
9
+ * Append user-provided parameters to URL path
10
+ * @param match Route matching result
11
+ * @param toInput User-provided route location object
12
+ * @param base Base URL
13
+ * @param to Current parsed URL object
14
+ */
15
+ export declare function applyRouteParams(match: RouteMatchResult, toInput: RouteLocationInput, base: URL, to: URL): void;
16
+ /**
17
+ * Route class provides complete route object functionality
18
+ */
19
+ export declare class Route {
20
+ private _handled;
21
+ private _handle;
22
+ private _handleResult;
23
+ private readonly _options;
24
+ readonly statusCode: number | null;
25
+ readonly state: RouteState;
26
+ readonly keepScrollPosition: boolean;
27
+ /** Custom confirm handler that overrides default route-transition confirm logic */
28
+ readonly confirm: RouteConfirmHook | null;
29
+ /** Layer configuration for layer routes */
30
+ readonly layer: RouteLayerOptions | null;
31
+ readonly type: RouteType;
32
+ readonly req: IncomingMessage | null;
33
+ readonly res: ServerResponse | null;
34
+ readonly context: Record<string | symbol, any>;
35
+ readonly url: URL;
36
+ readonly path: string;
37
+ readonly fullPath: string;
38
+ readonly hash: string;
39
+ readonly params: Record<string, string>;
40
+ readonly query: Record<string, string | undefined>;
41
+ readonly queryArray: Record<string, string[] | undefined>;
42
+ readonly meta: RouteMeta;
43
+ readonly matched: readonly RouteParsedConfig[];
44
+ readonly config: RouteParsedConfig | null;
45
+ constructor(routeOptions?: Partial<RouteOptions>);
46
+ get isPush(): boolean;
47
+ get handle(): RouteHandleHook | null;
48
+ set handle(val: RouteHandleHook | null);
49
+ get handleResult(): RouteHandleResult | null;
50
+ set handleResult(val: RouteHandleResult | null);
51
+ /**
52
+ * Set handle function with validation logic wrapper
53
+ */
54
+ setHandle(val: RouteHandleHook | null): void;
55
+ /**
56
+ * Apply navigation-generated state to current route
57
+ * Used by route handlers to add system state like pageId
58
+ * @param navigationState Navigation-generated state to apply
59
+ */
60
+ applyNavigationState(navigationState: Partial<RouteState>): void;
61
+ /**
62
+ * Sync all properties of current route to target route object
63
+ * Used for route object updates in reactive systems
64
+ * @param targetRoute Target route object
65
+ */
66
+ syncTo(targetRoute: Route): void;
67
+ /**
68
+ * Clone current route instance
69
+ * Returns a new Route instance with same configuration and state
70
+ */
71
+ clone(): Route;
72
+ }