@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,307 @@
1
+ /**
2
+ * 判断给定值是否为字符串。
3
+ *
4
+ * @example
5
+ * ```
6
+ * // Expect: true
7
+ * const example1 = internalIsString("#page=1")
8
+ *
9
+ * // Expect: false
10
+ * const example2 = internalIsString({ page: "1" })
11
+ * ```
12
+ */
13
+ const internalIsString = (value: unknown): value is string => {
14
+ return typeof value === "string"
15
+ }
16
+
17
+ /**
18
+ * 判断给定值是否为普通对象。
19
+ *
20
+ * @example
21
+ * ```
22
+ * // Expect: true
23
+ * const example1 = internalIsObject({ page: "1" })
24
+ *
25
+ * // Expect: false
26
+ * const example2 = internalIsObject(["page"])
27
+ * ```
28
+ */
29
+ const internalIsObject = (value: unknown): value is Record<string, string> => {
30
+ return typeof value === "object" && value !== null && Array.isArray(value) === false
31
+ }
32
+
33
+ /**
34
+ * 将单个 hash 条目拆分为 key 和 value。
35
+ *
36
+ * @example
37
+ * ```
38
+ * // Expect: ["page", "1"]
39
+ * const example1 = internalSplitHashEntry("page=1")
40
+ *
41
+ * // Expect: ["formula", "a=b"]
42
+ * const example2 = internalSplitHashEntry("formula=a=b")
43
+ * ```
44
+ */
45
+ const internalSplitHashEntry = (hashEntry: string): [key: string, value: string] => {
46
+ const internalEqualSignIndex = hashEntry.indexOf("=")
47
+
48
+ if (internalEqualSignIndex === -1) {
49
+ const key = hashEntry
50
+ const value = ""
51
+ return [key, value]
52
+ }
53
+
54
+ const key = hashEntry.slice(0, internalEqualSignIndex)
55
+ const value = hashEntry.slice(internalEqualSignIndex + 1)
56
+ return [key, value]
57
+ }
58
+
59
+ export type HashStringLoose = string
60
+ /**
61
+ * 带 `#` 前缀的 hash 字符串。
62
+ *
63
+ * @example
64
+ * ```
65
+ * // Expect: "#page=1"
66
+ * const example1: HashString = "#page=1"
67
+ * ```
68
+ */
69
+ export type HashStringStrict = `#${string}`
70
+
71
+ /**
72
+ * hash 参数对象。
73
+ *
74
+ * @example
75
+ * ```
76
+ * // Expect: { page: "1" }
77
+ * const example1: HashObject = { page: "1" }
78
+ * ```
79
+ */
80
+ export type HashObject = Record<string, string>
81
+
82
+ /**
83
+ * hash 支持的输入或输出形态。
84
+ *
85
+ * @example
86
+ * ```
87
+ * // Expect: "#page=1"
88
+ * const example1: HashUnion = "#page=1"
89
+ *
90
+ * // Expect: { page: "1" }
91
+ * const example2: HashUnion = { page: "1" }
92
+ * ```
93
+ */
94
+ export type HashUnion = HashStringLoose | HashStringStrict | HashObject
95
+
96
+ /**
97
+ * 规范化 hash 字符串,确保其带有 `#` 前缀。
98
+ *
99
+ * @example
100
+ * ```
101
+ * // Expect: "#name=cigaret"
102
+ * const example1 = neatenHash("#name=cigaret")
103
+ *
104
+ * // Expect: "#name=cigaret"
105
+ * const example2 = neatenHash("name=cigaret")
106
+ * ```
107
+ */
108
+ export const neatenHash = (target: string): HashStringStrict => {
109
+ return target.startsWith("#") ? (target as HashStringStrict) : `#${target}`
110
+ }
111
+
112
+ /**
113
+ * 将 hash 字符串解析为 hash 参数对象。
114
+ *
115
+ * @example
116
+ * ```
117
+ * // Expect: { name: "cigaret" }
118
+ * const example1 = hashStringToHashObject("#name=cigaret")
119
+ *
120
+ * // Expect: { formula: "a=b" }
121
+ * const example2 = hashStringToHashObject("#formula=a%3Db")
122
+ * ```
123
+ */
124
+ export const hashStringToHashObject = (target: HashStringLoose): HashObject => {
125
+ const hashString = neatenHash(target)
126
+ const hashEntries = hashString.slice(1).split("&")
127
+ const hashObject: HashObject = {}
128
+
129
+ hashEntries.forEach((hashEntry) => {
130
+ if (hashEntry !== "") {
131
+ const [rawKey, rawValue] = internalSplitHashEntry(hashEntry)
132
+ hashObject[decodeURIComponent(rawKey)] = decodeURIComponent(rawValue)
133
+ }
134
+ })
135
+
136
+ return hashObject
137
+ }
138
+
139
+ /**
140
+ * 将 hash 参数对象序列化为 hash 字符串。
141
+ *
142
+ * @example
143
+ * ```
144
+ * // Expect: "#name=cigaret"
145
+ * const example1 = hashObjectToHashString({ name: "cigaret" })
146
+ *
147
+ * // Expect: "#formula=a%3Db"
148
+ * const example2 = hashObjectToHashString({ formula: "a=b" })
149
+ * ```
150
+ */
151
+ export const hashObjectToHashString = (target: HashObject): HashStringStrict => {
152
+ const hashString = Object.entries(target)
153
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
154
+ .join("&")
155
+
156
+ return neatenHash(hashString)
157
+ }
158
+
159
+ /**
160
+ * 将 hash 统一转换为 hash 字符串。
161
+ *
162
+ * @example
163
+ * ```
164
+ * // Expect: "#page=1"
165
+ * const example1 = toHashString("#page=1")
166
+ *
167
+ * // Expect: "#page=1"
168
+ * const example2 = toHashString({ page: "1" })
169
+ * ```
170
+ */
171
+ export const toHashString = (target: HashUnion): HashStringStrict => {
172
+ if (internalIsString(target)) {
173
+ return neatenHash(target)
174
+ }
175
+ else if (internalIsObject(target)) {
176
+ return hashObjectToHashString(target)
177
+ }
178
+ else {
179
+ throw (new TypeError("\"target\" is expected to be type of \"string\" | \"object\"."))
180
+ }
181
+ }
182
+
183
+ /**
184
+ * 将 hash 统一转换为 hash 参数对象。
185
+ *
186
+ * @example
187
+ * ```
188
+ * // Expect: { page: "1" }
189
+ * const example1 = toHashObject("#page=1")
190
+ *
191
+ * // Expect: { page: "1" }
192
+ * const example2 = toHashObject({ page: "1" })
193
+ * ```
194
+ */
195
+ export const toHashObject = (target: HashUnion): HashObject => {
196
+ if (internalIsString(target)) {
197
+ return hashStringToHashObject(target)
198
+ }
199
+ else if (internalIsObject(target)) {
200
+ return target
201
+ }
202
+ else {
203
+ throw (new TypeError("\"target\" is expected to be type of \"string\" | \"object\"."))
204
+ }
205
+ }
206
+
207
+ /**
208
+ * 宽松包含关系:hash 参数的键集合是目标 hash 参数键集合的子集。
209
+ *
210
+ * @example
211
+ * ```
212
+ * // Expect: true
213
+ * const example1 = isHashLooseIncludes("#a=1&b=2", ["a"])
214
+ *
215
+ * // Expect: false
216
+ * const example2 = isHashLooseIncludes("#a=1&b=2", ["a", "c"])
217
+ * ```
218
+ */
219
+ export const isHashLooseIncludes = (hash: HashUnion, keys: string | string[]): boolean => {
220
+ const existKeys = Object.keys(toHashObject(hash))
221
+ const targetKeys = internalIsString(keys) ? [keys] : keys
222
+ return targetKeys.every(targetKey => existKeys.includes(targetKey))
223
+ }
224
+
225
+ /**
226
+ * 严格包含关系:hash 参数的键集合与目标 hash 参数键集合完全相同。
227
+ *
228
+ * @example
229
+ * ```
230
+ * // Expect: true
231
+ * const example1 = isHashStrictIncludes("#a=1&b=2", ["a", "b"])
232
+ *
233
+ * // Expect: false
234
+ * const example2 = isHashStrictIncludes("#a=1&b=2", ["a"])
235
+ * ```
236
+ */
237
+ export const isHashStrictIncludes = (hash: HashUnion, keys: string | string[]): boolean => {
238
+ const existKeys = Object.keys(toHashObject(hash)).toSorted()
239
+ const targetKeys = (internalIsString(keys) ? [keys] : keys).toSorted()
240
+
241
+ if (existKeys.length !== targetKeys.length) {
242
+ return false
243
+ }
244
+
245
+ return existKeys.every((key, index) => key === targetKeys[index])
246
+ }
247
+
248
+ /**
249
+ * 包含关系:hash 参数的键集合必须包含目标 hash 参数键集合,并且不能包含除目标 hash 参数键集合之外的其他键。
250
+ *
251
+ * @example
252
+ * ```
253
+ * // Expect: true
254
+ * const example1 = isHashIncludes("#a=1&b=2", { a: "required", b: "optional" })
255
+ *
256
+ * // Expect: false
257
+ * const example2 = isHashIncludes("#a=1&b=2", { a: "required" })
258
+ * ```
259
+ */
260
+ export const isHashIncludes = (hash: HashUnion, keys: Record<string, "required" | "optional">): boolean => {
261
+ const existKeys = Object.keys(toHashObject(hash))
262
+ const requiredKeys = Object.entries(keys).filter(([, value]) => value === "required").map(([key]) => key)
263
+ const optionalKeys = Object.entries(keys).filter(([, value]) => value === "optional").map(([key]) => key)
264
+
265
+ const requiredKeysExist = requiredKeys.every(requiredKey => existKeys.includes(requiredKey))
266
+ const extraKeysExist = existKeys.some(existKey => !requiredKeys.includes(existKey) && !optionalKeys.includes(existKey))
267
+
268
+ return requiredKeysExist && !extraKeysExist
269
+ }
270
+
271
+ /**
272
+ * 宽松相等:hash 参数的键集合是目标 hash 参数键集合的子集,并且对应的值相等。
273
+ *
274
+ * @example
275
+ * ```
276
+ * // Expect: true
277
+ * const example1 = isHashLooseEqual("#a=1&b=2", "#a=1")
278
+ *
279
+ * // Expect: false
280
+ * const example2 = isHashLooseEqual("#a=1&b=2", "#a=1&c=3")
281
+ * ```
282
+ */
283
+ export const isHashLooseEqual = (hashA: HashUnion, hashB: HashUnion): boolean => {
284
+ const hashObjectA = toHashObject(hashA)
285
+ const hashObjectB = toHashObject(hashB)
286
+ return Object.entries(hashObjectB).every(([key, value]) => hashObjectA[key] === value)
287
+ }
288
+
289
+ /**
290
+ * 严格相等:hash 参数的键集合与目标 hash 参数键集合完全相同,并且对应的值相等。
291
+ *
292
+ * @example
293
+ * ```
294
+ * // Expect: true
295
+ * const example1 = isHashStrictEqual("#a=1&b=2", "#b=2&a=1")
296
+ *
297
+ * // Expect: false
298
+ * const example2 = isHashStrictEqual("#a=1&b=2", "#a=1")
299
+ * ```
300
+ */
301
+ export const isHashStrictEqual = (hashA: HashUnion, hashB: HashUnion): boolean => {
302
+ const hashObjectA = toHashObject(hashA)
303
+ const hashObjectB = toHashObject(hashB)
304
+ const lengthEqual = Object.keys(hashObjectA).length === Object.keys(hashObjectB).length
305
+ const valueEqual = Object.entries(hashObjectB).every(([key, value]) => hashObjectA[key] === value)
306
+ return lengthEqual && valueEqual
307
+ }
@@ -0,0 +1,7 @@
1
+ export * from "./pathname.ts"
2
+ export * from "./search.ts"
3
+ export * from "./hash.ts"
4
+
5
+ // @see https://developer.mozilla.org/en-US/docs/Glossary/URI
6
+ // @see https://developer.mozilla.org/zh-CN/docs/Web/API/Location
7
+ // @see https://github.com/medialize/URI
@@ -0,0 +1,376 @@
1
+
2
+ /**
3
+ * 判断给定值是否为字符串。
4
+ *
5
+ * @example
6
+ * ```
7
+ * // Expect: true
8
+ * const example1 = internalIsString("/app/page")
9
+ *
10
+ * // Expect: false
11
+ * const example2 = internalIsString(["app", "page"])
12
+ * ```
13
+ */
14
+ const internalIsString = (value: unknown): value is string => {
15
+ return typeof value === "string"
16
+ }
17
+
18
+ /**
19
+ * 判断给定值是否为数组。
20
+ *
21
+ * @example
22
+ * ```
23
+ * // Expect: true
24
+ * const example1 = internalIsArray(["app", "page"])
25
+ *
26
+ * // Expect: false
27
+ * const example2 = internalIsArray("/app/page")
28
+ * ```
29
+ */
30
+ const internalIsArray = (value: unknown): value is unknown[] => {
31
+ return Array.isArray(value)
32
+ }
33
+
34
+ /**
35
+ * 判断给定值是否为真值。
36
+ *
37
+ * @example
38
+ * ```
39
+ * // Expect: true
40
+ * const example1 = internalIsTruthy("page")
41
+ *
42
+ * // Expect: false
43
+ * const example2 = internalIsTruthy("")
44
+ * ```
45
+ */
46
+ const internalIsTruthy = (value: unknown): boolean => {
47
+ return Boolean(value)
48
+ }
49
+
50
+ /**
51
+ * 以字符串形式表示的 pathname。
52
+ *
53
+ * @example
54
+ * ```
55
+ * // Expect: "/app/page"
56
+ * const example1: PathnameString = "/app/page"
57
+ * ```
58
+ */
59
+ export type PathnameString = string
60
+
61
+ /**
62
+ * 以路径段数组形式表示的 pathname。
63
+ *
64
+ * @example
65
+ * ```
66
+ * // Expect: ["", "app", "page"]
67
+ * const example1: PathnameArray = ["", "app", "page"]
68
+ * ```
69
+ */
70
+ export type PathnameArray = string[]
71
+
72
+ /**
73
+ * pathname 支持的输入或输出形态。
74
+ *
75
+ * @example
76
+ * ```
77
+ * // Expect: "/app/page"
78
+ * const example1: PathnameUnion = "/app/page"
79
+ *
80
+ * // Expect: ["", "app", "page"]
81
+ * const example2: PathnameUnion = ["", "app", "page"]
82
+ * ```
83
+ */
84
+ export type PathnameUnion = PathnameString | PathnameArray
85
+
86
+ /**
87
+ * 移除数组中相邻且重复的字符串。
88
+ *
89
+ * @example
90
+ * ```
91
+ * // Expect: ["", "app", "page", ""]
92
+ * const example1 = removeConsecutiveRepetition(["", "", "app", "app", "page", ""])
93
+ * ```
94
+ */
95
+ export const removeConsecutiveRepetition = (targetStrings: string[]): string[] => {
96
+ return targetStrings.filter((item: string, index: number, array: string[]) => {
97
+ return array[index - 1] !== undefined ? item !== array[index - 1] : true
98
+ })
99
+ }
100
+
101
+ /**
102
+ * 仅对指定目标值移除相邻重复项。
103
+ *
104
+ * @example
105
+ * ```
106
+ * // Expect: ["", "app", "/", "page", "page"]
107
+ * const example1 = removeConsecutiveRepetitionOf(["", "", "app", "/", "/", "page", "page"], ["", "/"])
108
+ * ```
109
+ */
110
+ export const removeConsecutiveRepetitionOf = (target: string[], ofList: string[]): string[] => {
111
+ return target.filter((item: string, index: number, array: string[]) => {
112
+ return (array[index - 1] !== undefined && ofList.includes(item)) ? item !== array[index - 1] : true
113
+ })
114
+ }
115
+
116
+ /**
117
+ * 移除不在例外名单中的相邻重复项。
118
+ *
119
+ * @example
120
+ * ```
121
+ * // Expect: ["", "", "app", "page", "", ""]
122
+ * const example1 = removeConsecutiveRepetitionExcept(["", "", "app", "app", "page", "page", "", ""], [""])
123
+ * ```
124
+ */
125
+ export const removeConsecutiveRepetitionExcept = (target: string[], exceptList: string[]): string[] => {
126
+ return target.filter((item: string, index: number, array: string[]) => {
127
+ return (array[index - 1] !== undefined && !exceptList.includes(item)) ? item !== array[index - 1] : true
128
+ })
129
+ }
130
+
131
+ /**
132
+ * 移除相邻重复的空字符串。
133
+ *
134
+ * @example
135
+ * ```
136
+ * // Expect: ["", "app", "", "page", ""]
137
+ * const example1 = removeConsecutiveRepetitionOfEmpty(["", "", "app", "", "", "page", ""])
138
+ * ```
139
+ */
140
+ export const removeConsecutiveRepetitionOfEmpty = (target: string[]): string[] => {
141
+ return removeConsecutiveRepetitionOf(target, [""])
142
+ }
143
+
144
+ /**
145
+ * 移除相邻重复的斜杠字符。
146
+ *
147
+ * @example
148
+ * ```
149
+ * // Expect: ["/", "a", "/", "b"]
150
+ * const example1 = removeConsecutiveRepetitionOfSlash(["/", "/", "a", "/", "/", "/", "b"])
151
+ * ```
152
+ */
153
+ export const removeConsecutiveRepetitionOfSlash = (target: string[]): string[] => {
154
+ return removeConsecutiveRepetitionOf(target, ["/"])
155
+ }
156
+
157
+ /**
158
+ * 移除路径数组内部的空字符串,但保留首尾位置的空字符串。
159
+ *
160
+ * @example
161
+ * ```
162
+ * // Expect: ["", "app", "page", ""]
163
+ * const example1 = removeInnerEmptyStringInArray(["", "app", "", "page", ""])
164
+ * ```
165
+ */
166
+ export const removeInnerEmptyStringInArray = (target: string[]): string[] => {
167
+ return target.filter((item, index, arr) => (index === 0 || index === arr.length - 1) ? true : item !== "")
168
+ }
169
+
170
+ /**
171
+ * 保证根路径至少保留为 `["", ""]` 的最小结构。
172
+ *
173
+ * @example
174
+ * ```
175
+ * // Expect: ["", ""]
176
+ * const example1 = keepMinimumPathArray([""])
177
+ *
178
+ * // Expect: ["", "app"]
179
+ * const example2 = keepMinimumPathArray(["", "app"])
180
+ * ```
181
+ */
182
+ export const keepMinimumPathArray = (target: string[]): string[] => {
183
+ return target.length === 1 && target[0] === "" ? ["", ""] : target
184
+ }
185
+
186
+ /**
187
+ * 规范化 pathname,使其具备稳定的根前缀和重复分隔符语义。
188
+ *
189
+ * @example
190
+ * ```
191
+ * // Expect: ["", "app", "page", ""]
192
+ * const example1 = neatenPathname(["app", "", "page", ""])
193
+ *
194
+ * // Expect: "/app/page/"
195
+ * const example2 = neatenPathname("//app//page//")
196
+ * ```
197
+ */
198
+ export const neatenPathname = (pathname: PathnameUnion): typeof pathname => {
199
+ if (internalIsArray(pathname)) {
200
+ // 第一步:补上根路径前缀,使数组形式始终从空字符串起始。
201
+ const _0 = ["", ...pathname]
202
+
203
+ // 第二步:折叠连续的空字符串,避免重复分隔段。
204
+ const _1 = removeConsecutiveRepetitionOfEmpty(_0)
205
+
206
+ // 第三步:确保根路径仍至少保留 `['', '']` 的最小表达。
207
+ const _2 = keepMinimumPathArray(_1)
208
+
209
+ // 第四步:移除中间无意义的空路径段,同时保留首尾语义。
210
+ const _3 = removeInnerEmptyStringInArray(_2)
211
+
212
+ return _3
213
+ }
214
+ else if (internalIsString(pathname)) {
215
+ // 第一步:拆成字符数组,便于统一处理连续斜杠。
216
+ // oxlint-disable-next-line typescript/no-misused-spread
217
+ const _0 = [...pathname]
218
+
219
+ // 第二步:补上根路径斜杠,确保字符串形式总是绝对路径语义。
220
+ const _1 = ["/", ..._0]
221
+
222
+ // 第三步:折叠连续斜杠,避免出现多余分隔符。
223
+ const _2 = removeConsecutiveRepetitionOfSlash(_1)
224
+
225
+ // 第四步:回到标准字符串,再按路径段拆分做后续清理。
226
+ const _3 = _2.join("")
227
+ const _4 = _3.split("/")
228
+
229
+ // 第五步:折叠空路径段,并保留根与尾部斜杠语义。
230
+ const _5 = removeConsecutiveRepetitionOfEmpty(_4)
231
+ const _6 = keepMinimumPathArray(_5)
232
+
233
+ // 第六步:重新拼回字符串形式,得到稳定输出。
234
+ const _7 = _6.join("/")
235
+
236
+ return _7
237
+ }
238
+ else {
239
+ throw (new TypeError("\"pathname\" must be string or array."))
240
+ }
241
+ }
242
+
243
+ /**
244
+ * 将 pathname 转换为路径段数组形式。
245
+ *
246
+ * @example
247
+ * ```
248
+ * // Expect: ["", "app", "page"]
249
+ * const example1 = toPathnameArray("app/page")
250
+ *
251
+ * // Expect: ["", "app", "page", ""]
252
+ * const example2 = toPathnameArray(["", "app", "", "page", ""])
253
+ * ```
254
+ */
255
+ export const toPathnameArray = (pathname: PathnameUnion): PathnameArray => {
256
+ const neatedPathname = neatenPathname(pathname)
257
+ if (internalIsArray(neatedPathname)) {
258
+ return neatedPathname
259
+ } if (internalIsString(neatedPathname)) {
260
+ return neatedPathname.split("/")
261
+ }
262
+ else {
263
+ throw (new TypeError("\"pathname\" must be string or array."))
264
+ }
265
+ }
266
+
267
+ /**
268
+ * 将 pathname 转换为标准字符串形式。
269
+ *
270
+ * @example
271
+ * ```
272
+ * // Expect: "/app/page"
273
+ * const example1 = toPathnameString(["app", "page"])
274
+ *
275
+ * // Expect: "/app/page/"
276
+ * const example2 = toPathnameString("//app//page//")
277
+ * ```
278
+ */
279
+ export const toPathnameString = (pathname: PathnameUnion): PathnameString => {
280
+ const neatedPathname = neatenPathname(pathname)
281
+ if (internalIsString(neatedPathname)) {
282
+ return neatedPathname
283
+ } if (internalIsArray(neatedPathname)) {
284
+ return neatedPathname.join("/")
285
+ }
286
+ else {
287
+ throw (new TypeError("\"pathname\" must be string or array."))
288
+ }
289
+ }
290
+
291
+ export const isPathnameLooseIncludes = (pathname: PathnameUnion, pieces: string | string[]): boolean => {
292
+ const pathnameArray = toPathnameArray(pathname)
293
+ const piecesArray = internalIsArray(pieces) ? pieces : [pieces]
294
+ return piecesArray.every(piece => pathnameArray.includes(piece))
295
+ }
296
+
297
+ export const isPathnameStrictIncludes = (pathname: PathnameUnion, pieces: string | string[]): boolean => {
298
+ const pathnameString = toPathnameString(pathname)
299
+ const piecesString = toPathnameString(pieces)
300
+ return pathnameString.includes(piecesString)
301
+ }
302
+
303
+ export const isPathnameIncludes = (mode: "strict" | "loose", pathname: PathnameUnion, pieces: string | string[]): boolean => {
304
+ switch (mode) {
305
+ case "strict": {
306
+ return isPathnameStrictIncludes(pathname, pieces)
307
+ }
308
+ case "loose": {
309
+ return isPathnameLooseIncludes(pathname, pieces)
310
+ }
311
+ default: {
312
+ throw (new TypeError("\"mode\" must be \"strict\" or \"loose\"."))
313
+ }
314
+ }
315
+ }
316
+
317
+ /**
318
+ * 以严格模式比较两个 pathname 是否相等。
319
+ *
320
+ * @example
321
+ * ```
322
+ * // Expect: true
323
+ * const example1 = isPathnameStrictEqual("/app/page", "/app/page")
324
+ *
325
+ * // Expect: false
326
+ * const example2 = isPathnameStrictEqual("/app/page", "/app/page/")
327
+ * ```
328
+ */
329
+ export const isPathnameStrictEqual = (pathname1: PathnameUnion, pathname2: PathnameUnion): boolean => {
330
+ return toPathnameString(pathname1) === toPathnameString(pathname2)
331
+ }
332
+
333
+ /**
334
+ * 以宽松模式比较两个 pathname 是否相等,并忽略尾部斜杠差异。
335
+ *
336
+ * @example
337
+ * ```
338
+ * // Expect: true
339
+ * const example1 = isPathnameLooseEqual("/app/page", "/app/page")
340
+ *
341
+ * // Expect: true
342
+ * const example2 = isPathnameLooseEqual("/app/page", "/app/page/")
343
+ * ```
344
+ */
345
+ export const isPathnameLooseEqual = (pathname1: PathnameUnion, pathname2: PathnameUnion): boolean => {
346
+ const preCook = (pathname: PathnameUnion): string => {
347
+ return toPathnameArray(pathname).filter(str => internalIsTruthy(str)).join("/")
348
+ }
349
+ return preCook(pathname1) === preCook(pathname2)
350
+ }
351
+
352
+ /**
353
+ * 根据指定模式比较两个 pathname 是否相等。
354
+ *
355
+ * @example
356
+ * ```
357
+ * // Expect: true
358
+ * const example1 = isPathnameEqual("strict", "/app/page", ["", "app", "page"])
359
+ *
360
+ * // Expect: true
361
+ * const example2 = isPathnameEqual("loose", "/app/page", "/app/page/")
362
+ * ```
363
+ */
364
+ export const isPathnameEqual = (mode: "strict" | "loose", pathname1: PathnameUnion, pathname2: PathnameUnion): boolean => {
365
+ switch (mode) {
366
+ case "strict": {
367
+ return isPathnameStrictEqual(pathname1, pathname2)
368
+ }
369
+ case "loose": {
370
+ return isPathnameLooseEqual(pathname1, pathname2)
371
+ }
372
+ default: {
373
+ throw (new TypeError("\"mode\" must be \"strict\" or \"loose\"."))
374
+ }
375
+ }
376
+ }