@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.
- package/CHANGELOG.md +50 -0
- package/oxlint.config.ts +1 -2
- package/package.json +29 -17
- package/scripts/build.ts +2 -52
- 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/promise.ts +141 -71
- 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 +198 -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 +5 -6
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +5 -6
- package/src/event/internal.ts +4 -4
- package/src/exception/README.md +28 -19
- package/src/exception/error/error.ts +123 -0
- package/src/exception/error/index.ts +2 -0
- package/src/exception/error/match.ts +38 -0
- package/src/exception/error/must-fix.ts +17 -0
- package/src/exception/index.ts +2 -0
- package/src/file-system/find.ts +53 -0
- package/src/file-system/index.ts +2 -0
- package/src/file-system/path.ts +76 -0
- package/src/file-system/resolve.ts +22 -0
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +861 -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 +179 -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 +34 -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 +294 -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 +21 -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 +509 -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 +280 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +280 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +246 -0
- package/src/request/request/general.ts +63 -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 +58 -0
- package/src/result/either.ts +363 -0
- package/src/result/generator.ts +168 -0
- package/src/result/index.ts +3 -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 +1641 -0
- package/src/route/uri/hash.ts +307 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +412 -0
- package/src/service/README.md +1 -0
- package/src/service/index.ts +1 -0
- package/src/service/service.ts +110 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +658 -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 +448 -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 +137 -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 +157 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +132 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +131 -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 +146 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/promise.spec.ts +158 -50
- 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 +36 -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 +40 -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/exception/error/error.spec.ts +83 -0
- package/tests/unit/exception/error/match.spec.ts +81 -0
- 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 +187 -0
- package/tests/unit/identifier/uuid.spec.ts +0 -1
- 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 +382 -0
- package/tests/unit/request/request/general.spec.ts +160 -0
- package/tests/unit/result/controller.spec.ts +82 -0
- package/tests/unit/result/either.spec.ts +377 -0
- package/tests/unit/result/generator.spec.ts +273 -0
- package/tests/unit/route/router/route.spec.ts +430 -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 +146 -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 +133 -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/vite.config.ts +2 -1
- package/dist/index.js +0 -50
- package/dist/index.js.map +0 -209
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { randomString } from "#Source/random/index.ts"
|
|
4
|
+
|
|
5
|
+
test("randomString returns expected output across available random sources", () => {
|
|
6
|
+
const cryptoBackedValue = randomString(12)
|
|
7
|
+
expect(cryptoBackedValue).toHaveLength(12)
|
|
8
|
+
|
|
9
|
+
const constrainedValue = randomString(10, "ab")
|
|
10
|
+
expect(constrainedValue.split("").every(char => char === "a" || char === "b")).toBe(true)
|
|
11
|
+
|
|
12
|
+
vi.stubGlobal("crypto", undefined)
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const fallbackValue = randomString(10, "ab")
|
|
16
|
+
expect(fallbackValue).toHaveLength(10)
|
|
17
|
+
expect(fallbackValue.split("").every(char => char === "a" || char === "b")).toBe(true)
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
vi.unstubAllGlobals()
|
|
21
|
+
}
|
|
22
|
+
})
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { BrowserFetch, browserFetch } from "#Source/request/index.ts"
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.useRealTimers()
|
|
7
|
+
vi.unstubAllGlobals()
|
|
8
|
+
vi.restoreAllMocks()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test("BrowserFetch.getJson builds request URL and JSON body", async () => {
|
|
12
|
+
let capturedInput: URL | RequestInfo | undefined
|
|
13
|
+
let capturedInit: RequestInit | undefined
|
|
14
|
+
vi.stubGlobal("fetch", (input: URL | RequestInfo, init?: RequestInit) => {
|
|
15
|
+
capturedInput = input
|
|
16
|
+
capturedInit = init
|
|
17
|
+
return {
|
|
18
|
+
json: async (): Promise<{ status: string }> => {
|
|
19
|
+
return await Promise.resolve({ status: "ok" })
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const fetcher = new BrowserFetch<
|
|
25
|
+
{ query: { page: number; tags: string[]; filter: { active: boolean } }; body: { name: string } },
|
|
26
|
+
{ type: "json"; data: { status: string } }
|
|
27
|
+
>({
|
|
28
|
+
baseUrl: "https://api.example.com",
|
|
29
|
+
path: "/users",
|
|
30
|
+
method: "post",
|
|
31
|
+
headers: {
|
|
32
|
+
Authorization: "Bearer token"
|
|
33
|
+
},
|
|
34
|
+
query: {
|
|
35
|
+
page: 2,
|
|
36
|
+
tags: ["alpha", "beta"],
|
|
37
|
+
filter: { active: true }
|
|
38
|
+
},
|
|
39
|
+
body: {
|
|
40
|
+
name: "Mobius"
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const json = await fetcher.getJson()
|
|
45
|
+
|
|
46
|
+
expect(json).toEqual({ status: "ok" })
|
|
47
|
+
expect(capturedInput).toBeDefined()
|
|
48
|
+
expect(capturedInit).toBeDefined()
|
|
49
|
+
|
|
50
|
+
if (capturedInput === undefined || capturedInit === undefined) {
|
|
51
|
+
throw new Error("Expected fetch to be called with request data.")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const inputUrl = capturedInput instanceof URL
|
|
55
|
+
? capturedInput.toString()
|
|
56
|
+
: typeof capturedInput === "string"
|
|
57
|
+
? capturedInput
|
|
58
|
+
: capturedInput.url
|
|
59
|
+
|
|
60
|
+
expect(inputUrl).toBe("https://api.example.com/users?page=2&tags=alpha&tags=beta&filter=%7B%22active%22%3Atrue%7D")
|
|
61
|
+
expect(capturedInit.method).toBe("POST")
|
|
62
|
+
expect(capturedInit.body).toBe('{"name":"Mobius"}')
|
|
63
|
+
|
|
64
|
+
const headers = new Headers(capturedInit.headers)
|
|
65
|
+
expect(headers.get("Authorization")).toBe("Bearer token")
|
|
66
|
+
expect(headers.get("Content-Type")).toBe("application/json")
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("BrowserFetch reads text, blob, arrayBuffer, and stream responses", async () => {
|
|
70
|
+
const blob = new Blob(["mobius"])
|
|
71
|
+
const arrayBuffer = new TextEncoder().encode("matrix").buffer
|
|
72
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
73
|
+
start(controller) {
|
|
74
|
+
controller.enqueue(new Uint8Array([1, 2, 3]))
|
|
75
|
+
controller.close()
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
const mockFetch = vi.fn((input: URL | RequestInfo) => {
|
|
79
|
+
const url = input instanceof URL ? input.pathname : new URL(typeof input === "string" ? input : input.url).pathname
|
|
80
|
+
|
|
81
|
+
if (url.endsWith("/text")) {
|
|
82
|
+
return {
|
|
83
|
+
text: async (): Promise<string> => {
|
|
84
|
+
return await Promise.resolve("hello")
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (url.endsWith("/blob")) {
|
|
90
|
+
return {
|
|
91
|
+
blob: async (): Promise<Blob> => {
|
|
92
|
+
return await Promise.resolve(blob)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (url.endsWith("/array-buffer")) {
|
|
98
|
+
return {
|
|
99
|
+
arrayBuffer: async (): Promise<ArrayBuffer> => {
|
|
100
|
+
return await Promise.resolve(arrayBuffer)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
body: stream
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
vi.stubGlobal("fetch", mockFetch)
|
|
110
|
+
|
|
111
|
+
const textFetcher = new BrowserFetch<
|
|
112
|
+
{ query?: undefined; body?: undefined },
|
|
113
|
+
{ type: "text"; data: string }
|
|
114
|
+
>({
|
|
115
|
+
baseUrl: "https://api.example.com",
|
|
116
|
+
path: "/text",
|
|
117
|
+
method: "get"
|
|
118
|
+
})
|
|
119
|
+
const blobFetcher = new BrowserFetch<
|
|
120
|
+
{ query?: undefined; body?: undefined },
|
|
121
|
+
{ type: "blob"; data: Blob }
|
|
122
|
+
>({
|
|
123
|
+
baseUrl: "https://api.example.com",
|
|
124
|
+
path: "/blob",
|
|
125
|
+
method: "get"
|
|
126
|
+
})
|
|
127
|
+
const arrayBufferFetcher = new BrowserFetch<
|
|
128
|
+
{ query?: undefined; body?: undefined },
|
|
129
|
+
{ type: "arrayBuffer"; data: ArrayBuffer }
|
|
130
|
+
>({
|
|
131
|
+
baseUrl: "https://api.example.com",
|
|
132
|
+
path: "/array-buffer",
|
|
133
|
+
method: "get"
|
|
134
|
+
})
|
|
135
|
+
const streamFetcher = new BrowserFetch<
|
|
136
|
+
{ query?: undefined; body?: undefined },
|
|
137
|
+
{ type: "stream"; data: ReadableStream<Uint8Array> }
|
|
138
|
+
>({
|
|
139
|
+
baseUrl: "https://api.example.com",
|
|
140
|
+
path: "/stream",
|
|
141
|
+
method: "get"
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
await expect(textFetcher.getText()).resolves.toBe("hello")
|
|
145
|
+
await expect(blobFetcher.getBlob()).resolves.toBe(blob)
|
|
146
|
+
await expect(arrayBufferFetcher.getArrayBuffer()).resolves.toBe(arrayBuffer)
|
|
147
|
+
await expect(streamFetcher.getStream()).resolves.toBe(stream)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test("BrowserFetch rejects with a timeout error when the request exceeds the limit", async () => {
|
|
151
|
+
vi.useFakeTimers()
|
|
152
|
+
|
|
153
|
+
const mockFetch = vi.fn(async (_input: URL | RequestInfo, init?: RequestInit) => {
|
|
154
|
+
return await new Promise<Response>((_resolve, reject) => {
|
|
155
|
+
init?.signal?.addEventListener("abort", () => {
|
|
156
|
+
const reason: unknown = init.signal?.reason
|
|
157
|
+
reject(reason instanceof Error ? reason : new Error("Request aborted."))
|
|
158
|
+
})
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
vi.stubGlobal("fetch", mockFetch)
|
|
162
|
+
|
|
163
|
+
const fetcher = new BrowserFetch<
|
|
164
|
+
{ query?: undefined; body?: undefined },
|
|
165
|
+
{ type: "json"; data: { ok: boolean } }
|
|
166
|
+
>({
|
|
167
|
+
baseUrl: "https://api.example.com",
|
|
168
|
+
path: "/timeout",
|
|
169
|
+
method: "get",
|
|
170
|
+
timeout: 50
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const resultPromise = fetcher.getJson()
|
|
174
|
+
|
|
175
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
176
|
+
|
|
177
|
+
await expect(resultPromise).rejects.toThrow("Request timeout after 50ms.")
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test("BrowserFetch applies the external abortSignal option", async () => {
|
|
181
|
+
const abortController = new AbortController()
|
|
182
|
+
let capturedSignal: AbortSignal | undefined
|
|
183
|
+
|
|
184
|
+
const mockFetch = vi.fn(async (_input: URL | RequestInfo, init?: RequestInit) => {
|
|
185
|
+
capturedSignal = init?.signal ?? undefined
|
|
186
|
+
|
|
187
|
+
return await new Promise<Response>((_resolve, reject) => {
|
|
188
|
+
init?.signal?.addEventListener("abort", () => {
|
|
189
|
+
const reason: unknown = init.signal?.reason
|
|
190
|
+
reject(reason instanceof Error ? reason : new Error("Request aborted."))
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
})
|
|
194
|
+
vi.stubGlobal("fetch", mockFetch)
|
|
195
|
+
|
|
196
|
+
const fetcher = new BrowserFetch<
|
|
197
|
+
{ query?: undefined; body?: undefined },
|
|
198
|
+
{ type: "json"; data: { ok: boolean } }
|
|
199
|
+
>({
|
|
200
|
+
baseUrl: "https://api.example.com",
|
|
201
|
+
path: "/abort",
|
|
202
|
+
method: "get",
|
|
203
|
+
abortSignal: abortController.signal
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const resultPromise = fetcher.getJson()
|
|
207
|
+
abortController.abort(new Error("Manual abort."))
|
|
208
|
+
|
|
209
|
+
await expect(resultPromise).rejects.toThrow("Manual abort.")
|
|
210
|
+
expect(capturedSignal).toBeDefined()
|
|
211
|
+
expect(capturedSignal?.aborted).toBe(true)
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test("browserFetch creates a BrowserFetch instance", () => {
|
|
215
|
+
const fetcher = browserFetch({
|
|
216
|
+
baseUrl: "https://api.example.com",
|
|
217
|
+
path: "/factory",
|
|
218
|
+
method: "get"
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
expect(fetcher).toBeInstanceOf(BrowserFetch)
|
|
222
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { BrowserFetch, generalFetch, NodejsFetch } from "#Source/request/index.ts"
|
|
4
|
+
|
|
5
|
+
const internalUseRuntimes = vi.hoisted(() => vi.fn())
|
|
6
|
+
|
|
7
|
+
vi.mock("#Source/environment/index.ts", () => {
|
|
8
|
+
return {
|
|
9
|
+
useRuntimes: internalUseRuntimes,
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test("generalFetch delegates fetch creation to the runtime-specific handler", () => {
|
|
14
|
+
const options = {
|
|
15
|
+
baseUrl: "https://api.example.com",
|
|
16
|
+
path: "/users",
|
|
17
|
+
method: "get",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
internalUseRuntimes.mockImplementationOnce((handlers: {
|
|
21
|
+
browser?: (() => unknown) | undefined
|
|
22
|
+
nodejs?: (() => unknown) | undefined
|
|
23
|
+
}) => {
|
|
24
|
+
return handlers.nodejs?.()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const nodejsFetcher = generalFetch(options)
|
|
28
|
+
|
|
29
|
+
expect(nodejsFetcher).toBeInstanceOf(NodejsFetch)
|
|
30
|
+
expect(internalUseRuntimes).toHaveBeenCalledTimes(1)
|
|
31
|
+
|
|
32
|
+
internalUseRuntimes.mockImplementationOnce((handlers: {
|
|
33
|
+
browser?: (() => unknown) | undefined
|
|
34
|
+
nodejs?: (() => unknown) | undefined
|
|
35
|
+
}) => {
|
|
36
|
+
return handlers.browser?.()
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const browserFetcher = generalFetch(options)
|
|
40
|
+
|
|
41
|
+
expect(browserFetcher).toBeInstanceOf(BrowserFetch)
|
|
42
|
+
expect(internalUseRuntimes).toHaveBeenCalledTimes(2)
|
|
43
|
+
})
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { NodejsFetch, nodejsFetch } from "#Source/request/index.ts"
|
|
4
|
+
|
|
5
|
+
const internalUndiciFetch = vi.hoisted(() => vi.fn())
|
|
6
|
+
|
|
7
|
+
vi.mock("undici", () => {
|
|
8
|
+
return {
|
|
9
|
+
fetch: internalUndiciFetch,
|
|
10
|
+
Headers: globalThis.Headers,
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.useRealTimers()
|
|
17
|
+
vi.restoreAllMocks()
|
|
18
|
+
internalUndiciFetch.mockReset()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test("NodejsFetch.getJson builds request URL and JSON body", async () => {
|
|
22
|
+
let capturedInput: string | undefined
|
|
23
|
+
let capturedInit: RequestInit | undefined
|
|
24
|
+
|
|
25
|
+
internalUndiciFetch.mockImplementation((input: string, init?: RequestInit) => {
|
|
26
|
+
capturedInput = input
|
|
27
|
+
capturedInit = init
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
json: async (): Promise<{ status: string }> => {
|
|
31
|
+
return await Promise.resolve({ status: "ok" })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
const fetcher = new NodejsFetch<
|
|
37
|
+
{ query: { page: number; tags: string[]; filter: { active: boolean } }; body: { name: string } },
|
|
38
|
+
{ type: "json"; data: { status: string } }
|
|
39
|
+
>({
|
|
40
|
+
baseUrl: "https://api.example.com",
|
|
41
|
+
path: "/users",
|
|
42
|
+
method: "post",
|
|
43
|
+
headers: {
|
|
44
|
+
Authorization: "Bearer token"
|
|
45
|
+
},
|
|
46
|
+
query: {
|
|
47
|
+
page: 2,
|
|
48
|
+
tags: ["alpha", "beta"],
|
|
49
|
+
filter: { active: true }
|
|
50
|
+
},
|
|
51
|
+
body: {
|
|
52
|
+
name: "Mobius"
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const json = await fetcher.getJson()
|
|
57
|
+
|
|
58
|
+
expect(json).toEqual({ status: "ok" })
|
|
59
|
+
expect(capturedInput).toBe("https://api.example.com/users?page=2&tags=alpha&tags=beta&filter=%7B%22active%22%3Atrue%7D")
|
|
60
|
+
expect(capturedInit).toBeDefined()
|
|
61
|
+
|
|
62
|
+
if (capturedInit === undefined) {
|
|
63
|
+
throw new Error("Expected fetch to be called with request data.")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
expect(capturedInit.method).toBe("POST")
|
|
67
|
+
expect(capturedInit.body).toBe('{"name":"Mobius"}')
|
|
68
|
+
|
|
69
|
+
const headers = new Headers(capturedInit.headers)
|
|
70
|
+
expect(headers.get("Authorization")).toBe("Bearer token")
|
|
71
|
+
expect(headers.get("Content-Type")).toBe("application/json")
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test("NodejsFetch reads text, blob, arrayBuffer, and stream responses", async () => {
|
|
75
|
+
const blob = new Blob(["mobius"])
|
|
76
|
+
const arrayBuffer = new TextEncoder().encode("matrix").buffer
|
|
77
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
78
|
+
start(controller) {
|
|
79
|
+
controller.enqueue(new Uint8Array([1, 2, 3]))
|
|
80
|
+
controller.close()
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
internalUndiciFetch.mockImplementation((input: string) => {
|
|
85
|
+
const pathname = new URL(input).pathname
|
|
86
|
+
|
|
87
|
+
if (pathname.endsWith("/text")) {
|
|
88
|
+
return {
|
|
89
|
+
text: async (): Promise<string> => {
|
|
90
|
+
return await Promise.resolve("hello")
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (pathname.endsWith("/blob")) {
|
|
96
|
+
return {
|
|
97
|
+
blob: async (): Promise<Blob> => {
|
|
98
|
+
return await Promise.resolve(blob)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (pathname.endsWith("/array-buffer")) {
|
|
104
|
+
return {
|
|
105
|
+
arrayBuffer: async (): Promise<ArrayBuffer> => {
|
|
106
|
+
return await Promise.resolve(arrayBuffer)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
body: stream
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
const textFetcher = new NodejsFetch<
|
|
117
|
+
{ query?: undefined; body?: undefined },
|
|
118
|
+
{ type: "text"; data: string }
|
|
119
|
+
>({
|
|
120
|
+
baseUrl: "https://api.example.com",
|
|
121
|
+
path: "/text",
|
|
122
|
+
method: "get"
|
|
123
|
+
})
|
|
124
|
+
const blobFetcher = new NodejsFetch<
|
|
125
|
+
{ query?: undefined; body?: undefined },
|
|
126
|
+
{ type: "blob"; data: Blob }
|
|
127
|
+
>({
|
|
128
|
+
baseUrl: "https://api.example.com",
|
|
129
|
+
path: "/blob",
|
|
130
|
+
method: "get"
|
|
131
|
+
})
|
|
132
|
+
const arrayBufferFetcher = new NodejsFetch<
|
|
133
|
+
{ query?: undefined; body?: undefined },
|
|
134
|
+
{ type: "arrayBuffer"; data: ArrayBuffer }
|
|
135
|
+
>({
|
|
136
|
+
baseUrl: "https://api.example.com",
|
|
137
|
+
path: "/array-buffer",
|
|
138
|
+
method: "get"
|
|
139
|
+
})
|
|
140
|
+
const streamFetcher = new NodejsFetch<
|
|
141
|
+
{ query?: undefined; body?: undefined },
|
|
142
|
+
{ type: "stream"; data: ReadableStream<Uint8Array> }
|
|
143
|
+
>({
|
|
144
|
+
baseUrl: "https://api.example.com",
|
|
145
|
+
path: "/stream",
|
|
146
|
+
method: "get"
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
await expect(textFetcher.getText()).resolves.toBe("hello")
|
|
150
|
+
await expect(blobFetcher.getBlob()).resolves.toBe(blob)
|
|
151
|
+
await expect(arrayBufferFetcher.getArrayBuffer()).resolves.toBe(arrayBuffer)
|
|
152
|
+
await expect(streamFetcher.getStream()).resolves.toBe(stream)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test("NodejsFetch rejects with a timeout error when the request exceeds the limit", async () => {
|
|
156
|
+
vi.useFakeTimers()
|
|
157
|
+
|
|
158
|
+
internalUndiciFetch.mockImplementation(async (_input: string, init?: RequestInit) => {
|
|
159
|
+
return await new Promise((_resolve, reject) => {
|
|
160
|
+
init?.signal?.addEventListener("abort", () => {
|
|
161
|
+
const reason: unknown = init.signal?.reason
|
|
162
|
+
reject(reason instanceof Error ? reason : new Error("Request aborted."))
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
const fetcher = new NodejsFetch<
|
|
168
|
+
{ query?: undefined; body?: undefined },
|
|
169
|
+
{ type: "json"; data: { ok: boolean } }
|
|
170
|
+
>({
|
|
171
|
+
baseUrl: "https://api.example.com",
|
|
172
|
+
path: "/timeout",
|
|
173
|
+
method: "get",
|
|
174
|
+
timeout: 50
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
const resultPromise = fetcher.getJson()
|
|
178
|
+
|
|
179
|
+
await vi.advanceTimersByTimeAsync(50)
|
|
180
|
+
|
|
181
|
+
await expect(resultPromise).rejects.toThrow("Request timeout after 50ms.")
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test("NodejsFetch applies the external abortSignal option", async () => {
|
|
185
|
+
const abortController = new AbortController()
|
|
186
|
+
let capturedSignal: AbortSignal | undefined
|
|
187
|
+
|
|
188
|
+
internalUndiciFetch.mockImplementation(async (_input: string, init?: RequestInit) => {
|
|
189
|
+
capturedSignal = init?.signal ?? undefined
|
|
190
|
+
|
|
191
|
+
return await new Promise((_resolve, reject) => {
|
|
192
|
+
init?.signal?.addEventListener("abort", () => {
|
|
193
|
+
const reason: unknown = init.signal?.reason
|
|
194
|
+
reject(reason instanceof Error ? reason : new Error("Request aborted."))
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const fetcher = new NodejsFetch<
|
|
200
|
+
{ query?: undefined; body?: undefined },
|
|
201
|
+
{ type: "json"; data: { ok: boolean } }
|
|
202
|
+
>({
|
|
203
|
+
baseUrl: "https://api.example.com",
|
|
204
|
+
path: "/abort",
|
|
205
|
+
method: "get",
|
|
206
|
+
abortSignal: abortController.signal
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
const resultPromise = fetcher.getJson()
|
|
210
|
+
abortController.abort(new Error("Manual abort."))
|
|
211
|
+
|
|
212
|
+
await expect(resultPromise).rejects.toThrow("Manual abort.")
|
|
213
|
+
expect(capturedSignal).toBeDefined()
|
|
214
|
+
expect(capturedSignal?.aborted).toBe(true)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test("nodejsFetch creates a NodejsFetch instance", () => {
|
|
218
|
+
const fetcher = nodejsFetch({
|
|
219
|
+
baseUrl: "https://api.example.com",
|
|
220
|
+
path: "/factory",
|
|
221
|
+
method: "get"
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
expect(fetcher).toBeInstanceOf(NodejsFetch)
|
|
225
|
+
})
|