@planet-matrix/mobius-model 0.6.0 → 0.9.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 (233) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/index.js +706 -36
  3. package/dist/index.js.map +855 -59
  4. package/package.json +28 -16
  5. package/src/ai/README.md +1 -0
  6. package/src/ai/ai.ts +107 -0
  7. package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
  8. package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
  9. package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
  10. package/src/ai/chat-completion-ai/index.ts +7 -0
  11. package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
  12. package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
  13. package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
  14. package/src/ai/embedding-ai/embedding-ai.ts +63 -0
  15. package/src/ai/embedding-ai/embedding.ts +50 -0
  16. package/src/ai/embedding-ai/index.ts +4 -0
  17. package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
  18. package/src/ai/index.ts +4 -0
  19. package/src/aio/README.md +100 -0
  20. package/src/aio/content.ts +141 -0
  21. package/src/aio/index.ts +3 -0
  22. package/src/aio/json.ts +127 -0
  23. package/src/aio/prompt.ts +246 -0
  24. package/src/basic/README.md +20 -15
  25. package/src/basic/error.ts +19 -5
  26. package/src/basic/function.ts +2 -2
  27. package/src/basic/index.ts +1 -0
  28. package/src/basic/schedule.ts +111 -0
  29. package/src/basic/stream.ts +135 -25
  30. package/src/credential/README.md +107 -0
  31. package/src/credential/api-key.ts +158 -0
  32. package/src/credential/bearer.ts +73 -0
  33. package/src/credential/index.ts +4 -0
  34. package/src/credential/json-web-token.ts +96 -0
  35. package/src/credential/password.ts +170 -0
  36. package/src/cron/README.md +86 -0
  37. package/src/cron/cron.ts +87 -0
  38. package/src/cron/index.ts +1 -0
  39. package/src/drizzle/README.md +1 -0
  40. package/src/drizzle/drizzle.ts +1 -0
  41. package/src/drizzle/helper.ts +47 -0
  42. package/src/drizzle/index.ts +5 -0
  43. package/src/drizzle/infer.ts +52 -0
  44. package/src/drizzle/kysely.ts +8 -0
  45. package/src/drizzle/pagination.ts +200 -0
  46. package/src/email/README.md +1 -0
  47. package/src/email/index.ts +1 -0
  48. package/src/email/resend.ts +25 -0
  49. package/src/event/class-event-proxy.ts +6 -5
  50. package/src/event/common.ts +13 -3
  51. package/src/event/event-manager.ts +3 -3
  52. package/src/event/instance-event-proxy.ts +6 -5
  53. package/src/event/internal.ts +4 -4
  54. package/src/form/README.md +25 -0
  55. package/src/form/index.ts +1 -0
  56. package/src/form/inputor-controller/base.ts +874 -0
  57. package/src/form/inputor-controller/boolean.ts +39 -0
  58. package/src/form/inputor-controller/file.ts +39 -0
  59. package/src/form/inputor-controller/form.ts +181 -0
  60. package/src/form/inputor-controller/helper.ts +117 -0
  61. package/src/form/inputor-controller/index.ts +17 -0
  62. package/src/form/inputor-controller/multi-select.ts +99 -0
  63. package/src/form/inputor-controller/number.ts +116 -0
  64. package/src/form/inputor-controller/select.ts +109 -0
  65. package/src/form/inputor-controller/text.ts +82 -0
  66. package/src/http/READMD.md +1 -0
  67. package/src/http/api/api-core.ts +84 -0
  68. package/src/http/api/api-handler.ts +79 -0
  69. package/src/http/api/api-host.ts +47 -0
  70. package/src/http/api/api-result.ts +56 -0
  71. package/src/http/api/api-schema.ts +154 -0
  72. package/src/http/api/api-server.ts +130 -0
  73. package/src/http/api/api-test.ts +142 -0
  74. package/src/http/api/api-type.ts +37 -0
  75. package/src/http/api/api.ts +81 -0
  76. package/src/http/api/index.ts +11 -0
  77. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  78. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  79. package/src/http/api-adapter/api-result-arktype.ts +297 -0
  80. package/src/http/api-adapter/api-result-zod.ts +286 -0
  81. package/src/http/api-adapter/index.ts +5 -0
  82. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  83. package/src/http/bin/gen-api-list/index.ts +1 -0
  84. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  85. package/src/http/bin/gen-api-test/index.ts +1 -0
  86. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  87. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  88. package/src/http/bin/gen-api-type/index.ts +2 -0
  89. package/src/http/bin/index.ts +2 -0
  90. package/src/http/index.ts +3 -0
  91. package/src/huawei/README.md +1 -0
  92. package/src/huawei/index.ts +2 -0
  93. package/src/huawei/moderation/index.ts +1 -0
  94. package/src/huawei/moderation/moderation.ts +355 -0
  95. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  96. package/src/huawei/obs/index.ts +1 -0
  97. package/src/huawei/obs/obs.ts +42 -0
  98. package/src/index.ts +19 -2
  99. package/src/json/README.md +92 -0
  100. package/src/json/index.ts +1 -0
  101. package/src/json/repair.ts +18 -0
  102. package/src/log/logger.ts +15 -4
  103. package/src/openai/README.md +1 -0
  104. package/src/openai/index.ts +1 -0
  105. package/src/openai/openai.ts +510 -0
  106. package/src/orchestration/README.md +9 -7
  107. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  108. package/src/orchestration/dispatching/index.ts +2 -0
  109. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  110. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  111. package/src/orchestration/dispatching/selector/index.ts +2 -0
  112. package/src/orchestration/index.ts +2 -0
  113. package/src/orchestration/scheduling/index.ts +2 -0
  114. package/src/orchestration/scheduling/scheduler.ts +103 -0
  115. package/src/orchestration/scheduling/task.ts +32 -0
  116. package/src/random/README.md +8 -7
  117. package/src/random/base.ts +66 -0
  118. package/src/random/index.ts +5 -1
  119. package/src/random/random-boolean.ts +40 -0
  120. package/src/random/random-integer.ts +60 -0
  121. package/src/random/random-number.ts +72 -0
  122. package/src/random/random-string.ts +66 -0
  123. package/src/request/README.md +108 -0
  124. package/src/request/fetch/base.ts +108 -0
  125. package/src/request/fetch/browser.ts +285 -0
  126. package/src/request/fetch/general.ts +20 -0
  127. package/src/request/fetch/index.ts +4 -0
  128. package/src/request/fetch/nodejs.ts +285 -0
  129. package/src/request/index.ts +2 -0
  130. package/src/request/request/base.ts +250 -0
  131. package/src/request/request/general.ts +64 -0
  132. package/src/request/request/index.ts +3 -0
  133. package/src/request/request/resource.ts +68 -0
  134. package/src/result/README.md +4 -0
  135. package/src/result/controller.ts +54 -0
  136. package/src/result/either.ts +193 -0
  137. package/src/result/index.ts +2 -0
  138. package/src/route/README.md +105 -0
  139. package/src/route/adapter/browser.ts +122 -0
  140. package/src/route/adapter/driver.ts +56 -0
  141. package/src/route/adapter/index.ts +2 -0
  142. package/src/route/index.ts +3 -0
  143. package/src/route/router/index.ts +2 -0
  144. package/src/route/router/route.ts +630 -0
  145. package/src/route/router/router.ts +1642 -0
  146. package/src/route/uri/hash.ts +308 -0
  147. package/src/route/uri/index.ts +7 -0
  148. package/src/route/uri/pathname.ts +376 -0
  149. package/src/route/uri/search.ts +413 -0
  150. package/src/socket/README.md +105 -0
  151. package/src/socket/client/index.ts +2 -0
  152. package/src/socket/client/socket-unit.ts +660 -0
  153. package/src/socket/client/socket.ts +203 -0
  154. package/src/socket/common/index.ts +2 -0
  155. package/src/socket/common/socket-unit-common.ts +23 -0
  156. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  157. package/src/socket/index.ts +3 -0
  158. package/src/socket/server/index.ts +3 -0
  159. package/src/socket/server/server.ts +183 -0
  160. package/src/socket/server/socket-unit.ts +449 -0
  161. package/src/socket/server/socket.ts +264 -0
  162. package/src/storage/table.ts +3 -3
  163. package/src/timer/expiration/expiration-manager.ts +3 -3
  164. package/src/timer/expiration/remaining-manager.ts +3 -3
  165. package/src/tube/README.md +99 -0
  166. package/src/tube/helper.ts +138 -0
  167. package/src/tube/index.ts +2 -0
  168. package/src/tube/tube.ts +880 -0
  169. package/src/weixin/README.md +1 -0
  170. package/src/weixin/index.ts +2 -0
  171. package/src/weixin/official-account/authorization.ts +159 -0
  172. package/src/weixin/official-account/index.ts +2 -0
  173. package/src/weixin/official-account/js-api.ts +134 -0
  174. package/src/weixin/open/index.ts +1 -0
  175. package/src/weixin/open/oauth2.ts +133 -0
  176. package/tests/unit/ai/ai.spec.ts +85 -0
  177. package/tests/unit/aio/content.spec.ts +105 -0
  178. package/tests/unit/aio/json.spec.ts +147 -0
  179. package/tests/unit/aio/prompt.spec.ts +111 -0
  180. package/tests/unit/basic/error.spec.ts +16 -4
  181. package/tests/unit/basic/schedule.spec.ts +74 -0
  182. package/tests/unit/basic/stream.spec.ts +90 -37
  183. package/tests/unit/credential/api-key.spec.ts +37 -0
  184. package/tests/unit/credential/bearer.spec.ts +23 -0
  185. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  186. package/tests/unit/credential/password.spec.ts +41 -0
  187. package/tests/unit/cron/cron.spec.ts +84 -0
  188. package/tests/unit/event/class-event-proxy.spec.ts +3 -3
  189. package/tests/unit/event/event-manager.spec.ts +3 -3
  190. package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
  191. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  192. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  193. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  194. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  195. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  196. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  197. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  198. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  199. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  200. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  201. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  202. package/tests/unit/http/api/api-server.spec.ts +363 -0
  203. package/tests/unit/http/api/api-test.spec.ts +117 -0
  204. package/tests/unit/http/api/api.spec.ts +121 -0
  205. package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
  206. package/tests/unit/json/repair.spec.ts +11 -0
  207. package/tests/unit/log/logger.spec.ts +19 -4
  208. package/tests/unit/openai/openai.spec.ts +64 -0
  209. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  210. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  211. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  212. package/tests/unit/random/base.spec.ts +58 -0
  213. package/tests/unit/random/random-boolean.spec.ts +25 -0
  214. package/tests/unit/random/random-integer.spec.ts +32 -0
  215. package/tests/unit/random/random-number.spec.ts +33 -0
  216. package/tests/unit/random/random-string.spec.ts +22 -0
  217. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  218. package/tests/unit/request/fetch/general.spec.ts +43 -0
  219. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  220. package/tests/unit/request/request/base.spec.ts +385 -0
  221. package/tests/unit/request/request/general.spec.ts +161 -0
  222. package/tests/unit/route/router/route.spec.ts +431 -0
  223. package/tests/unit/route/router/router.spec.ts +407 -0
  224. package/tests/unit/route/uri/hash.spec.ts +72 -0
  225. package/tests/unit/route/uri/pathname.spec.ts +147 -0
  226. package/tests/unit/route/uri/search.spec.ts +107 -0
  227. package/tests/unit/socket/client.spec.ts +208 -0
  228. package/tests/unit/socket/server.spec.ts +135 -0
  229. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  230. package/tests/unit/tube/helper.spec.ts +139 -0
  231. package/tests/unit/tube/tube.spec.ts +501 -0
  232. package/src/random/string.ts +0 -35
  233. package/tests/unit/random/string.spec.ts +0 -11
