@planet-matrix/mobius-model 0.6.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/dist/index.js +706 -36
- package/dist/index.js.map +855 -59
- package/package.json +28 -16
- package/src/ai/README.md +1 -0
- package/src/ai/ai.ts +107 -0
- package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
- package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
- package/src/ai/chat-completion-ai/index.ts +7 -0
- package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
- package/src/ai/embedding-ai/embedding-ai.ts +63 -0
- package/src/ai/embedding-ai/embedding.ts +50 -0
- package/src/ai/embedding-ai/index.ts +4 -0
- package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
- package/src/ai/index.ts +4 -0
- package/src/aio/README.md +100 -0
- package/src/aio/content.ts +141 -0
- package/src/aio/index.ts +3 -0
- package/src/aio/json.ts +127 -0
- package/src/aio/prompt.ts +246 -0
- package/src/basic/README.md +20 -15
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +2 -2
- package/src/basic/index.ts +1 -0
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- package/src/credential/README.md +107 -0
- package/src/credential/api-key.ts +158 -0
- package/src/credential/bearer.ts +73 -0
- package/src/credential/index.ts +4 -0
- package/src/credential/json-web-token.ts +96 -0
- package/src/credential/password.ts +170 -0
- package/src/cron/README.md +86 -0
- package/src/cron/cron.ts +87 -0
- package/src/cron/index.ts +1 -0
- package/src/drizzle/README.md +1 -0
- package/src/drizzle/drizzle.ts +1 -0
- package/src/drizzle/helper.ts +47 -0
- package/src/drizzle/index.ts +5 -0
- package/src/drizzle/infer.ts +52 -0
- package/src/drizzle/kysely.ts +8 -0
- package/src/drizzle/pagination.ts +200 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/event/class-event-proxy.ts +6 -5
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +6 -5
- package/src/event/internal.ts +4 -4
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +874 -0
- package/src/form/inputor-controller/boolean.ts +39 -0
- package/src/form/inputor-controller/file.ts +39 -0
- package/src/form/inputor-controller/form.ts +181 -0
- package/src/form/inputor-controller/helper.ts +117 -0
- package/src/form/inputor-controller/index.ts +17 -0
- package/src/form/inputor-controller/multi-select.ts +99 -0
- package/src/form/inputor-controller/number.ts +116 -0
- package/src/form/inputor-controller/select.ts +109 -0
- package/src/form/inputor-controller/text.ts +82 -0
- package/src/http/READMD.md +1 -0
- package/src/http/api/api-core.ts +84 -0
- package/src/http/api/api-handler.ts +79 -0
- package/src/http/api/api-host.ts +47 -0
- package/src/http/api/api-result.ts +56 -0
- package/src/http/api/api-schema.ts +154 -0
- package/src/http/api/api-server.ts +130 -0
- package/src/http/api/api-test.ts +142 -0
- package/src/http/api/api-type.ts +37 -0
- package/src/http/api/api.ts +81 -0
- package/src/http/api/index.ts +11 -0
- package/src/http/api-adapter/api-core-node-http.ts +260 -0
- package/src/http/api-adapter/api-host-node-http.ts +156 -0
- package/src/http/api-adapter/api-result-arktype.ts +297 -0
- package/src/http/api-adapter/api-result-zod.ts +286 -0
- package/src/http/api-adapter/index.ts +5 -0
- package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
- package/src/http/bin/gen-api-list/index.ts +1 -0
- package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
- package/src/http/bin/gen-api-test/index.ts +1 -0
- package/src/http/bin/gen-api-type/calc-code.ts +25 -0
- package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
- package/src/http/bin/gen-api-type/index.ts +2 -0
- package/src/http/bin/index.ts +2 -0
- package/src/http/index.ts +3 -0
- package/src/huawei/README.md +1 -0
- package/src/huawei/index.ts +2 -0
- package/src/huawei/moderation/index.ts +1 -0
- package/src/huawei/moderation/moderation.ts +355 -0
- package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
- package/src/huawei/obs/index.ts +1 -0
- package/src/huawei/obs/obs.ts +42 -0
- package/src/index.ts +19 -2
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/logger.ts +15 -4
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +510 -0
- package/src/orchestration/README.md +9 -7
- package/src/orchestration/dispatching/dispatcher.ts +83 -0
- package/src/orchestration/dispatching/index.ts +2 -0
- package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
- package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
- package/src/orchestration/dispatching/selector/index.ts +2 -0
- package/src/orchestration/index.ts +2 -0
- package/src/orchestration/scheduling/index.ts +2 -0
- package/src/orchestration/scheduling/scheduler.ts +103 -0
- package/src/orchestration/scheduling/task.ts +32 -0
- package/src/random/README.md +8 -7
- package/src/random/base.ts +66 -0
- package/src/random/index.ts +5 -1
- package/src/random/random-boolean.ts +40 -0
- package/src/random/random-integer.ts +60 -0
- package/src/random/random-number.ts +72 -0
- package/src/random/random-string.ts +66 -0
- package/src/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +285 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +285 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +250 -0
- package/src/request/request/general.ts +64 -0
- package/src/request/request/index.ts +3 -0
- package/src/request/request/resource.ts +68 -0
- package/src/result/README.md +4 -0
- package/src/result/controller.ts +54 -0
- package/src/result/either.ts +193 -0
- package/src/result/index.ts +2 -0
- package/src/route/README.md +105 -0
- package/src/route/adapter/browser.ts +122 -0
- package/src/route/adapter/driver.ts +56 -0
- package/src/route/adapter/index.ts +2 -0
- package/src/route/index.ts +3 -0
- package/src/route/router/index.ts +2 -0
- package/src/route/router/route.ts +630 -0
- package/src/route/router/router.ts +1642 -0
- package/src/route/uri/hash.ts +308 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +413 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +660 -0
- package/src/socket/client/socket.ts +203 -0
- package/src/socket/common/index.ts +2 -0
- package/src/socket/common/socket-unit-common.ts +23 -0
- package/src/socket/common/socket-unit-heartbeat.ts +427 -0
- package/src/socket/index.ts +3 -0
- package/src/socket/server/index.ts +3 -0
- package/src/socket/server/server.ts +183 -0
- package/src/socket/server/socket-unit.ts +449 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/table.ts +3 -3
- package/src/timer/expiration/expiration-manager.ts +3 -3
- package/src/timer/expiration/remaining-manager.ts +3 -3
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +138 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +159 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +134 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +133 -0
- package/tests/unit/ai/ai.spec.ts +85 -0
- package/tests/unit/aio/content.spec.ts +105 -0
- package/tests/unit/aio/json.spec.ts +147 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +90 -37
- package/tests/unit/credential/api-key.spec.ts +37 -0
- package/tests/unit/credential/bearer.spec.ts +23 -0
- package/tests/unit/credential/json-web-token.spec.ts +23 -0
- package/tests/unit/credential/password.spec.ts +41 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/event/class-event-proxy.spec.ts +3 -3
- package/tests/unit/event/event-manager.spec.ts +3 -3
- package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
- package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
- package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
- package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
- package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
- package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
- package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
- package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
- package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
- package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
- package/tests/unit/http/api/api-core-host.spec.ts +207 -0
- package/tests/unit/http/api/api-schema.spec.ts +120 -0
- package/tests/unit/http/api/api-server.spec.ts +363 -0
- package/tests/unit/http/api/api-test.spec.ts +117 -0
- package/tests/unit/http/api/api.spec.ts +121 -0
- package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
- package/tests/unit/json/repair.spec.ts +11 -0
- package/tests/unit/log/logger.spec.ts +19 -4
- package/tests/unit/openai/openai.spec.ts +64 -0
- package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
- package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
- package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
- package/tests/unit/random/base.spec.ts +58 -0
- package/tests/unit/random/random-boolean.spec.ts +25 -0
- package/tests/unit/random/random-integer.spec.ts +32 -0
- package/tests/unit/random/random-number.spec.ts +33 -0
- package/tests/unit/random/random-string.spec.ts +22 -0
- package/tests/unit/request/fetch/browser.spec.ts +222 -0
- package/tests/unit/request/fetch/general.spec.ts +43 -0
- package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
- package/tests/unit/request/request/base.spec.ts +385 -0
- package/tests/unit/request/request/general.spec.ts +161 -0
- package/tests/unit/route/router/route.spec.ts +431 -0
- package/tests/unit/route/router/router.spec.ts +407 -0
- package/tests/unit/route/uri/hash.spec.ts +72 -0
- package/tests/unit/route/uri/pathname.spec.ts +147 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +135 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -0,0 +1,285 @@
|
|
|
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
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
233
|
+
return await response.json() as FetchOutputJsonData<Output>
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 以文本形式读取浏览器响应。
|
|
238
|
+
*/
|
|
239
|
+
async getText(): Promise<FetchOutputTextData<Output>> {
|
|
240
|
+
const response = await this.getResponse()
|
|
241
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
242
|
+
return await response.text() as FetchOutputTextData<Output>
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 以 Blob 形式读取浏览器响应。
|
|
247
|
+
*/
|
|
248
|
+
async getBlob(): Promise<FetchOutputBlobData<Output>> {
|
|
249
|
+
const response = await this.getResponse()
|
|
250
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
251
|
+
return await response.blob() as FetchOutputBlobData<Output>
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 以 ArrayBuffer 形式读取浏览器响应。
|
|
256
|
+
*/
|
|
257
|
+
async getArrayBuffer(): Promise<FetchOutputArrayBufferData<Output>> {
|
|
258
|
+
const response = await this.getResponse()
|
|
259
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
260
|
+
return await response.arrayBuffer() as FetchOutputArrayBufferData<Output>
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 以流形式读取浏览器响应。
|
|
265
|
+
*/
|
|
266
|
+
async getStream(): Promise<FetchOutputStreamData<Output>> {
|
|
267
|
+
const response = await this.getResponse()
|
|
268
|
+
|
|
269
|
+
if (response.body === null) {
|
|
270
|
+
throw new Error("Response body stream is unavailable.")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
274
|
+
return response.body as FetchOutputStreamData<Output>
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 创建浏览器环境下的 fetch 执行器。
|
|
280
|
+
*/
|
|
281
|
+
export const browserFetch = <Input extends FetchInput, Output extends FetchOutput>(
|
|
282
|
+
options: BrowserFetchOptions<Input, Output>
|
|
283
|
+
): BrowserFetch<Input, Output> => {
|
|
284
|
+
return new BrowserFetch(options)
|
|
285
|
+
}
|
|
@@ -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,285 @@
|
|
|
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
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
233
|
+
return await response.json() as FetchOutputJsonData<Output>
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 以文本形式读取 Node.js 响应。
|
|
238
|
+
*/
|
|
239
|
+
async getText(): Promise<FetchOutputTextData<Output>> {
|
|
240
|
+
const response = await this.getResponse()
|
|
241
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
242
|
+
return await response.text() as FetchOutputTextData<Output>
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* 以 Blob 形式读取 Node.js 响应。
|
|
247
|
+
*/
|
|
248
|
+
async getBlob(): Promise<FetchOutputBlobData<Output>> {
|
|
249
|
+
const response = await this.getResponse()
|
|
250
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
251
|
+
return await response.blob() as FetchOutputBlobData<Output>
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 以 ArrayBuffer 形式读取 Node.js 响应。
|
|
256
|
+
*/
|
|
257
|
+
async getArrayBuffer(): Promise<FetchOutputArrayBufferData<Output>> {
|
|
258
|
+
const response = await this.getResponse()
|
|
259
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
260
|
+
return await response.arrayBuffer() as FetchOutputArrayBufferData<Output>
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 以流形式读取 Node.js 响应。
|
|
265
|
+
*/
|
|
266
|
+
async getStream(): Promise<FetchOutputStreamData<Output>> {
|
|
267
|
+
const response = await this.getResponse()
|
|
268
|
+
|
|
269
|
+
if (response.body === null) {
|
|
270
|
+
throw new Error("Response body stream is unavailable.")
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
274
|
+
return response.body as FetchOutputStreamData<Output>
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 创建 Node.js 环境下的 fetch 执行器。
|
|
280
|
+
*/
|
|
281
|
+
export const nodejsFetch = <Input extends FetchInput, Output extends FetchOutput>(
|
|
282
|
+
options: NodejsFetchOptions<Input, Output>
|
|
283
|
+
): NodejsFetch<Input, Output> => {
|
|
284
|
+
return new NodejsFetch(options)
|
|
285
|
+
}
|