@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,280 @@
1
+ import type {
2
+ FetchInput,
3
+ FetchOutput,
4
+ BaseFetchOptions,
5
+ FetchOutputJsonData,
6
+ FetchOutputTextData,
7
+ FetchOutputBlobData,
8
+ FetchOutputArrayBufferData,
9
+ FetchOutputStreamData,
10
+ } from "./base.ts"
11
+ import { BaseFetch, } from "./base.ts"
12
+
13
+ const isRecord = (value: unknown): value is Record<string, unknown> => {
14
+ return typeof value === "object" && value !== null
15
+ }
16
+
17
+ const serializeQueryValue = (value: unknown): string => {
18
+ if (value instanceof Date) {
19
+ return value.toISOString()
20
+ }
21
+
22
+ if (typeof value === "bigint") {
23
+ return value.toString()
24
+ }
25
+
26
+ if (isRecord(value) || Array.isArray(value)) {
27
+ return JSON.stringify(value)
28
+ }
29
+
30
+ return String(value)
31
+ }
32
+
33
+ const appendQuery = (url: URL, query: unknown): void => {
34
+ if (query === undefined || query === null) {
35
+ return
36
+ }
37
+
38
+ if (query instanceof URLSearchParams) {
39
+ for (const [key, value] of query.entries()) {
40
+ url.searchParams.append(key, value)
41
+ }
42
+ return
43
+ }
44
+
45
+ if (isRecord(query) === false) {
46
+ throw new TypeError("BrowserFetch query must be a record or URLSearchParams.")
47
+ }
48
+
49
+ for (const [key, value] of Object.entries(query)) {
50
+ if (value === undefined) {
51
+ continue
52
+ }
53
+
54
+ if (Array.isArray(value)) {
55
+ for (const item of value) {
56
+ url.searchParams.append(key, serializeQueryValue(item))
57
+ }
58
+ continue
59
+ }
60
+
61
+ url.searchParams.append(key, serializeQueryValue(value))
62
+ }
63
+ }
64
+
65
+ const isBodyInit = (value: unknown): value is BodyInit => {
66
+ if (typeof value === "string") {
67
+ return true
68
+ }
69
+
70
+ if (
71
+ (typeof Blob !== "undefined" && value instanceof Blob)
72
+ || value instanceof ArrayBuffer
73
+ || value instanceof URLSearchParams
74
+ || (typeof FormData !== "undefined" && value instanceof FormData)
75
+ ) {
76
+ return true
77
+ }
78
+
79
+ if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
80
+ return true
81
+ }
82
+
83
+ const isArrayBufferView = ArrayBuffer.isView(value)
84
+
85
+ return isArrayBufferView === true
86
+ }
87
+
88
+ const buildHeaders = (headers: Record<string, string> | undefined, body: unknown): Headers => {
89
+ const output = new Headers(headers)
90
+
91
+ if (body !== undefined && body !== null && isBodyInit(body) === false && output.has("Content-Type") === false) {
92
+ output.set("Content-Type", "application/json")
93
+ }
94
+
95
+ return output
96
+ }
97
+
98
+ const buildBody = (body: unknown): BodyInit | undefined => {
99
+ if (body === undefined || body === null) {
100
+ return undefined
101
+ }
102
+
103
+ if (isBodyInit(body) === true) {
104
+ return body
105
+ }
106
+
107
+ return JSON.stringify(body)
108
+ }
109
+
110
+ const buildRequestAbortSignal = (
111
+ timeoutSignal: AbortSignal,
112
+ abortSignal: AbortSignal | undefined,
113
+ ): { signal: AbortSignal; cleanup: () => void } => {
114
+ if (abortSignal === undefined) {
115
+ return {
116
+ signal: timeoutSignal,
117
+ cleanup: () => {
118
+ // no cleanup needed when abortSignal is not provided
119
+ }
120
+ }
121
+ }
122
+
123
+ const requestAbortController = new AbortController()
124
+ const cleanupList: Array<() => void> = []
125
+
126
+ const bindAbortSignal = (signal: AbortSignal): void => {
127
+ if (signal.aborted === true) {
128
+ requestAbortController.abort(signal.reason)
129
+ return
130
+ }
131
+
132
+ const onAbort = (): void => {
133
+ requestAbortController.abort(signal.reason)
134
+ }
135
+ signal.addEventListener("abort", onAbort, { once: true })
136
+ cleanupList.push(() => {
137
+ signal.removeEventListener("abort", onAbort)
138
+ })
139
+ }
140
+
141
+ bindAbortSignal(timeoutSignal)
142
+ bindAbortSignal(abortSignal)
143
+
144
+ return {
145
+ signal: requestAbortController.signal,
146
+ cleanup: () => {
147
+ for (const cleanup of cleanupList) {
148
+ cleanup()
149
+ }
150
+ }
151
+ }
152
+ }
153
+
154
+ /**
155
+ * 描述创建浏览器 fetch 执行器时可提供的选项。
156
+ */
157
+ export interface BrowserFetchOptions<
158
+ Input extends FetchInput = FetchInput,
159
+ Output extends FetchOutput = FetchOutput
160
+ > extends BaseFetchOptions<Input, Output> {
161
+ }
162
+
163
+ /**
164
+ * 基于浏览器原生 fetch 的请求执行器。
165
+ */
166
+ export class BrowserFetch<
167
+ Input extends FetchInput,
168
+ Output extends FetchOutput
169
+ > extends BaseFetch<Input, Output> {
170
+ private responsePromise: Promise<Response> | undefined
171
+
172
+ constructor(options: BrowserFetchOptions<Input, Output>) {
173
+ super(options)
174
+ }
175
+
176
+ private async execute(): Promise<Response> {
177
+ const { baseUrl, path, method, headers, query, body, timeout, abortSignal } = this.options
178
+
179
+ const url = new URL(path, baseUrl)
180
+ appendQuery(url, query)
181
+
182
+ const requestHeaders = buildHeaders(headers, body)
183
+ const requestBody = buildBody(body)
184
+ const timeoutAbortController = new AbortController()
185
+ const { signal: requestAbortSignal, cleanup } = buildRequestAbortSignal(timeoutAbortController.signal, abortSignal)
186
+ const requestInit: RequestInit = {
187
+ method: method.toUpperCase(),
188
+ headers: requestHeaders,
189
+ signal: requestAbortSignal,
190
+ }
191
+
192
+ if (requestBody !== undefined) {
193
+ requestInit.body = requestBody
194
+ }
195
+
196
+ let timeoutId: ReturnType<typeof setTimeout> | undefined
197
+ if (timeout !== undefined && timeout > 0) {
198
+ timeoutId = setTimeout(() => {
199
+ timeoutAbortController.abort(new Error(`Request timeout after ${timeout}ms.`))
200
+ }, timeout)
201
+ }
202
+
203
+ try {
204
+ return await fetch(url.toString(), requestInit)
205
+ }
206
+ catch (exception) {
207
+ if (requestAbortSignal.aborted === true) {
208
+ throw requestAbortSignal.reason
209
+ }
210
+ throw exception
211
+ }
212
+ finally {
213
+ cleanup()
214
+ if (timeoutId !== undefined) {
215
+ clearTimeout(timeoutId)
216
+ }
217
+ }
218
+ }
219
+
220
+ private async getResponse(): Promise<Response> {
221
+ if (this.responsePromise === undefined) {
222
+ this.responsePromise = this.execute()
223
+ }
224
+ return await this.responsePromise
225
+ }
226
+
227
+ /**
228
+ * 以 JSON 形式读取浏览器响应。
229
+ */
230
+ async getJson(): Promise<FetchOutputJsonData<Output>> {
231
+ const response = await this.getResponse()
232
+ return await response.json() as FetchOutputJsonData<Output>
233
+ }
234
+
235
+ /**
236
+ * 以文本形式读取浏览器响应。
237
+ */
238
+ async getText(): Promise<FetchOutputTextData<Output>> {
239
+ const response = await this.getResponse()
240
+ return await response.text() as FetchOutputTextData<Output>
241
+ }
242
+
243
+ /**
244
+ * 以 Blob 形式读取浏览器响应。
245
+ */
246
+ async getBlob(): Promise<FetchOutputBlobData<Output>> {
247
+ const response = await this.getResponse()
248
+ return await response.blob() as FetchOutputBlobData<Output>
249
+ }
250
+
251
+ /**
252
+ * 以 ArrayBuffer 形式读取浏览器响应。
253
+ */
254
+ async getArrayBuffer(): Promise<FetchOutputArrayBufferData<Output>> {
255
+ const response = await this.getResponse()
256
+ return await response.arrayBuffer() as FetchOutputArrayBufferData<Output>
257
+ }
258
+
259
+ /**
260
+ * 以流形式读取浏览器响应。
261
+ */
262
+ async getStream(): Promise<FetchOutputStreamData<Output>> {
263
+ const response = await this.getResponse()
264
+
265
+ if (response.body === null) {
266
+ throw new Error("Response body stream is unavailable.")
267
+ }
268
+
269
+ return response.body as FetchOutputStreamData<Output>
270
+ }
271
+ }
272
+
273
+ /**
274
+ * 创建浏览器环境下的 fetch 执行器。
275
+ */
276
+ export const browserFetch = <Input extends FetchInput, Output extends FetchOutput>(
277
+ options: BrowserFetchOptions<Input, Output>
278
+ ): BrowserFetch<Input, Output> => {
279
+ return new BrowserFetch(options)
280
+ }
@@ -0,0 +1,20 @@
1
+ import { useRuntimes } from "#Source/environment/index.ts"
2
+
3
+ import type {
4
+ BaseFetch, FetchInput, FetchOutput, BaseFetchOptions
5
+ } from './base.ts'
6
+ import { browserFetch } from "./browser.ts"
7
+ import { nodejsFetch } from "./nodejs.ts"
8
+
9
+ /**
10
+ * 根据当前运行时选择浏览器或 Node.js 的 fetch 执行器。
11
+ */
12
+ export const generalFetch = <Input extends FetchInput, Output extends FetchOutput>(
13
+ options: BaseFetchOptions<Input, Output>
14
+ ): BaseFetch<Input, Output> => {
15
+ const runtimeFetch = useRuntimes<BaseFetch<Input, Output>>({
16
+ browser: () => browserFetch(options),
17
+ nodejs: () => nodejsFetch(options),
18
+ })
19
+ return runtimeFetch
20
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./base.ts"
2
+ export * from "./browser.ts"
3
+ export * from "./nodejs.ts"
4
+ export * from "./general.ts"
@@ -0,0 +1,280 @@
1
+ import { fetch, Headers } from "undici"
2
+ import type { BodyInit, RequestInit, Response } from "undici"
3
+
4
+ import type {
5
+ FetchInput,
6
+ FetchOutput,
7
+ BaseFetchOptions,
8
+ FetchOutputArrayBufferData,
9
+ FetchOutputBlobData,
10
+ FetchOutputJsonData,
11
+ FetchOutputStreamData,
12
+ FetchOutputTextData,
13
+ } from "./base.ts"
14
+ import { BaseFetch } from "./base.ts"
15
+
16
+ const isRecord = (value: unknown): value is Record<string, unknown> => {
17
+ return typeof value === "object" && value !== null
18
+ }
19
+
20
+ const serializeQueryValue = (value: unknown): string => {
21
+ if (value instanceof Date) {
22
+ return value.toISOString()
23
+ }
24
+
25
+ if (typeof value === "bigint") {
26
+ return value.toString()
27
+ }
28
+
29
+ if (isRecord(value) || Array.isArray(value)) {
30
+ return JSON.stringify(value)
31
+ }
32
+
33
+ return String(value)
34
+ }
35
+
36
+ const appendQuery = (url: URL, query: unknown): void => {
37
+ if (query === undefined || query === null) {
38
+ return
39
+ }
40
+
41
+ if (query instanceof URLSearchParams) {
42
+ for (const [key, value] of query.entries()) {
43
+ url.searchParams.append(key, value)
44
+ }
45
+ return
46
+ }
47
+
48
+ if (isRecord(query) === false) {
49
+ throw new TypeError("NodejsFetch query must be a record or URLSearchParams.")
50
+ }
51
+
52
+ for (const [key, value] of Object.entries(query)) {
53
+ if (value === undefined) {
54
+ continue
55
+ }
56
+
57
+ if (Array.isArray(value)) {
58
+ for (const item of value) {
59
+ url.searchParams.append(key, serializeQueryValue(item))
60
+ }
61
+ continue
62
+ }
63
+
64
+ url.searchParams.append(key, serializeQueryValue(value))
65
+ }
66
+ }
67
+
68
+ const isBodyInit = (value: unknown): value is BodyInit => {
69
+ if (typeof value === "string") {
70
+ return true
71
+ }
72
+
73
+ if (
74
+ (typeof Blob !== "undefined" && value instanceof Blob)
75
+ || value instanceof ArrayBuffer
76
+ || value instanceof URLSearchParams
77
+ || (typeof FormData !== "undefined" && value instanceof FormData)
78
+ ) {
79
+ return true
80
+ }
81
+
82
+ if (typeof ReadableStream !== "undefined" && value instanceof ReadableStream) {
83
+ return true
84
+ }
85
+
86
+ const isArrayBufferView = ArrayBuffer.isView(value)
87
+
88
+ return isArrayBufferView === true
89
+ }
90
+
91
+ const buildHeaders = (headers: Record<string, string> | undefined, body: unknown): Headers => {
92
+ const output = new Headers(headers)
93
+
94
+ if (body !== undefined && body !== null && isBodyInit(body) === false && output.has("Content-Type") === false) {
95
+ output.set("Content-Type", "application/json")
96
+ }
97
+
98
+ return output
99
+ }
100
+
101
+ const buildBody = (body: unknown): BodyInit | undefined => {
102
+ if (body === undefined || body === null) {
103
+ return undefined
104
+ }
105
+
106
+ if (isBodyInit(body) === true) {
107
+ return body
108
+ }
109
+
110
+ return JSON.stringify(body)
111
+ }
112
+
113
+ const buildRequestAbortSignal = (
114
+ timeoutSignal: AbortSignal,
115
+ abortSignal: AbortSignal | undefined,
116
+ ): { signal: AbortSignal; cleanup: () => void } => {
117
+ if (abortSignal === undefined) {
118
+ return {
119
+ signal: timeoutSignal,
120
+ cleanup: () => {
121
+ // no cleanup needed when abortSignal is not provided
122
+ }
123
+ }
124
+ }
125
+
126
+ const requestAbortController = new AbortController()
127
+ const cleanupList: Array<() => void> = []
128
+
129
+ const bindAbortSignal = (signal: AbortSignal): void => {
130
+ if (signal.aborted === true) {
131
+ requestAbortController.abort(signal.reason)
132
+ return
133
+ }
134
+
135
+ const onAbort = (): void => {
136
+ requestAbortController.abort(signal.reason)
137
+ }
138
+ signal.addEventListener("abort", onAbort, { once: true })
139
+ cleanupList.push(() => {
140
+ signal.removeEventListener("abort", onAbort)
141
+ })
142
+ }
143
+
144
+ bindAbortSignal(timeoutSignal)
145
+ bindAbortSignal(abortSignal)
146
+
147
+ return {
148
+ signal: requestAbortController.signal,
149
+ cleanup: () => {
150
+ for (const cleanup of cleanupList) {
151
+ cleanup()
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ /**
158
+ * 描述创建 Node.js fetch 执行器时可提供的选项。
159
+ */
160
+ export interface NodejsFetchOptions<
161
+ Input extends FetchInput = FetchInput,
162
+ Output extends FetchOutput = FetchOutput
163
+ > extends BaseFetchOptions<Input, Output> {
164
+ }
165
+
166
+ /**
167
+ * 基于 Undici fetch 的 Node.js 请求执行器。
168
+ */
169
+ export class NodejsFetch<
170
+ Input extends FetchInput,
171
+ Output extends FetchOutput
172
+ > extends BaseFetch<Input, Output> {
173
+ private responsePromise: Promise<Response> | undefined
174
+
175
+ private async execute(): Promise<Response> {
176
+ const { baseUrl, path, method, headers, query, body, timeout, abortSignal } = this.options
177
+
178
+ const url = new URL(path, baseUrl)
179
+ appendQuery(url, query)
180
+
181
+ const requestHeaders = buildHeaders(headers, body)
182
+ const requestBody = buildBody(body)
183
+ const timeoutAbortController = new AbortController()
184
+ const { signal: requestAbortSignal, cleanup } = buildRequestAbortSignal(timeoutAbortController.signal, abortSignal)
185
+ const requestInit: RequestInit = {
186
+ method: method.toUpperCase(),
187
+ headers: requestHeaders,
188
+ signal: requestAbortSignal,
189
+ }
190
+
191
+ if (requestBody !== undefined) {
192
+ requestInit.body = requestBody
193
+ }
194
+
195
+ let timeoutId: ReturnType<typeof setTimeout> | undefined
196
+ if (timeout !== undefined && timeout > 0) {
197
+ timeoutId = setTimeout(() => {
198
+ timeoutAbortController.abort(new Error(`Request timeout after ${timeout}ms.`))
199
+ }, timeout)
200
+ }
201
+
202
+ try {
203
+ return await fetch(url.toString(), requestInit)
204
+ }
205
+ catch (exception) {
206
+ if (requestAbortSignal.aborted === true) {
207
+ throw requestAbortSignal.reason
208
+ }
209
+ throw exception
210
+ }
211
+ finally {
212
+ cleanup()
213
+ if (timeoutId !== undefined) {
214
+ clearTimeout(timeoutId)
215
+ }
216
+ }
217
+ }
218
+
219
+ private async getResponse(): Promise<Response> {
220
+ if (this.responsePromise === undefined) {
221
+ this.responsePromise = this.execute()
222
+ }
223
+
224
+ return await this.responsePromise
225
+ }
226
+
227
+ /**
228
+ * 以 JSON 形式读取 Node.js 响应。
229
+ */
230
+ async getJson(): Promise<FetchOutputJsonData<Output>> {
231
+ const response = await this.getResponse()
232
+ return await response.json() as FetchOutputJsonData<Output>
233
+ }
234
+
235
+ /**
236
+ * 以文本形式读取 Node.js 响应。
237
+ */
238
+ async getText(): Promise<FetchOutputTextData<Output>> {
239
+ const response = await this.getResponse()
240
+ return await response.text() as FetchOutputTextData<Output>
241
+ }
242
+
243
+ /**
244
+ * 以 Blob 形式读取 Node.js 响应。
245
+ */
246
+ async getBlob(): Promise<FetchOutputBlobData<Output>> {
247
+ const response = await this.getResponse()
248
+ return await response.blob() as FetchOutputBlobData<Output>
249
+ }
250
+
251
+ /**
252
+ * 以 ArrayBuffer 形式读取 Node.js 响应。
253
+ */
254
+ async getArrayBuffer(): Promise<FetchOutputArrayBufferData<Output>> {
255
+ const response = await this.getResponse()
256
+ return await response.arrayBuffer() as FetchOutputArrayBufferData<Output>
257
+ }
258
+
259
+ /**
260
+ * 以流形式读取 Node.js 响应。
261
+ */
262
+ async getStream(): Promise<FetchOutputStreamData<Output>> {
263
+ const response = await this.getResponse()
264
+
265
+ if (response.body === null) {
266
+ throw new Error("Response body stream is unavailable.")
267
+ }
268
+
269
+ return response.body as FetchOutputStreamData<Output>
270
+ }
271
+ }
272
+
273
+ /**
274
+ * 创建 Node.js 环境下的 fetch 执行器。
275
+ */
276
+ export const nodejsFetch = <Input extends FetchInput, Output extends FetchOutput>(
277
+ options: NodejsFetchOptions<Input, Output>
278
+ ): NodejsFetch<Input, Output> => {
279
+ return new NodejsFetch(options)
280
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./fetch/index.ts"
2
+ export * from "./request/index.ts"