@@ -0,0 +1,1642 @@
1
+
2
+ import type { BuildEvents } from "#Source/event/index.ts"
3
+ import { EventManager } from "#Source/event/index.ts"
4
+ import { generateUuidV4FromUrl } from "#Source/identifier/index.ts"
5
+
6
+ import type { HashUnion, SearchUnion } from "../uri/index.ts"
7
+ import { Route } from "./route.ts"
8
+
9
+ /**
10
+ * Router 内部历史动作的种类。
11
+ */
12
+ export const ROUTE_ACTION_TYPE = {
13
+ MOVE: "move",
14
+ TRIM: "trim",
15
+ PUSH: "push",
16
+ REPLACE: "replace"
17
+ } as const
18
+
19
+ /**
20
+ * Router 内部历史动作的联合字面量类型。
21
+ */
22
+ export type RouteActionType = (typeof ROUTE_ACTION_TYPE)[keyof typeof ROUTE_ACTION_TYPE]
23
+
24
+ /**
25
+ * 在历史链表上移动当前位置时使用的输入项。
26
+ */
27
+ export interface RouteMoveOptions {
28
+ step: number
29
+ }
30
+
31
+ /**
32
+ * 在历史链表上移动当前位置后返回的结果。
33
+ */
34
+ export interface RouteMoveResult {
35
+ fromRouteEntry: RouteEntry
36
+ toRouteEntry: RouteEntry
37
+ direction: "forward" | "backward" | "none"
38
+ }
39
+
40
+ /**
41
+ * 截断某个历史节点之后分支时使用的输入项。
42
+ */
43
+ export interface RouteTrimOptions {
44
+ routeEntry: RouteEntry
45
+ }
46
+
47
+ /**
48
+ * 截断历史分支后返回的结果。
49
+ */
50
+ export interface RouteTrimResult {
51
+ fromRouteEntry: RouteEntry
52
+ trimmedRouteEntries: RouteEntry[]
53
+ }
54
+
55
+ /**
56
+ * 向历史尾部压入新路由时使用的输入项。
57
+ */
58
+ export interface RoutePushOptions {
59
+ route: Route
60
+ }
61
+
62
+ /**
63
+ * 向历史尾部压入新路由后返回的结果。
64
+ */
65
+ export interface RoutePushResult {
66
+ fromRouteEntry: RouteEntry
67
+ toRouteEntry: RouteEntry
68
+ }
69
+
70
+ /**
71
+ * 用新路由替换当前位置时使用的输入项。
72
+ */
73
+ export interface RouteReplaceOptions {
74
+ route: Route
75
+ }
76
+
77
+ /**
78
+ * 用新路由替换当前位置后返回的结果。
79
+ */
80
+ export interface RouteReplaceResult {
81
+ fromRouteEntry: RouteEntry
82
+ toRouteEntry: RouteEntry
83
+ }
84
+
85
+ /**
86
+ * 对外可见的路由历史操作类型。
87
+ */
88
+ export const ROUTE_TYPE = {
89
+ INITIALIZE: "initialize",
90
+ REFRESH: "refresh",
91
+ REPLACE_TO: "replace-to",
92
+ SWITCH_TO: "switch-to",
93
+ NAVIGATE_TO: "navigate-to",
94
+ NAVIGATE_SEARCH: "navigate-search",
95
+ NAVIGATE_HASH: "navigate-hash",
96
+ REDIRECT_TO: "redirect-to",
97
+ REDIRECT_SEARCH: "redirect-search",
98
+ REDIRECT_HASH: "redirect-hash",
99
+ ROAMING_BY: "roaming-by",
100
+ ROAMING_FORWARD_BY: "roaming-forward-by",
101
+ ROAMING_BACKWARD_BY: "roaming-backward-by",
102
+ ROAMING_TO: "roaming-to",
103
+ ROAMING_FORWARD_TO: "roaming-forward-to",
104
+ ROAMING_BACKWARD_TO: "roaming-backward-to",
105
+ GO_BY: "go-by",
106
+ GO_FORWARD_BY: "go-forward-by",
107
+ GO_BACKWARD_BY: "go-backward-by",
108
+ GO_TO: "go-to",
109
+ GO_FORWARD_TO: "go-forward-to",
110
+ GO_BACKWARD_TO: "go-backward-to",
111
+ } as const
112
+
113
+ /**
114
+ * 对外可见的路由历史操作类型联合。
115
+ */
116
+ export type RouteType = (typeof ROUTE_TYPE)[keyof typeof ROUTE_TYPE]
117
+
118
+ /**
119
+ * 初始化 Router 时使用的输入项。
120
+ */
121
+ export interface RouteInitializeOptions {
122
+ url: string
123
+ }
124
+
125
+ /**
126
+ * 刷新当前路由时使用的输入项。
127
+ */
128
+ export interface RouteRefreshOptions {
129
+ }
130
+
131
+ /**
132
+ * 用新地址替换当前位置时使用的输入项。
133
+ */
134
+ export interface RouteReplaceToOptions {
135
+ url: string
136
+ }
137
+
138
+ /**
139
+ * 切换到历史尾部并替换目标地址时使用的输入项。
140
+ */
141
+ export interface RouteSwitchToOptions {
142
+ url: string
143
+ }
144
+
145
+ /**
146
+ * 以追加历史的方式导航到新地址时使用的输入项。
147
+ */
148
+ export interface RouteNavigateToOptions {
149
+ url: string
150
+ }
151
+
152
+ /**
153
+ * 以追加历史的方式导航到新 search 时使用的输入项。
154
+ */
155
+ export interface RouteNavigateSearchOptions {
156
+ search: SearchUnion
157
+ }
158
+
159
+ /**
160
+ * 以追加历史的方式导航到新 hash 时使用的输入项。
161
+ */
162
+ export interface RouteNavigateHashOptions {
163
+ hash: HashUnion
164
+ }
165
+
166
+ /**
167
+ * 以替换当前位置的方式重定向到新地址时使用的输入项。
168
+ */
169
+ export interface RouteRedirectToOptions {
170
+ url: string
171
+ }
172
+
173
+ /**
174
+ * 以替换当前位置的方式重定向到新 search 时使用的输入项。
175
+ */
176
+ export interface RouteRedirectSearchOptions {
177
+ search: SearchUnion
178
+ }
179
+
180
+ /**
181
+ * 以替换当前位置的方式重定向到新 hash 时使用的输入项。
182
+ */
183
+ export interface RouteRedirectHashOptions {
184
+ hash: HashUnion
185
+ }
186
+
187
+ /**
188
+ * 在历史链表中按相对步数漫游时使用的输入项。
189
+ */
190
+ export interface RouteRoamingByOptions {
191
+ step: number
192
+ }
193
+
194
+ /**
195
+ * 在历史链表中向前漫游时使用的输入项。
196
+ */
197
+ export interface RouteRoamingForwardByOptions {
198
+ step: number
199
+ }
200
+
201
+ /**
202
+ * 在历史链表中向后漫游时使用的输入项。
203
+ */
204
+ export interface RouteRoamingBackwardByOptions {
205
+ step: number
206
+ }
207
+
208
+ /**
209
+ * 在全部历史范围内按条件漫游时使用的输入项。
210
+ */
211
+ export interface RouteRoamingToOptions {
212
+ predicate: (routeEntry: RouteEntry) => boolean
213
+ }
214
+
215
+ /**
216
+ * 在当前位置之后按条件漫游时使用的输入项。
217
+ */
218
+ export interface RouteRoamingForwardToOptions {
219
+ predicate: (routeEntry: RouteEntry) => boolean
220
+ }
221
+
222
+ /**
223
+ * 在当前位置之前按条件漫游时使用的输入项。
224
+ */
225
+ export interface RouteRoamingBackwardToOptions {
226
+ predicate: (routeEntry: RouteEntry) => boolean
227
+ }
228
+
229
+ /**
230
+ * 在历史链表中按相对步数移动并截断后续分支时使用的输入项。
231
+ */
232
+ export interface RouteGoByOptions {
233
+ step: number
234
+ }
235
+
236
+ /**
237
+ * 在历史链表中向前移动并截断后续分支时使用的输入项。
238
+ */
239
+ export interface RouteGoForwardByOptions {
240
+ step: number
241
+ }
242
+
243
+ /**
244
+ * 在历史链表中向后移动并截断后续分支时使用的输入项。
245
+ */
246
+ export interface RouteGoBackwardByOptions {
247
+ step: number
248
+ }
249
+
250
+ /**
251
+ * 在全部历史范围内按条件移动并截断后续分支时使用的输入项。
252
+ */
253
+ export interface RouteGoToOptions {
254
+ predicate: (routeEntry: RouteEntry) => boolean
255
+ }
256
+
257
+ /**
258
+ * 在当前位置之后按条件移动并截断后续分支时使用的输入项。
259
+ */
260
+ export interface RouteGoForwardToOptions {
261
+ predicate: (routeEntry: RouteEntry) => boolean
262
+ }
263
+
264
+ /**
265
+ * 在当前位置之前按条件移动并截断后续分支时使用的输入项。
266
+ */
267
+ export interface RouteGoBackwardToOptions {
268
+ predicate: (routeEntry: RouteEntry) => boolean
269
+ }
270
+
271
+ /**
272
+ * Router 在一次历史记录中执行的底层动作。
273
+ */
274
+ export type RouteHistoryAction =
275
+ | { type: "move", options: RouteMoveOptions, result: RouteMoveResult }
276
+ | { type: "trim", options: RouteTrimOptions, result: RouteTrimResult }
277
+ | { type: "push", options: RoutePushOptions, result: RoutePushResult }
278
+ | { type: "replace", options: RouteReplaceOptions, result: RouteReplaceResult }
279
+
280
+ /**
281
+ * Router 对外暴露的历史记录条目。
282
+ */
283
+ export type RouteHistoryEntry =
284
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "initialize", options: RouteInitializeOptions, actions: RouteHistoryAction[] }
285
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "refresh", options: RouteRefreshOptions, actions: RouteHistoryAction[] }
286
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "replace-to", options: RouteReplaceToOptions, actions: RouteHistoryAction[] }
287
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "switch-to", options: RouteSwitchToOptions, actions: RouteHistoryAction[] }
288
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "navigate-to", options: RouteNavigateToOptions, actions: RouteHistoryAction[] }
289
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "navigate-search", options: RouteNavigateSearchOptions, actions: RouteHistoryAction[] }
290
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "navigate-hash", options: RouteNavigateHashOptions, actions: RouteHistoryAction[] }
291
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "redirect-to", options: RouteRedirectToOptions, actions: RouteHistoryAction[] }
292
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "redirect-search", options: RouteRedirectSearchOptions, actions: RouteHistoryAction[] }
293
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "redirect-hash", options: RouteRedirectHashOptions, actions: RouteHistoryAction[] }
294
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "roaming-by", options: RouteRoamingByOptions, actions: RouteHistoryAction[] }
295
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "roaming-forward-by", options: RouteRoamingForwardByOptions, actions: RouteHistoryAction[] }
296
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "roaming-backward-by", options: RouteRoamingBackwardByOptions, actions: RouteHistoryAction[] }
297
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "roaming-to", options: RouteRoamingToOptions, actions: RouteHistoryAction[] }
298
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "roaming-forward-to", options: RouteRoamingForwardToOptions, actions: RouteHistoryAction[] }
299
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "roaming-backward-to", options: RouteRoamingBackwardToOptions, actions: RouteHistoryAction[] }
300
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "go-by", options: RouteGoByOptions, actions: RouteHistoryAction[] }
301
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "go-forward-by", options: RouteGoForwardByOptions, actions: RouteHistoryAction[] }
302
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "go-backward-by", options: RouteGoBackwardByOptions, actions: RouteHistoryAction[] }
303
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "go-to", options: RouteGoToOptions, actions: RouteHistoryAction[] }
304
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "go-forward-to", options: RouteGoForwardToOptions, actions: RouteHistoryAction[] }
305
+ | { id: string, from: RouteEntry, to: RouteEntry, type: "go-backward-to", options: RouteGoBackwardToOptions, actions: RouteHistoryAction[] }
306
+
307
+ /**
308
+ * Router 历史链表中的节点。
309
+ */
310
+ export interface RouteEntry {
311
+ route: Route
312
+ prevEntry?: RouteEntry | undefined
313
+ nextEntry?: RouteEntry | undefined
314
+ history: RouteHistoryEntry[]
315
+ index: number
316
+ }
317
+
318
+ /**
319
+ * 1. `history.options` 中涉及 `predicate` 的地方替换为 `(): boolean => false`。
320
+ * 2. 所有涉及 `Route` 的地方使用 `Route.clone()` 方法进行克隆。
321
+ * 3. `RouteEntry.prevEntry` 和 `RouteEntry.nextEntry` 递归克隆。
322
+ * 4. `RouteEntry.history` 中的每个 `RouteHistoryEntry` 都进行克隆。
323
+ * 5. 克隆前后保持引用关系不变。
324
+ */
325
+ const internalRouteMap = new Map<Route, Route>()
326
+ const internalRouteEntryMap = new Map<RouteEntry, RouteEntry>()
327
+ const internalRouteHistoryEntryMap = new Map<RouteHistoryEntry, RouteHistoryEntry>()
328
+ const internalCloneReset = (): void => {
329
+ internalRouteMap.clear()
330
+ internalRouteEntryMap.clear()
331
+ internalRouteHistoryEntryMap.clear()
332
+ }
333
+ const internalCloneRoute = (sourceRoute: Route): Route => {
334
+ const cachedRoute = internalRouteMap.get(sourceRoute)
335
+ if (cachedRoute !== undefined) {
336
+ return cachedRoute
337
+ }
338
+
339
+ const clonedRoute = sourceRoute.clone()
340
+ internalRouteMap.set(sourceRoute, clonedRoute)
341
+ return clonedRoute
342
+ }
343
+ const internalCloneRouteEntry = (sourceRouteEntry: RouteEntry): RouteEntry => {
344
+ const cachedRouteEntry = internalRouteEntryMap.get(sourceRouteEntry)
345
+ if (cachedRouteEntry !== undefined) {
346
+ return cachedRouteEntry
347
+ }
348
+
349
+ const clonedRouteEntry: RouteEntry = {
350
+ route: internalCloneRoute(sourceRouteEntry.route),
351
+ prevEntry: undefined,
352
+ nextEntry: undefined,
353
+ history: [],
354
+ index: sourceRouteEntry.index
355
+ }
356
+ internalRouteEntryMap.set(sourceRouteEntry, clonedRouteEntry)
357
+
358
+ clonedRouteEntry.prevEntry = sourceRouteEntry.prevEntry === undefined
359
+ ? undefined
360
+ : internalCloneRouteEntry(sourceRouteEntry.prevEntry)
361
+ clonedRouteEntry.nextEntry = sourceRouteEntry.nextEntry === undefined
362
+ ? undefined
363
+ : internalCloneRouteEntry(sourceRouteEntry.nextEntry)
364
+ clonedRouteEntry.history = sourceRouteEntry.history.map((historyEntry) => {
365
+ return internalCloneRouteHistoryEntry(historyEntry)
366
+ })
367
+
368
+ return clonedRouteEntry
369
+ }
370
+ const internalCloneRouteHistoryEntry = (historyEntry: RouteHistoryEntry): RouteHistoryEntry => {
371
+ const cloneHistoryOptions = (sourceHistoryEntry: RouteHistoryEntry): RouteHistoryEntry["options"] => {
372
+ switch (sourceHistoryEntry.type) {
373
+ case ROUTE_TYPE.INITIALIZE: {
374
+ return {
375
+ url: sourceHistoryEntry.options.url
376
+ }
377
+ }
378
+ case ROUTE_TYPE.REFRESH: {
379
+ return {}
380
+ }
381
+ case ROUTE_TYPE.SWITCH_TO:
382
+ case ROUTE_TYPE.REPLACE_TO:
383
+ case ROUTE_TYPE.NAVIGATE_TO:
384
+ case ROUTE_TYPE.REDIRECT_TO: {
385
+ return {
386
+ url: sourceHistoryEntry.options.url
387
+ }
388
+ }
389
+ case ROUTE_TYPE.NAVIGATE_SEARCH:
390
+ case ROUTE_TYPE.REDIRECT_SEARCH: {
391
+ return {
392
+ search: structuredClone(sourceHistoryEntry.options.search)
393
+ }
394
+ }
395
+ case ROUTE_TYPE.NAVIGATE_HASH:
396
+ case ROUTE_TYPE.REDIRECT_HASH: {
397
+ return {
398
+ hash: structuredClone(sourceHistoryEntry.options.hash)
399
+ }
400
+ }
401
+ case ROUTE_TYPE.ROAMING_BY:
402
+ case ROUTE_TYPE.ROAMING_FORWARD_BY:
403
+ case ROUTE_TYPE.ROAMING_BACKWARD_BY:
404
+ case ROUTE_TYPE.GO_BY:
405
+ case ROUTE_TYPE.GO_FORWARD_BY:
406
+ case ROUTE_TYPE.GO_BACKWARD_BY: {
407
+ return {
408
+ step: sourceHistoryEntry.options.step
409
+ }
410
+ }
411
+ case ROUTE_TYPE.ROAMING_TO:
412
+ case ROUTE_TYPE.ROAMING_FORWARD_TO:
413
+ case ROUTE_TYPE.ROAMING_BACKWARD_TO:
414
+ case ROUTE_TYPE.GO_TO:
415
+ case ROUTE_TYPE.GO_FORWARD_TO:
416
+ case ROUTE_TYPE.GO_BACKWARD_TO: {
417
+ return {
418
+ predicate: (_routeEntry: RouteEntry): boolean => false
419
+ }
420
+ }
421
+ default: {
422
+ throw new Error(`Unsupported route history entry type`)
423
+ }
424
+ }
425
+ }
426
+
427
+ const cloneRouteHistoryAction = (sourceAction: RouteHistoryAction): RouteHistoryAction => {
428
+ switch (sourceAction.type) {
429
+ case ROUTE_ACTION_TYPE.MOVE: {
430
+ return {
431
+ type: ROUTE_ACTION_TYPE.MOVE,
432
+ options: { step: sourceAction.options.step },
433
+ result: {
434
+ fromRouteEntry: internalCloneRouteEntry(sourceAction.result.fromRouteEntry),
435
+ toRouteEntry: internalCloneRouteEntry(sourceAction.result.toRouteEntry),
436
+ direction: sourceAction.result.direction
437
+ }
438
+ }
439
+ }
440
+ case ROUTE_ACTION_TYPE.TRIM: {
441
+ return {
442
+ type: ROUTE_ACTION_TYPE.TRIM,
443
+ options: { routeEntry: internalCloneRouteEntry(sourceAction.options.routeEntry) },
444
+ result: {
445
+ fromRouteEntry: internalCloneRouteEntry(sourceAction.result.fromRouteEntry),
446
+ trimmedRouteEntries: sourceAction.result.trimmedRouteEntries.map((entry) => {
447
+ return internalCloneRouteEntry(entry)
448
+ })
449
+ }
450
+ }
451
+ }
452
+ case ROUTE_ACTION_TYPE.PUSH: {
453
+ return {
454
+ type: ROUTE_ACTION_TYPE.PUSH,
455
+ options: { route: internalCloneRoute(sourceAction.options.route) },
456
+ result: {
457
+ fromRouteEntry: internalCloneRouteEntry(sourceAction.result.fromRouteEntry),
458
+ toRouteEntry: internalCloneRouteEntry(sourceAction.result.toRouteEntry)
459
+ }
460
+ }
461
+ }
462
+ case ROUTE_ACTION_TYPE.REPLACE: {
463
+ return {
464
+ type: ROUTE_ACTION_TYPE.REPLACE,
465
+ options: { route: internalCloneRoute(sourceAction.options.route) },
466
+ result: {
467
+ fromRouteEntry: internalCloneRouteEntry(sourceAction.result.fromRouteEntry),
468
+ toRouteEntry: internalCloneRouteEntry(sourceAction.result.toRouteEntry)
469
+ }
470
+ }
471
+ }
472
+ default: {
473
+ throw new Error(`Unsupported route history action type`)
474
+ }
475
+ }
476
+ }
477
+
478
+ const cloneRouteHistoryEntry = (sourceHistoryEntry: RouteHistoryEntry): RouteHistoryEntry => {
479
+ const cachedRouteHistoryEntry = internalRouteHistoryEntryMap.get(sourceHistoryEntry)
480
+ if (cachedRouteHistoryEntry !== undefined) {
481
+ return cachedRouteHistoryEntry
482
+ }
483
+
484
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
485
+ const clonedRouteHistoryEntry = {
486
+ id: sourceHistoryEntry.id,
487
+ from: internalCloneRouteEntry(sourceHistoryEntry.from),
488
+ to: internalCloneRouteEntry(sourceHistoryEntry.to),
489
+ type: sourceHistoryEntry.type,
490
+ options: cloneHistoryOptions(sourceHistoryEntry),
491
+ actions: []
492
+ } as RouteHistoryEntry
493
+ internalRouteHistoryEntryMap.set(sourceHistoryEntry, clonedRouteHistoryEntry)
494
+
495
+ clonedRouteHistoryEntry.actions = sourceHistoryEntry.actions.map((action) => {
496
+ return cloneRouteHistoryAction(action)
497
+ })
498
+
499
+ return clonedRouteHistoryEntry
500
+ }
501
+
502
+ return cloneRouteHistoryEntry(historyEntry)
503
+ }
504
+
505
+ /**
506
+ * Router 对外发出的事件集合。
507
+ */
508
+ export type RouterEvents = BuildEvents<{
509
+ history: (routeHistoryEntry: RouteHistoryEntry) => void
510
+ }>
511
+
512
+ /**
513
+ * 创建 Router 时使用的输入项。
514
+ */
515
+ export interface RouterOptions {
516
+ url: string
517
+ }
518
+
519
+ /**
520
+ * 路由历史管理器。
521
+ *
522
+ * Router 维护一条可漫游、可裁剪、可替换的路由历史链表,并通过 history 事件向外暴露每次动作对应的历史记录。
523
+ */
524
+ export class Router {
525
+ protected options: RouterOptions
526
+ protected history: RouteHistoryEntry[]
527
+ protected headRouteEntry!: RouteEntry
528
+ protected tailRouteEntry!: RouteEntry
529
+ protected routeEntry!: RouteEntry
530
+
531
+ eventManager: EventManager<RouterEvents>
532
+
533
+ constructor(options: RouterOptions) {
534
+ this.options = options
535
+ this.history = []
536
+ this.eventManager = new EventManager<RouterEvents>()
537
+ this.initialize({ url: options.url })
538
+ }
539
+
540
+ protected initialize(options: RouteInitializeOptions): void {
541
+ const { url } = options
542
+
543
+ const newRoute = new Route({ partialUrl: url })
544
+ const initialRouteEntry: RouteEntry = {
545
+ route: newRoute,
546
+ prevEntry: undefined,
547
+ nextEntry: undefined,
548
+ history: [],
549
+ index: -1
550
+ }
551
+ this.headRouteEntry = initialRouteEntry
552
+ this.tailRouteEntry = initialRouteEntry
553
+ this.routeEntry = initialRouteEntry
554
+
555
+ const historyEntry: RouteHistoryEntry = {
556
+ id: generateUuidV4FromUrl(),
557
+ from: initialRouteEntry,
558
+ to: initialRouteEntry,
559
+ type: ROUTE_TYPE.INITIALIZE,
560
+ options,
561
+ actions: []
562
+ }
563
+ this.pushHistory(historyEntry)
564
+ }
565
+
566
+ /**
567
+ * 返回当前最新的历史记录快照。
568
+ */
569
+ getCurrentRouteHistoryEntry(): RouteHistoryEntry {
570
+ const currentRouteHistoryEntry = this.history.at(-1)
571
+ if (currentRouteHistoryEntry === undefined) {
572
+ throw new Error(`No current route history entry`)
573
+ }
574
+ internalCloneReset()
575
+ const clonedRouteHistoryEntry = internalCloneRouteHistoryEntry(currentRouteHistoryEntry)
576
+ return clonedRouteHistoryEntry
577
+ }
578
+
579
+ /**
580
+ * 返回当前最新历史记录的标识。
581
+ */
582
+ getCurrentRouteHistoryEntryId(): string {
583
+ const currentRouteHistoryEntry = this.history.at(-1)
584
+ if (currentRouteHistoryEntry === undefined) {
585
+ throw new Error(`No current route history entry`)
586
+ }
587
+ const currentRouteHistoryEntryId = currentRouteHistoryEntry.id
588
+ return currentRouteHistoryEntryId
589
+ }
590
+
591
+ protected pushHistory(routeHistoryEntry: RouteHistoryEntry): void {
592
+ this.history.push(routeHistoryEntry)
593
+ internalCloneReset()
594
+ const clonedRouteHistoryEntry = internalCloneRouteHistoryEntry(routeHistoryEntry)
595
+ this.eventManager.emit("history", clonedRouteHistoryEntry)
596
+ }
597
+
598
+ /**
599
+ * 判断当前位置是否处于历史尾部之前。
600
+ */
601
+ isRoaming(): boolean {
602
+ return this.routeEntry !== this.tailRouteEntry
603
+ }
604
+
605
+ /**
606
+ * 返回当前位置最多还能向前漫游多少步。
607
+ */
608
+ getMaxRoamingForwardSteps(): number {
609
+ return this.tailRouteEntry.index - this.routeEntry.index
610
+ }
611
+
612
+ /**
613
+ * 返回当前位置最多还能向后漫游多少步。
614
+ */
615
+ getMaxRoamingBackwardSteps(): number {
616
+ return this.routeEntry.index - this.headRouteEntry.index
617
+ }
618
+
619
+ /**
620
+ * 判断当前位置是否还能继续向前漫游。
621
+ */
622
+ canRoamingForward(): boolean {
623
+ return this.getMaxRoamingForwardSteps() > 0
624
+ }
625
+
626
+ /**
627
+ * 判断当前位置是否还能继续向后漫游。
628
+ */
629
+ canRoamingBackward(): boolean {
630
+ return this.getMaxRoamingBackwardSteps() > 0
631
+ }
632
+
633
+ protected move(options: RouteMoveOptions): RouteMoveResult {
634
+ const { step } = options
635
+
636
+ const fromRouteEntry = this.routeEntry
637
+ let toRouteEntry = this.routeEntry
638
+ const direction = step > 0 ? "forward" : step < 0 ? "backward" : "none"
639
+
640
+ if (step > 0) {
641
+ for (let currentStep = 0; currentStep < step; currentStep = currentStep + 1) {
642
+ if (toRouteEntry.nextEntry === undefined) {
643
+ break
644
+ }
645
+ toRouteEntry = toRouteEntry.nextEntry
646
+ }
647
+ }
648
+ else if (step < 0) {
649
+ for (let currentStep = 0; currentStep > step; currentStep = currentStep - 1) {
650
+ if (toRouteEntry.prevEntry === undefined) {
651
+ break
652
+ }
653
+ toRouteEntry = toRouteEntry.prevEntry
654
+ }
655
+ }
656
+
657
+ this.routeEntry = toRouteEntry
658
+
659
+ return { fromRouteEntry, toRouteEntry, direction }
660
+ }
661
+
662
+ protected trim(options: RouteTrimOptions): RouteTrimResult {
663
+ const { routeEntry } = options
664
+
665
+ const fromRouteEntry = routeEntry
666
+ const trimmedRouteEntries: RouteEntry[] = []
667
+
668
+ let nextEntry = fromRouteEntry.nextEntry
669
+ while (nextEntry !== undefined) {
670
+ trimmedRouteEntries.push(nextEntry)
671
+ nextEntry = nextEntry.nextEntry
672
+ }
673
+
674
+ fromRouteEntry.nextEntry = undefined
675
+ this.tailRouteEntry = fromRouteEntry
676
+
677
+ return { fromRouteEntry, trimmedRouteEntries }
678
+ }
679
+
680
+ protected push(options: RoutePushOptions): RoutePushResult {
681
+ const { route } = options
682
+
683
+ const fromRouteEntry = this.routeEntry
684
+
685
+ const toRouteEntry: RouteEntry = {
686
+ route,
687
+ prevEntry: this.routeEntry,
688
+ nextEntry: undefined,
689
+ history: [],
690
+ index: this.routeEntry.index + 1
691
+ }
692
+
693
+ this.routeEntry.nextEntry = toRouteEntry
694
+
695
+ this.tailRouteEntry = toRouteEntry
696
+ this.routeEntry = toRouteEntry
697
+
698
+ return { fromRouteEntry, toRouteEntry }
699
+ }
700
+
701
+ protected replace(options: RouteReplaceOptions): RouteReplaceResult {
702
+ const { route } = options
703
+
704
+ const fromRouteEntry = this.routeEntry
705
+
706
+ const prevEntry = this.routeEntry.prevEntry
707
+ const nextEntry = this.routeEntry.nextEntry
708
+ const toRouteEntry: RouteEntry = {
709
+ route,
710
+ prevEntry,
711
+ nextEntry,
712
+ history: [],
713
+ index: fromRouteEntry.index
714
+ }
715
+
716
+ if (prevEntry !== undefined) {
717
+ prevEntry.nextEntry = toRouteEntry
718
+ }
719
+ if (nextEntry !== undefined) {
720
+ nextEntry.prevEntry = toRouteEntry
721
+ }
722
+
723
+ if (prevEntry === undefined) {
724
+ this.headRouteEntry = toRouteEntry
725
+ }
726
+ if (nextEntry === undefined) {
727
+ this.tailRouteEntry = toRouteEntry
728
+ }
729
+ this.routeEntry = toRouteEntry
730
+
731
+ return { fromRouteEntry, toRouteEntry }
732
+ }
733
+
734
+ protected internalFind(predicate: (routeEntry: RouteEntry) => boolean): RouteEntry | undefined {
735
+ let currentEntry: RouteEntry | undefined = this.headRouteEntry
736
+ while (currentEntry !== undefined) {
737
+ if (predicate(currentEntry)) {
738
+ return currentEntry
739
+ }
740
+ currentEntry = currentEntry.nextEntry
741
+ }
742
+ return undefined
743
+ }
744
+
745
+ protected internalFindForward(predicate: (routeEntry: RouteEntry) => boolean): RouteEntry | undefined {
746
+ let currentEntry = this.routeEntry.nextEntry
747
+ while (currentEntry !== undefined) {
748
+ if (predicate(currentEntry)) {
749
+ return currentEntry
750
+ }
751
+ currentEntry = currentEntry.nextEntry
752
+ }
753
+ return undefined
754
+ }
755
+
756
+ protected internalFindBackward(predicate: (routeEntry: RouteEntry) => boolean): RouteEntry | undefined {
757
+ let currentEntry = this.routeEntry.prevEntry
758
+ while (currentEntry !== undefined) {
759
+ if (predicate(currentEntry)) {
760
+ return currentEntry
761
+ }
762
+ currentEntry = currentEntry.prevEntry
763
+ }
764
+ return undefined
765
+ }
766
+
767
+ /**
768
+ * 在当前位置生成一条 refresh 历史记录。
769
+ */
770
+ refresh(options: RouteRefreshOptions): void {
771
+ const actions: RouteHistoryAction[] = []
772
+
773
+ const moveOptions: RouteMoveOptions = { step: 0 }
774
+ const moveResult = this.move(moveOptions)
775
+ actions.push({
776
+ type: ROUTE_ACTION_TYPE.MOVE,
777
+ options: moveOptions,
778
+ result: moveResult
779
+ })
780
+
781
+ const fromRouteEntry = moveResult.fromRouteEntry
782
+ const toRouteEntry = moveResult.toRouteEntry
783
+ const historyEntry: RouteHistoryEntry = {
784
+ id: generateUuidV4FromUrl(),
785
+ from: fromRouteEntry,
786
+ to: toRouteEntry,
787
+ type: ROUTE_TYPE.REFRESH,
788
+ options,
789
+ actions,
790
+ }
791
+ toRouteEntry.history.push(historyEntry)
792
+ this.pushHistory(historyEntry)
793
+ }
794
+
795
+ /**
796
+ * 用新地址替换当前位置,并记录 replace-to 历史。
797
+ */
798
+ replaceTo(options: RouteReplaceToOptions): void {
799
+ const { url } = options
800
+
801
+ const newRoute = new Route({ partialUrl: url })
802
+
803
+ const actions: RouteHistoryAction[] = []
804
+
805
+ const replaceOptions: RouteReplaceOptions = { route: newRoute }
806
+ const replaceResult = this.replace(replaceOptions)
807
+ actions.push({
808
+ type: ROUTE_ACTION_TYPE.REPLACE,
809
+ options: replaceOptions,
810
+ result: replaceResult
811
+ })
812
+
813
+ const fromRouteEntry = replaceResult.fromRouteEntry
814
+ const toRouteEntry = replaceResult.toRouteEntry
815
+ const historyEntry: RouteHistoryEntry = {
816
+ id: generateUuidV4FromUrl(),
817
+ from: fromRouteEntry,
818
+ to: toRouteEntry,
819
+ type: ROUTE_TYPE.REPLACE_TO,
820
+ options,
821
+ actions,
822
+ }
823
+ toRouteEntry.history.push(historyEntry)
824
+ this.pushHistory(historyEntry)
825
+ }
826
+
827
+ /**
828
+ * 切换到历史尾部并用新地址替换该位置。
829
+ */
830
+ switchTo(options: RouteSwitchToOptions): void {
831
+ const { url } = options
832
+
833
+ const newRoute = new Route({ partialUrl: url })
834
+ const targetStep = this.getMaxRoamingForwardSteps()
835
+
836
+ const actions: RouteHistoryAction[] = []
837
+
838
+ const moveOptions: RouteMoveOptions = { step: targetStep }
839
+ const moveResult = this.move(moveOptions)
840
+ actions.push({
841
+ type: ROUTE_ACTION_TYPE.MOVE,
842
+ options: moveOptions,
843
+ result: moveResult
844
+ })
845
+
846
+ const trimOptions: RouteTrimOptions = { routeEntry: moveResult.toRouteEntry }
847
+ const trimResult = this.trim(trimOptions)
848
+ actions.push({
849
+ type: ROUTE_ACTION_TYPE.TRIM,
850
+ options: trimOptions,
851
+ result: trimResult
852
+ })
853
+
854
+ const replaceOptions: RouteReplaceOptions = { route: newRoute }
855
+ const replaceResult = this.replace(replaceOptions)
856
+ actions.push({
857
+ type: ROUTE_ACTION_TYPE.REPLACE,
858
+ options: replaceOptions,
859
+ result: replaceResult
860
+ })
861
+
862
+ const fromRouteEntry = moveResult.fromRouteEntry
863
+ const toRouteEntry = replaceResult.toRouteEntry
864
+ const historyEntry: RouteHistoryEntry = {
865
+ id: generateUuidV4FromUrl(),
866
+ from: fromRouteEntry,
867
+ to: toRouteEntry,
868
+ type: ROUTE_TYPE.SWITCH_TO,
869
+ options,
870
+ actions
871
+ }
872
+ toRouteEntry.history.push(historyEntry)
873
+ this.pushHistory(historyEntry)
874
+ }
875
+
876
+ /**
877
+ * 裁剪当前位置之后的分支,并把新地址压入历史尾部。
878
+ */
879
+ navigateTo(options: RouteNavigateToOptions): void {
880
+ const { url } = options
881
+
882
+ const newRoute = new Route({ partialUrl: url })
883
+
884
+ const actions: RouteHistoryAction[] = []
885
+
886
+ const trimOptions: RouteTrimOptions = { routeEntry: this.routeEntry }
887
+ const trimResult = this.trim(trimOptions)
888
+ actions.push({
889
+ type: ROUTE_ACTION_TYPE.TRIM,
890
+ options: trimOptions,
891
+ result: trimResult
892
+ })
893
+
894
+ const pushOptions: RoutePushOptions = { route: newRoute }
895
+ const pushResult = this.push(pushOptions)
896
+ actions.push({
897
+ type: ROUTE_ACTION_TYPE.PUSH,
898
+ options: pushOptions,
899
+ result: pushResult
900
+ })
901
+
902
+ const fromRouteEntry = trimResult.fromRouteEntry
903
+ const toRouteEntry = pushResult.toRouteEntry
904
+ const historyEntry: RouteHistoryEntry = {
905
+ id: generateUuidV4FromUrl(),
906
+ from: fromRouteEntry,
907
+ to: toRouteEntry,
908
+ type: ROUTE_TYPE.NAVIGATE_TO,
909
+ options,
910
+ actions
911
+ }
912
+ toRouteEntry.history.push(historyEntry)
913
+ this.pushHistory(historyEntry)
914
+ }
915
+
916
+ /**
917
+ * 基于当前 route 更新 search,并以追加历史的方式导航。
918
+ */
919
+ navigateSearch(options: RouteNavigateSearchOptions): void {
920
+ const { search } = options
921
+
922
+ const newRoute = this.routeEntry.route.clone().search({ search })
923
+
924
+ const actions: RouteHistoryAction[] = []
925
+
926
+ const trimOptions: RouteTrimOptions = { routeEntry: this.routeEntry }
927
+ const trimResult = this.trim(trimOptions)
928
+ actions.push({
929
+ type: ROUTE_ACTION_TYPE.TRIM,
930
+ options: trimOptions,
931
+ result: trimResult
932
+ })
933
+
934
+ const pushOptions: RoutePushOptions = { route: newRoute }
935
+ const pushResult = this.push(pushOptions)
936
+ actions.push({
937
+ type: ROUTE_ACTION_TYPE.PUSH,
938
+ options: pushOptions,
939
+ result: pushResult
940
+ })
941
+
942
+ const fromRouteEntry = trimResult.fromRouteEntry
943
+ const toRouteEntry = pushResult.toRouteEntry
944
+ const historyEntry: RouteHistoryEntry = {
945
+ id: generateUuidV4FromUrl(),
946
+ from: fromRouteEntry,
947
+ to: toRouteEntry,
948
+ type: ROUTE_TYPE.NAVIGATE_SEARCH,
949
+ options,
950
+ actions
951
+ }
952
+ toRouteEntry.history.push(historyEntry)
953
+ this.pushHistory(historyEntry)
954
+ }
955
+
956
+ /**
957
+ * 基于当前 route 更新 hash,并以追加历史的方式导航。
958
+ */
959
+ navigateHash(options: RouteNavigateHashOptions): void {
960
+ const { hash } = options
961
+
962
+ const newRoute = this.routeEntry.route.clone().hash({ hash })
963
+
964
+ const actions: RouteHistoryAction[] = []
965
+
966
+ const trimOptions: RouteTrimOptions = { routeEntry: this.routeEntry }
967
+ const trimResult = this.trim(trimOptions)
968
+ actions.push({
969
+ type: ROUTE_ACTION_TYPE.TRIM,
970
+ options: trimOptions,
971
+ result: trimResult
972
+ })
973
+
974
+ const pushOptions: RoutePushOptions = { route: newRoute }
975
+ const pushResult = this.push(pushOptions)
976
+ actions.push({
977
+ type: ROUTE_ACTION_TYPE.PUSH,
978
+ options: pushOptions,
979
+ result: pushResult
980
+ })
981
+
982
+ const fromRouteEntry = trimResult.fromRouteEntry
983
+ const toRouteEntry = pushResult.toRouteEntry
984
+ const historyEntry: RouteHistoryEntry = {
985
+ id: generateUuidV4FromUrl(),
986
+ from: fromRouteEntry,
987
+ to: toRouteEntry,
988
+ type: ROUTE_TYPE.NAVIGATE_HASH,
989
+ options,
990
+ actions
991
+ }
992
+ toRouteEntry.history.push(historyEntry)
993
+ this.pushHistory(historyEntry)
994
+ }
995
+
996
+ /**
997
+ * 裁剪当前位置之后的分支,并直接替换当前位置为新地址。
998
+ */
999
+ redirectTo(options: RouteRedirectToOptions): void {
1000
+ const { url } = options
1001
+
1002
+ const newRoute = new Route({ partialUrl: url })
1003
+
1004
+ const actions: RouteHistoryAction[] = []
1005
+
1006
+ const trimOptions: RouteTrimOptions = { routeEntry: this.routeEntry }
1007
+ const trimResult = this.trim(trimOptions)
1008
+ actions.push({
1009
+ type: ROUTE_ACTION_TYPE.TRIM,
1010
+ options: trimOptions,
1011
+ result: trimResult
1012
+ })
1013
+
1014
+ const replaceOptions: RouteReplaceOptions = { route: newRoute }
1015
+ const replaceResult = this.replace(replaceOptions)
1016
+ actions.push({
1017
+ type: ROUTE_ACTION_TYPE.REPLACE,
1018
+ options: replaceOptions,
1019
+ result: replaceResult
1020
+ })
1021
+
1022
+ const fromRouteEntry = trimResult.fromRouteEntry
1023
+ const toRouteEntry = replaceResult.toRouteEntry
1024
+ const historyEntry: RouteHistoryEntry = {
1025
+ id: generateUuidV4FromUrl(),
1026
+ from: fromRouteEntry,
1027
+ to: toRouteEntry,
1028
+ type: ROUTE_TYPE.REDIRECT_TO,
1029
+ options,
1030
+ actions
1031
+ }
1032
+ toRouteEntry.history.push(historyEntry)
1033
+ this.pushHistory(historyEntry)
1034
+ }
1035
+
1036
+ /**
1037
+ * 基于当前 route 更新 search,并直接替换当前位置。
1038
+ */
1039
+ redirectSearch(options: RouteRedirectSearchOptions): void {
1040
+ const { search } = options
1041
+
1042
+ const newRoute = this.routeEntry.route.clone().search({ search })
1043
+
1044
+ const actions: RouteHistoryAction[] = []
1045
+
1046
+ const trimOptions: RouteTrimOptions = { routeEntry: this.routeEntry }
1047
+ const trimResult = this.trim(trimOptions)
1048
+ actions.push({
1049
+ type: ROUTE_ACTION_TYPE.TRIM,
1050
+ options: trimOptions,
1051
+ result: trimResult
1052
+ })
1053
+
1054
+ const replaceOptions: RouteReplaceOptions = { route: newRoute }
1055
+ const replaceResult = this.replace(replaceOptions)
1056
+ actions.push({
1057
+ type: ROUTE_ACTION_TYPE.REPLACE,
1058
+ options: replaceOptions,
1059
+ result: replaceResult
1060
+ })
1061
+
1062
+ const fromRouteEntry = trimResult.fromRouteEntry
1063
+ const toRouteEntry = replaceResult.toRouteEntry
1064
+ const historyEntry: RouteHistoryEntry = {
1065
+ id: generateUuidV4FromUrl(),
1066
+ from: fromRouteEntry,
1067
+ to: toRouteEntry,
1068
+ type: ROUTE_TYPE.REDIRECT_SEARCH,
1069
+ options,
1070
+ actions
1071
+ }
1072
+ toRouteEntry.history.push(historyEntry)
1073
+ this.pushHistory(historyEntry)
1074
+ }
1075
+
1076
+ /**
1077
+ * 基于当前 route 更新 hash,并直接替换当前位置。
1078
+ */
1079
+ redirectHash(options: RouteRedirectHashOptions): void {
1080
+ const { hash } = options
1081
+
1082
+ const newRoute = this.routeEntry.route.clone().hash({ hash })
1083
+
1084
+ const actions: RouteHistoryAction[] = []
1085
+
1086
+ const trimOptions: RouteTrimOptions = { routeEntry: this.routeEntry }
1087
+ const trimResult = this.trim(trimOptions)
1088
+ actions.push({
1089
+ type: ROUTE_ACTION_TYPE.TRIM,
1090
+ options: trimOptions,
1091
+ result: trimResult
1092
+ })
1093
+
1094
+ const replaceOptions: RouteReplaceOptions = { route: newRoute }
1095
+ const replaceResult = this.replace(replaceOptions)
1096
+ actions.push({
1097
+ type: ROUTE_ACTION_TYPE.REPLACE,
1098
+ options: replaceOptions,
1099
+ result: replaceResult
1100
+ })
1101
+
1102
+ const fromRouteEntry = trimResult.fromRouteEntry
1103
+ const toRouteEntry = replaceResult.toRouteEntry
1104
+ const historyEntry: RouteHistoryEntry = {
1105
+ id: generateUuidV4FromUrl(),
1106
+ from: fromRouteEntry,
1107
+ to: toRouteEntry,
1108
+ type: ROUTE_TYPE.REDIRECT_HASH,
1109
+ options,
1110
+ actions
1111
+ }
1112
+ toRouteEntry.history.push(historyEntry)
1113
+ this.pushHistory(historyEntry)
1114
+ }
1115
+
1116
+ /**
1117
+ * 按相对步数在历史链表中漫游,但不裁剪分支。
1118
+ */
1119
+ roamingBy(options: RouteRoamingByOptions): void {
1120
+ const { step } = options
1121
+
1122
+ if (Number.isInteger(step) === false) {
1123
+ throw new TypeError("Step must be an integer")
1124
+ }
1125
+
1126
+ const actions: RouteHistoryAction[] = []
1127
+
1128
+ const moveOptions: RouteMoveOptions = { step }
1129
+ const moveResult = this.move(moveOptions)
1130
+ actions.push({
1131
+ type: ROUTE_ACTION_TYPE.MOVE,
1132
+ options: moveOptions,
1133
+ result: moveResult
1134
+ })
1135
+
1136
+ const fromRouteEntry = moveResult.fromRouteEntry
1137
+ const toRouteEntry = moveResult.toRouteEntry
1138
+ const historyEntry: RouteHistoryEntry = {
1139
+ id: generateUuidV4FromUrl(),
1140
+ from: fromRouteEntry,
1141
+ to: toRouteEntry,
1142
+ type: ROUTE_TYPE.ROAMING_BY,
1143
+ options,
1144
+ actions
1145
+ }
1146
+ toRouteEntry.history.push(historyEntry)
1147
+ this.pushHistory(historyEntry)
1148
+ }
1149
+
1150
+ /**
1151
+ * 按非负步数向前漫游,但不裁剪分支。
1152
+ */
1153
+ roamingForwardBy(options: RouteRoamingForwardByOptions): void {
1154
+ const { step } = options
1155
+
1156
+ if (Number.isInteger(step) === false) {
1157
+ throw new TypeError("Step must be an integer")
1158
+ }
1159
+ if (step < 0) {
1160
+ throw new RangeError("Step must be a non-negative integer")
1161
+ }
1162
+
1163
+ const actions: RouteHistoryAction[] = []
1164
+
1165
+ const moveOptions: RouteMoveOptions = { step }
1166
+ const moveResult = this.move(moveOptions)
1167
+ actions.push({
1168
+ type: ROUTE_ACTION_TYPE.MOVE,
1169
+ options: moveOptions,
1170
+ result: moveResult
1171
+ })
1172
+
1173
+ const fromRouteEntry = moveResult.fromRouteEntry
1174
+ const toRouteEntry = moveResult.toRouteEntry
1175
+ const historyEntry: RouteHistoryEntry = {
1176
+ id: generateUuidV4FromUrl(),
1177
+ from: fromRouteEntry,
1178
+ to: toRouteEntry,
1179
+ type: ROUTE_TYPE.ROAMING_FORWARD_BY,
1180
+ options,
1181
+ actions
1182
+ }
1183
+ toRouteEntry.history.push(historyEntry)
1184
+ this.pushHistory(historyEntry)
1185
+ }
1186
+
1187
+ /**
1188
+ * 按非负步数向后漫游,但不裁剪分支。
1189
+ */
1190
+ roamingBackwardBy(options: RouteRoamingBackwardByOptions): void {
1191
+ const { step } = options
1192
+
1193
+ if (Number.isInteger(step) === false) {
1194
+ throw new TypeError("Step must be an integer")
1195
+ }
1196
+ if (step < 0) {
1197
+ throw new RangeError("Step must be a non-negative integer")
1198
+ }
1199
+
1200
+ const actions: RouteHistoryAction[] = []
1201
+
1202
+ const moveOptions: RouteMoveOptions = { step: -step }
1203
+ const moveResult = this.move(moveOptions)
1204
+ actions.push({
1205
+ type: ROUTE_ACTION_TYPE.MOVE,
1206
+ options: moveOptions,
1207
+ result: moveResult
1208
+ })
1209
+
1210
+ const fromRouteEntry = moveResult.fromRouteEntry
1211
+ const toRouteEntry = moveResult.toRouteEntry
1212
+ const historyEntry: RouteHistoryEntry = {
1213
+ id: generateUuidV4FromUrl(),
1214
+ from: fromRouteEntry,
1215
+ to: toRouteEntry,
1216
+ type: ROUTE_TYPE.ROAMING_BACKWARD_BY,
1217
+ options,
1218
+ actions
1219
+ }
1220
+ toRouteEntry.history.push(historyEntry)
1221
+ this.pushHistory(historyEntry)
1222
+ }
1223
+
1224
+ /**
1225
+ * 在整条历史链表中查找首个匹配项并漫游到该位置。
1226
+ */
1227
+ roamingTo(options: RouteRoamingToOptions): void {
1228
+ const { predicate } = options
1229
+
1230
+ const targetRouteEntry = this.internalFind(predicate)
1231
+ let targetStep: number
1232
+ if (targetRouteEntry === undefined) {
1233
+ targetStep = 0
1234
+ } else {
1235
+ targetStep = targetRouteEntry.index - this.routeEntry.index
1236
+ }
1237
+
1238
+ const actions: RouteHistoryAction[] = []
1239
+
1240
+ const moveOptions: RouteMoveOptions = { step: targetStep }
1241
+ const moveResult = this.move(moveOptions)
1242
+ actions.push({
1243
+ type: ROUTE_ACTION_TYPE.MOVE,
1244
+ options: moveOptions,
1245
+ result: moveResult
1246
+ })
1247
+
1248
+ const fromRouteEntry = moveResult.fromRouteEntry
1249
+ const toRouteEntry = moveResult.toRouteEntry
1250
+ const historyEntry: RouteHistoryEntry = {
1251
+ id: generateUuidV4FromUrl(),
1252
+ from: fromRouteEntry,
1253
+ to: toRouteEntry,
1254
+ type: ROUTE_TYPE.ROAMING_TO,
1255
+ options,
1256
+ actions
1257
+ }
1258
+ toRouteEntry.history.push(historyEntry)
1259
+ this.pushHistory(historyEntry)
1260
+ }
1261
+
1262
+ /**
1263
+ * 在当前位置之后查找首个匹配项并漫游到该位置。
1264
+ */
1265
+ roamingForwardTo(options: RouteRoamingForwardToOptions): void {
1266
+ const { predicate } = options
1267
+
1268
+ const targetRouteEntry = this.internalFindForward(predicate)
1269
+ let targetStep: number
1270
+ if (targetRouteEntry === undefined) {
1271
+ targetStep = 0
1272
+ } else {
1273
+ targetStep = targetRouteEntry.index - this.routeEntry.index
1274
+ }
1275
+
1276
+ const actions: RouteHistoryAction[] = []
1277
+
1278
+ const moveOptions: RouteMoveOptions = { step: targetStep }
1279
+ const moveResult = this.move(moveOptions)
1280
+ actions.push({
1281
+ type: ROUTE_ACTION_TYPE.MOVE,
1282
+ options: moveOptions,
1283
+ result: moveResult
1284
+ })
1285
+
1286
+ const fromRouteEntry = moveResult.fromRouteEntry
1287
+ const toRouteEntry = moveResult.toRouteEntry
1288
+ const historyEntry: RouteHistoryEntry = {
1289
+ id: generateUuidV4FromUrl(),
1290
+ from: fromRouteEntry,
1291
+ to: toRouteEntry,
1292
+ type: ROUTE_TYPE.ROAMING_FORWARD_TO,
1293
+ options,
1294
+ actions
1295
+ }
1296
+ toRouteEntry.history.push(historyEntry)
1297
+ this.pushHistory(historyEntry)
1298
+ }
1299
+
1300
+ /**
1301
+ * 在当前位置之前查找首个匹配项并漫游到该位置。
1302
+ */
1303
+ roamingBackwardTo(options: RouteRoamingBackwardToOptions): void {
1304
+ const { predicate } = options
1305
+
1306
+ const targetRouteEntry = this.internalFindBackward(predicate)
1307
+ let targetStep: number
1308
+ if (targetRouteEntry === undefined) {
1309
+ targetStep = 0
1310
+ } else {
1311
+ targetStep = targetRouteEntry.index - this.routeEntry.index
1312
+ }
1313
+
1314
+ const actions: RouteHistoryAction[] = []
1315
+
1316
+ const moveOptions: RouteMoveOptions = { step: targetStep }
1317
+ const moveResult = this.move(moveOptions)
1318
+ actions.push({
1319
+ type: ROUTE_ACTION_TYPE.MOVE,
1320
+ options: moveOptions,
1321
+ result: moveResult
1322
+ })
1323
+
1324
+ const fromRouteEntry = moveResult.fromRouteEntry
1325
+ const toRouteEntry = moveResult.toRouteEntry
1326
+ const historyEntry: RouteHistoryEntry = {
1327
+ id: generateUuidV4FromUrl(),
1328
+ from: fromRouteEntry,
1329
+ to: toRouteEntry,
1330
+ type: ROUTE_TYPE.ROAMING_BACKWARD_TO,
1331
+ options,
1332
+ actions
1333
+ }
1334
+ toRouteEntry.history.push(historyEntry)
1335
+ this.pushHistory(historyEntry)
1336
+ }
1337
+
1338
+ /**
1339
+ * 按相对步数在历史链表中移动,并裁剪目标位置之后的分支。
1340
+ */
1341
+ goBy(options: RouteGoByOptions): void {
1342
+ const { step } = options
1343
+
1344
+ if (Number.isInteger(step) === false) {
1345
+ throw new TypeError("Step must be an integer")
1346
+ }
1347
+
1348
+ const actions: RouteHistoryAction[] = []
1349
+
1350
+ const moveOptions: RouteMoveOptions = { step }
1351
+ const moveResult = this.move(moveOptions)
1352
+ actions.push({
1353
+ type: ROUTE_ACTION_TYPE.MOVE,
1354
+ options: moveOptions,
1355
+ result: moveResult
1356
+ })
1357
+
1358
+ const trimOptions: RouteTrimOptions = { routeEntry: moveResult.toRouteEntry }
1359
+ const trimResult = this.trim(trimOptions)
1360
+ actions.push({
1361
+ type: ROUTE_ACTION_TYPE.TRIM,
1362
+ options: trimOptions,
1363
+ result: trimResult
1364
+ })
1365
+
1366
+ const fromRouteEntry = moveResult.fromRouteEntry
1367
+ const toRouteEntry = moveResult.toRouteEntry
1368
+ const historyEntry: RouteHistoryEntry = {
1369
+ id: generateUuidV4FromUrl(),
1370
+ from: fromRouteEntry,
1371
+ to: toRouteEntry,
1372
+ type: ROUTE_TYPE.GO_BY,
1373
+ options,
1374
+ actions
1375
+ }
1376
+ toRouteEntry.history.push(historyEntry)
1377
+ this.pushHistory(historyEntry)
1378
+ }
1379
+
1380
+ /**
1381
+ * 按非负步数向前移动,并裁剪目标位置之后的分支。
1382
+ */
1383
+ goForwardBy(options: RouteGoForwardByOptions): void {
1384
+ const { step } = options
1385
+
1386
+ if (Number.isInteger(step) === false) {
1387
+ throw new TypeError("Step must be an integer")
1388
+ }
1389
+ if (step < 0) {
1390
+ throw new RangeError("Step must be a non-negative integer")
1391
+ }
1392
+
1393
+ const actions: RouteHistoryAction[] = []
1394
+
1395
+ const moveOptions: RouteMoveOptions = { step }
1396
+ const moveResult = this.move(moveOptions)
1397
+ actions.push({
1398
+ type: ROUTE_ACTION_TYPE.MOVE,
1399
+ options: moveOptions,
1400
+ result: moveResult
1401
+ })
1402
+
1403
+ const trimOptions: RouteTrimOptions = { routeEntry: moveResult.toRouteEntry }
1404
+ const trimResult = this.trim(trimOptions)
1405
+ actions.push({
1406
+ type: ROUTE_ACTION_TYPE.TRIM,
1407
+ options: trimOptions,
1408
+ result: trimResult
1409
+ })
1410
+
1411
+ const fromRouteEntry = moveResult.fromRouteEntry
1412
+ const toRouteEntry = moveResult.toRouteEntry
1413
+ const historyEntry: RouteHistoryEntry = {
1414
+ id: generateUuidV4FromUrl(),
1415
+ from: fromRouteEntry,
1416
+ to: toRouteEntry,
1417
+ type: ROUTE_TYPE.GO_FORWARD_BY,
1418
+ options,
1419
+ actions
1420
+ }
1421
+ toRouteEntry.history.push(historyEntry)
1422
+ this.pushHistory(historyEntry)
1423
+ }
1424
+
1425
+ /**
1426
+ * 按非负步数向后移动,并裁剪目标位置之后的分支。
1427
+ */
1428
+ goBackwardBy(options: RouteGoBackwardByOptions): void {
1429
+ const { step } = options
1430
+
1431
+ if (Number.isInteger(step) === false) {
1432
+ throw new TypeError("Step must be an integer")
1433
+ }
1434
+ if (step < 0) {
1435
+ throw new RangeError("Step must be a non-negative integer")
1436
+ }
1437
+
1438
+ const actions: RouteHistoryAction[] = []
1439
+
1440
+ const moveOptions: RouteMoveOptions = { step: -step }
1441
+ const moveResult = this.move(moveOptions)
1442
+ actions.push({
1443
+ type: ROUTE_ACTION_TYPE.MOVE,
1444
+ options: moveOptions,
1445
+ result: moveResult
1446
+ })
1447
+
1448
+ const trimOptions: RouteTrimOptions = { routeEntry: moveResult.toRouteEntry }
1449
+ const trimResult = this.trim(trimOptions)
1450
+ actions.push({
1451
+ type: ROUTE_ACTION_TYPE.TRIM,
1452
+ options: trimOptions,
1453
+ result: trimResult
1454
+ })
1455
+
1456
+ const fromRouteEntry = moveResult.fromRouteEntry
1457
+ const toRouteEntry = moveResult.toRouteEntry
1458
+ const historyEntry: RouteHistoryEntry = {
1459
+ id: generateUuidV4FromUrl(),
1460
+ from: fromRouteEntry,
1461
+ to: toRouteEntry,
1462
+ type: ROUTE_TYPE.GO_BACKWARD_BY,
1463
+ options,
1464
+ actions
1465
+ }
1466
+ toRouteEntry.history.push(historyEntry)
1467
+ this.pushHistory(historyEntry)
1468
+ }
1469
+
1470
+ /**
1471
+ * 在整条历史链表中查找首个匹配项并移动到该位置,同时裁剪后续分支。
1472
+ */
1473
+ goTo(options: RouteGoToOptions): void {
1474
+ const { predicate } = options
1475
+
1476
+ const targetRouteEntry = this.internalFind(predicate)
1477
+ let targetStep: number
1478
+ if (targetRouteEntry === undefined) {
1479
+ targetStep = 0
1480
+ } else {
1481
+ targetStep = targetRouteEntry.index - this.routeEntry.index
1482
+ }
1483
+
1484
+ const actions: RouteHistoryAction[] = []
1485
+
1486
+ const moveOptions: RouteMoveOptions = { step: targetStep }
1487
+ const moveResult = this.move(moveOptions)
1488
+ actions.push({
1489
+ type: ROUTE_ACTION_TYPE.MOVE,
1490
+ options: moveOptions,
1491
+ result: moveResult
1492
+ })
1493
+
1494
+ const trimOptions: RouteTrimOptions = { routeEntry: moveResult.toRouteEntry }
1495
+ const trimResult = this.trim(trimOptions)
1496
+ actions.push({
1497
+ type: ROUTE_ACTION_TYPE.TRIM,
1498
+ options: trimOptions,
1499
+ result: trimResult
1500
+ })
1501
+
1502
+ const fromRouteEntry = moveResult.fromRouteEntry
1503
+ const toRouteEntry = moveResult.toRouteEntry
1504
+ const historyEntry: RouteHistoryEntry = {
1505
+ id: generateUuidV4FromUrl(),
1506
+ from: fromRouteEntry,
1507
+ to: toRouteEntry,
1508
+ type: ROUTE_TYPE.GO_TO,
1509
+ options,
1510
+ actions
1511
+ }
1512
+ toRouteEntry.history.push(historyEntry)
1513
+ this.pushHistory(historyEntry)
1514
+ }
1515
+
1516
+ /**
1517
+ * 在当前位置之后查找首个匹配项并移动到该位置,同时裁剪后续分支。
1518
+ */
1519
+ goForwardTo(options: RouteGoForwardToOptions): void {
1520
+ const { predicate } = options
1521
+
1522
+ const targetRouteEntry = this.internalFindForward(predicate)
1523
+ let targetStep: number
1524
+ if (targetRouteEntry === undefined) {
1525
+ targetStep = 0
1526
+ } else {
1527
+ targetStep = targetRouteEntry.index - this.routeEntry.index
1528
+ }
1529
+
1530
+ const actions: RouteHistoryAction[] = []
1531
+
1532
+ const moveOptions: RouteMoveOptions = { step: targetStep }
1533
+ const moveResult = this.move(moveOptions)
1534
+ actions.push({
1535
+ type: ROUTE_ACTION_TYPE.MOVE,
1536
+ options: moveOptions,
1537
+ result: moveResult
1538
+ })
1539
+
1540
+ const trimOptions: RouteTrimOptions = { routeEntry: moveResult.toRouteEntry }
1541
+ const trimResult = this.trim(trimOptions)
1542
+ actions.push({
1543
+ type: ROUTE_ACTION_TYPE.TRIM,
1544
+ options: trimOptions,
1545
+ result: trimResult
1546
+ })
1547
+
1548
+ const fromRouteEntry = moveResult.fromRouteEntry
1549
+ const toRouteEntry = moveResult.toRouteEntry
1550
+ const historyEntry: RouteHistoryEntry = {
1551
+ id: generateUuidV4FromUrl(),
1552
+ from: fromRouteEntry,
1553
+ to: toRouteEntry,
1554
+ type: ROUTE_TYPE.GO_FORWARD_TO,
1555
+ options,
1556
+ actions
1557
+ }
1558
+ toRouteEntry.history.push(historyEntry)
1559
+ this.pushHistory(historyEntry)
1560
+ }
1561
+
1562
+ /**
1563
+ * 在当前位置之前查找首个匹配项并移动到该位置,同时裁剪后续分支。
1564
+ */
1565
+ goBackwardTo(options: RouteGoBackwardToOptions): void {
1566
+ const { predicate } = options
1567
+
1568
+ const targetRouteEntry = this.internalFindBackward(predicate)
1569
+ let targetStep: number
1570
+ if (targetRouteEntry === undefined) {
1571
+ targetStep = 0
1572
+ } else {
1573
+ targetStep = targetRouteEntry.index - this.routeEntry.index
1574
+ }
1575
+
1576
+ const actions: RouteHistoryAction[] = []
1577
+
1578
+ const moveOptions: RouteMoveOptions = { step: targetStep }
1579
+ const moveResult = this.move(moveOptions)
1580
+ actions.push({
1581
+ type: ROUTE_ACTION_TYPE.MOVE,
1582
+ options: moveOptions,
1583
+ result: moveResult
1584
+ })
1585
+
1586
+ const trimOptions: RouteTrimOptions = { routeEntry: moveResult.toRouteEntry }
1587
+ const trimResult = this.trim(trimOptions)
1588
+ actions.push({
1589
+ type: ROUTE_ACTION_TYPE.TRIM,
1590
+ options: trimOptions,
1591
+ result: trimResult
1592
+ })
1593
+
1594
+ const fromRouteEntry = moveResult.fromRouteEntry
1595
+ const toRouteEntry = moveResult.toRouteEntry
1596
+ const historyEntry: RouteHistoryEntry = {
1597
+ id: generateUuidV4FromUrl(),
1598
+ from: fromRouteEntry,
1599
+ to: toRouteEntry,
1600
+ type: ROUTE_TYPE.GO_BACKWARD_TO,
1601
+ options,
1602
+ actions
1603
+ }
1604
+ toRouteEntry.history.push(historyEntry)
1605
+ this.pushHistory(historyEntry)
1606
+ }
1607
+
1608
+ /**
1609
+ * 优先向后漫游指定步数;若无法向后漫游,则重定向到指定地址。
1610
+ */
1611
+ roamingBackwardByOrRedirectTo(options: { step: number, url: string }): void {
1612
+ const { step, url } = options
1613
+
1614
+ const canRoamingBackward = this.canRoamingBackward()
1615
+ if (canRoamingBackward === true) {
1616
+ return this.roamingBackwardBy({ step })
1617
+ } else {
1618
+ return this.redirectTo({ url })
1619
+ }
1620
+ }
1621
+
1622
+ /**
1623
+ * 优先向后移动并裁剪历史;若无法向后移动,则重定向到指定地址。
1624
+ */
1625
+ goBackwardByOrRedirectTo(options: { step: number, url: string }): void {
1626
+ const { step, url } = options
1627
+
1628
+ const canRoamingBackward = this.canRoamingBackward()
1629
+ if (canRoamingBackward === true) {
1630
+ return this.goBackwardBy({ step })
1631
+ } else {
1632
+ return this.redirectTo({ url })
1633
+ }
1634
+ }
1635
+ }
1636
+
1637
+ /**
1638
+ * 创建一个 Router 实例。
1639
+ */
1640
+ export const createRouter = (options: RouterOptions): Router => {
1641
+ return new Router(options)
1642
+ }