@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,430 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import {
4
+ emptyRoute,
5
+ isPartialUrl,
6
+ isRoute,
7
+ isRouteOptions,
8
+ isRouteRecord,
9
+ partialUrlToRouteRecord,
10
+ partialUrlToUrl,
11
+ Route,
12
+ createRoute,
13
+ urlToRouteRecord,
14
+ } from "#Source/route/index.ts"
15
+
16
+ test("isPartialUrl accepts string values and rejects non-strings", () => {
17
+ expect(isPartialUrl("/users/42?tab=profile#section=info")).toBe(true)
18
+ expect(isPartialUrl(42)).toBe(false)
19
+ })
20
+
21
+ test("isRouteRecord accepts normalized route records and rejects incomplete objects", () => {
22
+ expect(isRouteRecord(createRoute("/users/42?tab=profile#section=info").getRecord())).toBe(true)
23
+ expect(isRouteRecord({ partialUrl: "/users/42" })).toBe(false)
24
+ })
25
+
26
+ test("partialUrlToUrl resolves partial urls against the placeholder origin", () => {
27
+ const result = partialUrlToUrl({ partialUrl: "profile?tab=security#panel=2", baseUrl: "/users/42/" })
28
+
29
+ expect(result.href).toBe("https://url.example.com/users/42/profile?tab=security#panel=2")
30
+ })
31
+
32
+ test("urlToRouteRecord converts a URL into a normalized route record", () => {
33
+ const record = urlToRouteRecord(new URL("https://example.com/users/42?tab=profile#section=info"))
34
+
35
+ expect(record).toEqual({
36
+ partialUrl: "/users/42?tab=profile#section=info",
37
+ pathname: "/users/42",
38
+ search: "?tab=profile",
39
+ hash: "#section=info",
40
+ pathnameString: "/users/42",
41
+ pathnameArray: ["", "users", "42"],
42
+ query: "tab=profile",
43
+ queryString: "tab=profile",
44
+ queryObject: { tab: "profile" },
45
+ hashString: "#section=info",
46
+ hashObject: { section: "info" },
47
+ })
48
+ })
49
+
50
+ test("partialUrlToRouteRecord normalizes pathname search and hash data", () => {
51
+ expect(partialUrlToRouteRecord("users/42?tab=profile#section=info")).toEqual({
52
+ partialUrl: "/users/42?tab=profile#section=info",
53
+ pathname: "/users/42",
54
+ search: "?tab=profile",
55
+ hash: "#section=info",
56
+ pathnameString: "/users/42",
57
+ pathnameArray: ["", "users", "42"],
58
+ query: "tab=profile",
59
+ queryString: "tab=profile",
60
+ queryObject: { tab: "profile" },
61
+ hashString: "#section=info",
62
+ hashObject: { section: "info" },
63
+ })
64
+ })
65
+
66
+ test("isRouteOptions accepts route option objects with a partialUrl", () => {
67
+ expect(isRouteOptions({ partialUrl: "/users/42" })).toBe(true)
68
+ expect(isRouteOptions({ pathname: "/users/42" })).toBe(false)
69
+ })
70
+
71
+ test("Route.getPayload returns the currently attached payload", () => {
72
+ const currentRoute = new Route<{ id: string }>({ partialUrl: "/users/42" })
73
+
74
+ expect(currentRoute.getPayload()).toBeUndefined()
75
+ currentRoute.setPayload({ id: "42" })
76
+ expect(currentRoute.getPayload()).toEqual({ id: "42" })
77
+ })
78
+
79
+ test("Route.setPayload stores payload values and keeps chainability", () => {
80
+ const currentRoute = new Route<number>({ partialUrl: "/users/42" })
81
+
82
+ expect(currentRoute.setPayload(42)).toBe(currentRoute)
83
+ expect(currentRoute.getPayload()).toBe(42)
84
+ })
85
+
86
+ test("Route.getRecord returns a defensive clone of the internal record", () => {
87
+ const currentRoute = createRoute("/users/42?tab=profile#section=info")
88
+ const snapshot = currentRoute.getRecord()
89
+
90
+ snapshot.queryObject["tab"] = "security"
91
+ snapshot.pathnameArray.push("extra")
92
+
93
+ expect(currentRoute.getRecord()).toEqual({
94
+ partialUrl: "/users/42?tab=profile#section=info",
95
+ pathname: "/users/42",
96
+ search: "?tab=profile",
97
+ hash: "#section=info",
98
+ pathnameString: "/users/42",
99
+ pathnameArray: ["", "users", "42"],
100
+ query: "tab=profile",
101
+ queryString: "tab=profile",
102
+ queryObject: { tab: "profile" },
103
+ hashString: "#section=info",
104
+ hashObject: { section: "info" },
105
+ })
106
+ })
107
+
108
+ test("Route.resetSearch clears search and query fields while keeping pathname and hash", () => {
109
+ const currentRoute = createRoute("/users/42?tab=profile#section=info")
110
+
111
+ currentRoute.resetSearch()
112
+
113
+ expect(currentRoute.getRecord()).toEqual({
114
+ partialUrl: "/users/42#section=info",
115
+ pathname: "/users/42",
116
+ search: "",
117
+ hash: "#section=info",
118
+ pathnameString: "/users/42",
119
+ pathnameArray: ["", "users", "42"],
120
+ query: "",
121
+ queryString: "",
122
+ queryObject: {},
123
+ hashString: "#section=info",
124
+ hashObject: { section: "info" },
125
+ })
126
+ })
127
+
128
+ test("Route.resetQuery aliases resetSearch behavior", () => {
129
+ const currentRoute = createRoute("/users/42?tab=profile#section=info")
130
+
131
+ currentRoute.resetQuery()
132
+
133
+ expect(currentRoute.getRecord().search).toBe("")
134
+ expect(currentRoute.getRecord().queryObject).toEqual({})
135
+ expect(currentRoute.getRecord().partialUrl).toBe("/users/42#section=info")
136
+ })
137
+
138
+ test("Route.resetHash clears hash fields while keeping pathname and search", () => {
139
+ const currentRoute = createRoute("/users/42?tab=profile#section=info")
140
+
141
+ currentRoute.resetHash()
142
+
143
+ expect(currentRoute.getRecord()).toEqual({
144
+ partialUrl: "/users/42?tab=profile",
145
+ pathname: "/users/42",
146
+ search: "?tab=profile",
147
+ hash: "",
148
+ pathnameString: "/users/42",
149
+ pathnameArray: ["", "users", "42"],
150
+ query: "tab=profile",
151
+ queryString: "tab=profile",
152
+ queryObject: { tab: "profile" },
153
+ hashString: "",
154
+ hashObject: {},
155
+ })
156
+ })
157
+
158
+ test("Route.url replaces the current state from a URL instance", () => {
159
+ const currentRoute = createRoute("/users/42")
160
+
161
+ currentRoute.url(new URL("https://example.com/projects/alpha?view=grid#panel=files"))
162
+
163
+ expect(currentRoute.getRecord().partialUrl).toBe("/projects/alpha?view=grid#panel=files")
164
+ })
165
+
166
+ test("Route.partialUrl replaces the current state from a partial url string", () => {
167
+ const currentRoute = createRoute("/users/42")
168
+
169
+ currentRoute.partialUrl("projects/alpha?view=grid#panel=files")
170
+
171
+ expect(currentRoute.getRecord().partialUrl).toBe("/projects/alpha?view=grid#panel=files")
172
+ })
173
+
174
+ test("Route.pathname normalizes the pathname and resets search and hash only when the path changes", () => {
175
+ const currentRoute = createRoute("/users/42?tab=profile#section=info")
176
+
177
+ currentRoute.pathname({ pathname: "/users/42" })
178
+ expect(currentRoute.getRecord().partialUrl).toBe("/users/42?tab=profile#section=info")
179
+
180
+ currentRoute.pathname({ pathname: ["projects", "alpha", ""] })
181
+ expect(currentRoute.getRecord()).toEqual({
182
+ partialUrl: "/projects/alpha/",
183
+ pathname: "/projects/alpha/",
184
+ search: "",
185
+ hash: "",
186
+ pathnameString: "/projects/alpha/",
187
+ pathnameArray: ["", "projects", "alpha", ""],
188
+ query: "",
189
+ queryString: "",
190
+ queryObject: {},
191
+ hashString: "",
192
+ hashObject: {},
193
+ })
194
+ })
195
+
196
+ test("Route.search rewrites search and derived query fields", () => {
197
+ const currentRoute = createRoute("/users/42#section=info")
198
+
199
+ currentRoute.search({ search: { tab: "profile", mode: "grid" } })
200
+
201
+ expect(currentRoute.getRecord()).toEqual({
202
+ partialUrl: "/users/42?tab=profile&mode=grid#section=info",
203
+ pathname: "/users/42",
204
+ search: "?tab=profile&mode=grid",
205
+ hash: "#section=info",
206
+ pathnameString: "/users/42",
207
+ pathnameArray: ["", "users", "42"],
208
+ query: "tab=profile&mode=grid",
209
+ queryString: "tab=profile&mode=grid",
210
+ queryObject: { tab: "profile", mode: "grid" },
211
+ hashString: "#section=info",
212
+ hashObject: { section: "info" },
213
+ })
214
+ })
215
+
216
+ test("Route.upsertSearch merges new query values into the current search state", () => {
217
+ const currentRoute = createRoute("/users/42?page=1&mode=list")
218
+
219
+ currentRoute.upsertSearch({ search: "page=2&filter=active" })
220
+
221
+ expect(currentRoute.getRecord().search).toBe("?page=2&mode=list&filter=active")
222
+ expect(currentRoute.getRecord().queryObject).toEqual({ page: "2", mode: "list", filter: "active" })
223
+ })
224
+
225
+ test("Route.hash rewrites hash data and resets when given an empty string", () => {
226
+ const currentRoute = createRoute("/users/42?tab=profile#section=info")
227
+
228
+ currentRoute.hash({ hash: { section: "permissions", panel: "advanced" } })
229
+ expect(currentRoute.getRecord().hash).toBe("#section=permissions&panel=advanced")
230
+ expect(currentRoute.getRecord().hashObject).toEqual({ section: "permissions", panel: "advanced" })
231
+
232
+ currentRoute.hash({ hash: "" })
233
+ expect(currentRoute.getRecord().hash).toBe("")
234
+ expect(currentRoute.getRecord().partialUrl).toBe("/users/42?tab=profile")
235
+ })
236
+
237
+ test("Route.distinctByPartialUrl compares partial urls for both Route and RouteRecord inputs", () => {
238
+ const left = createRoute("/users/42?tab=profile")
239
+ const right = createRoute("/users/42?tab=security")
240
+
241
+ expect(Route.distinctByPartialUrl(left, right)).toBe(true)
242
+ expect(Route.distinctByPartialUrl(left.getRecord(), left)).toBe(false)
243
+ })
244
+
245
+ test("Route.distinctByPathname compares normalized pathname strings", () => {
246
+ const left = createRoute("/users/42?tab=profile")
247
+ const right = createRoute("/users/99?tab=profile")
248
+
249
+ expect(Route.distinctByPathname(left, right)).toBe(true)
250
+ expect(Route.distinctByPathname(left, left.getRecord())).toBe(false)
251
+ })
252
+
253
+ test("Route.distinctBySearch compares normalized search strings", () => {
254
+ const left = createRoute("/users/42?tab=profile")
255
+ const right = createRoute("/users/42?tab=security")
256
+
257
+ expect(Route.distinctBySearch(left, right)).toBe(true)
258
+ expect(Route.distinctBySearch(left, left.getRecord())).toBe(false)
259
+ })
260
+
261
+ test("Route.distinctByHash compares normalized hash strings", () => {
262
+ const left = createRoute("/users/42#section=profile")
263
+ const right = createRoute("/users/42#section=security")
264
+
265
+ expect(Route.distinctByHash(left, right)).toBe(true)
266
+ expect(Route.distinctByHash(left, left.getRecord())).toBe(false)
267
+ })
268
+
269
+ test("Route.isDistinctByPartialUrl compares the current route against another route", () => {
270
+ const currentRoute = createRoute("/users/42?tab=profile")
271
+
272
+ expect(currentRoute.isDistinctByPartialUrl(createRoute("/users/42?tab=security"))).toBe(true)
273
+ expect(currentRoute.isDistinctByPartialUrl(currentRoute.getRecord())).toBe(false)
274
+ })
275
+
276
+ test("Route.isDistinctByPathname compares pathname differences against another route", () => {
277
+ const currentRoute = createRoute("/users/42?tab=profile")
278
+
279
+ expect(currentRoute.isDistinctByPathname(createRoute("/users/99?tab=profile"))).toBe(true)
280
+ expect(currentRoute.isDistinctByPathname(currentRoute.getRecord())).toBe(false)
281
+ })
282
+
283
+ test("Route.isDistinctBySearch compares search differences against another route", () => {
284
+ const currentRoute = createRoute("/users/42?tab=profile")
285
+
286
+ expect(currentRoute.isDistinctBySearch(createRoute("/users/42?tab=security"))).toBe(true)
287
+ expect(currentRoute.isDistinctBySearch(currentRoute.getRecord())).toBe(false)
288
+ })
289
+
290
+ test("Route.isDistinctByHash compares hash differences against another route", () => {
291
+ const currentRoute = createRoute("/users/42#section=profile")
292
+
293
+ expect(currentRoute.isDistinctByHash(createRoute("/users/42#section=security"))).toBe(true)
294
+ expect(currentRoute.isDistinctByHash(currentRoute.getRecord())).toBe(false)
295
+ })
296
+
297
+ test("Route.isPathnameLooseIncludes checks whether requested pathname segments exist", () => {
298
+ const currentRoute = createRoute("/workspace/projects/alpha")
299
+
300
+ expect(currentRoute.isPathnameLooseIncludes(["workspace", "alpha"])).toBe(true)
301
+ expect(currentRoute.isPathnameLooseIncludes(["workspace", "beta"])).toBe(false)
302
+ })
303
+
304
+ test("Route.isPathnameStrictIncludes compares pathname fragments without delimiter collisions", () => {
305
+ const currentRoute = createRoute("/a,b/c")
306
+
307
+ expect(currentRoute.isPathnameStrictIncludes(["a,b", "c"])).toBe(true)
308
+ expect(currentRoute.isPathnameStrictIncludes(["a", "b,c"])).toBe(false)
309
+ })
310
+
311
+ test("Route.isPathnameLooseEqual compares normalized pathname equality without trailing slash sensitivity", () => {
312
+ const currentRoute = createRoute("/workspace/projects/alpha/")
313
+
314
+ expect(currentRoute.isPathnameLooseEqual("/workspace/projects/alpha")).toBe(true)
315
+ expect(currentRoute.isPathnameLooseEqual("/workspace/projects/beta")).toBe(false)
316
+ })
317
+
318
+ test("Route.isPathnameStrictEqual compares normalized pathname equality with delimiter sensitivity", () => {
319
+ const currentRoute = createRoute("/a,b/c")
320
+
321
+ expect(currentRoute.isPathnameStrictEqual("/a,b/c")).toBe(true)
322
+ expect(currentRoute.isPathnameStrictEqual("/a/b,c")).toBe(false)
323
+ })
324
+
325
+ test("Route.isSearchLooseIncludes checks whether requested query keys exist", () => {
326
+ const currentRoute = createRoute("/users/42?tab=profile&mode=grid")
327
+
328
+ expect(currentRoute.isSearchLooseIncludes(["tab"])).toBe(true)
329
+ expect(currentRoute.isSearchLooseIncludes(["tab", "page"])).toBe(false)
330
+ })
331
+
332
+ test("Route.isSearchStrictIncludes compares query key sets without concatenation collisions", () => {
333
+ const currentRoute = createRoute("/demo?ab=1&c=2")
334
+
335
+ expect(currentRoute.isSearchStrictIncludes(["ab", "c"])).toBe(true)
336
+ expect(currentRoute.isSearchStrictIncludes(["a", "bc"])).toBe(false)
337
+ })
338
+
339
+ test("Route.isSearchIncludes supports required and optional query keys", () => {
340
+ const currentRoute = createRoute("/users/42?tab=profile&mode=grid")
341
+
342
+ expect(currentRoute.isSearchIncludes({ tab: "required", mode: "optional" })).toBe(true)
343
+ expect(currentRoute.isSearchIncludes({ tab: "required" })).toBe(false)
344
+ })
345
+
346
+ test("Route.isSearchLooseEqual checks whether target query entries are a matching subset", () => {
347
+ const currentRoute = createRoute("/users/42?tab=profile&mode=grid")
348
+
349
+ expect(currentRoute.isSearchLooseEqual({ tab: "profile" })).toBe(true)
350
+ expect(currentRoute.isSearchLooseEqual({ tab: "profile", page: "1" })).toBe(false)
351
+ })
352
+
353
+ test("Route.isSearchStrictEqual checks whether query key sets and values are identical", () => {
354
+ const currentRoute = createRoute("/users/42?tab=profile&mode=grid")
355
+
356
+ expect(currentRoute.isSearchStrictEqual("?mode=grid&tab=profile")).toBe(true)
357
+ expect(currentRoute.isSearchStrictEqual("?tab=profile")).toBe(false)
358
+ })
359
+
360
+ test("Route.isHashLooseIncludes checks whether requested hash keys exist", () => {
361
+ const currentRoute = createRoute("/users/42#section=profile&panel=overview")
362
+
363
+ expect(currentRoute.isHashLooseIncludes(["section"])).toBe(true)
364
+ expect(currentRoute.isHashLooseIncludes(["section", "mode"])).toBe(false)
365
+ })
366
+
367
+ test("Route.isHashStrictIncludes compares hash key sets without concatenation collisions", () => {
368
+ const currentRoute = createRoute("/demo#ab=1&c=2")
369
+
370
+ expect(currentRoute.isHashStrictIncludes(["ab", "c"])).toBe(true)
371
+ expect(currentRoute.isHashStrictIncludes(["a", "bc"])).toBe(false)
372
+ })
373
+
374
+ test("Route.isHashIncludes supports required and optional hash keys", () => {
375
+ const currentRoute = createRoute("/users/42#section=profile&panel=overview")
376
+
377
+ expect(currentRoute.isHashIncludes({ section: "required", panel: "optional" })).toBe(true)
378
+ expect(currentRoute.isHashIncludes({ section: "required" })).toBe(false)
379
+ })
380
+
381
+ test("Route.isHashLooseEqual checks whether target hash entries are a matching subset", () => {
382
+ const currentRoute = createRoute("/users/42#section=profile&panel=overview")
383
+
384
+ expect(currentRoute.isHashLooseEqual({ section: "profile" })).toBe(true)
385
+ expect(currentRoute.isHashLooseEqual({ section: "profile", tab: "files" })).toBe(false)
386
+ })
387
+
388
+ test("Route.isHashStrictEqual checks whether hash key sets and values are identical", () => {
389
+ const currentRoute = createRoute("/users/42#section=profile&panel=overview")
390
+
391
+ expect(currentRoute.isHashStrictEqual("#panel=overview&section=profile")).toBe(true)
392
+ expect(currentRoute.isHashStrictEqual("#section=profile")).toBe(false)
393
+ })
394
+
395
+ test("isRoute distinguishes Route instances from plain values", () => {
396
+ expect(isRoute(createRoute("/users/42"))).toBe(true)
397
+ expect(isRoute({ partialUrl: "/users/42" })).toBe(false)
398
+ })
399
+
400
+ test("createRoute normalizes strings options records and existing route instances", () => {
401
+ const fromString = createRoute("/users/42?tab=profile")
402
+ const fromOptions = createRoute({ partialUrl: "/users/42?tab=profile" })
403
+ const fromRecord = createRoute(fromString.getRecord())
404
+ const reused = createRoute(fromString)
405
+
406
+ expect(fromString.getRecord().partialUrl).toBe("/users/42?tab=profile")
407
+ expect(fromOptions.getRecord().partialUrl).toBe("/users/42?tab=profile")
408
+ expect(fromRecord.getRecord().partialUrl).toBe("/users/42?tab=profile")
409
+ expect(reused).toBe(fromString)
410
+ expect(() => createRoute(42 as never)).toThrow("\"target\" is expected to be type of \"String\" or \"Route\" or \"RouteRecord\" or \"RouteOptions\".")
411
+ })
412
+
413
+ test("emptyRoute creates a root route with empty search hash and payload", () => {
414
+ const currentRoute = emptyRoute()
415
+
416
+ expect(currentRoute.getPayload()).toBeUndefined()
417
+ expect(currentRoute.getRecord()).toEqual({
418
+ partialUrl: "/",
419
+ pathname: "/",
420
+ search: "",
421
+ hash: "",
422
+ pathnameString: "/",
423
+ pathnameArray: ["", ""],
424
+ query: "",
425
+ queryString: "",
426
+ queryObject: {},
427
+ hashString: "",
428
+ hashObject: {},
429
+ })
430
+ })