@planet-matrix/mobius-model 0.6.0 → 0.10.1

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