@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.
Files changed (233) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/index.js +706 -36
  3. package/dist/index.js.map +855 -59
  4. package/package.json +28 -16
  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/schedule.ts +111 -0
  29. package/src/basic/stream.ts +135 -25
  30. package/src/credential/README.md +107 -0
  31. package/src/credential/api-key.ts +158 -0
  32. package/src/credential/bearer.ts +73 -0
  33. package/src/credential/index.ts +4 -0
  34. package/src/credential/json-web-token.ts +96 -0
  35. package/src/credential/password.ts +170 -0
  36. package/src/cron/README.md +86 -0
  37. package/src/cron/cron.ts +87 -0
  38. package/src/cron/index.ts +1 -0
  39. package/src/drizzle/README.md +1 -0
  40. package/src/drizzle/drizzle.ts +1 -0
  41. package/src/drizzle/helper.ts +47 -0
  42. package/src/drizzle/index.ts +5 -0
  43. package/src/drizzle/infer.ts +52 -0
  44. package/src/drizzle/kysely.ts +8 -0
  45. package/src/drizzle/pagination.ts +200 -0
  46. package/src/email/README.md +1 -0
  47. package/src/email/index.ts +1 -0
  48. package/src/email/resend.ts +25 -0
  49. package/src/event/class-event-proxy.ts +6 -5
  50. package/src/event/common.ts +13 -3
  51. package/src/event/event-manager.ts +3 -3
  52. package/src/event/instance-event-proxy.ts +6 -5
  53. package/src/event/internal.ts +4 -4
  54. package/src/form/README.md +25 -0
  55. package/src/form/index.ts +1 -0
  56. package/src/form/inputor-controller/base.ts +874 -0
  57. package/src/form/inputor-controller/boolean.ts +39 -0
  58. package/src/form/inputor-controller/file.ts +39 -0
  59. package/src/form/inputor-controller/form.ts +181 -0
  60. package/src/form/inputor-controller/helper.ts +117 -0
  61. package/src/form/inputor-controller/index.ts +17 -0
  62. package/src/form/inputor-controller/multi-select.ts +99 -0
  63. package/src/form/inputor-controller/number.ts +116 -0
  64. package/src/form/inputor-controller/select.ts +109 -0
  65. package/src/form/inputor-controller/text.ts +82 -0
  66. package/src/http/READMD.md +1 -0
  67. package/src/http/api/api-core.ts +84 -0
  68. package/src/http/api/api-handler.ts +79 -0
  69. package/src/http/api/api-host.ts +47 -0
  70. package/src/http/api/api-result.ts +56 -0
  71. package/src/http/api/api-schema.ts +154 -0
  72. package/src/http/api/api-server.ts +130 -0
  73. package/src/http/api/api-test.ts +142 -0
  74. package/src/http/api/api-type.ts +37 -0
  75. package/src/http/api/api.ts +81 -0
  76. package/src/http/api/index.ts +11 -0
  77. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  78. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  79. package/src/http/api-adapter/api-result-arktype.ts +297 -0
  80. package/src/http/api-adapter/api-result-zod.ts +286 -0
  81. package/src/http/api-adapter/index.ts +5 -0
  82. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  83. package/src/http/bin/gen-api-list/index.ts +1 -0
  84. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  85. package/src/http/bin/gen-api-test/index.ts +1 -0
  86. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  87. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  88. package/src/http/bin/gen-api-type/index.ts +2 -0
  89. package/src/http/bin/index.ts +2 -0
  90. package/src/http/index.ts +3 -0
  91. package/src/huawei/README.md +1 -0
  92. package/src/huawei/index.ts +2 -0
  93. package/src/huawei/moderation/index.ts +1 -0
  94. package/src/huawei/moderation/moderation.ts +355 -0
  95. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  96. package/src/huawei/obs/index.ts +1 -0
  97. package/src/huawei/obs/obs.ts +42 -0
  98. package/src/index.ts +19 -2
  99. package/src/json/README.md +92 -0
  100. package/src/json/index.ts +1 -0
  101. package/src/json/repair.ts +18 -0
  102. package/src/log/logger.ts +15 -4
  103. package/src/openai/README.md +1 -0
  104. package/src/openai/index.ts +1 -0
  105. package/src/openai/openai.ts +510 -0
  106. package/src/orchestration/README.md +9 -7
  107. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  108. package/src/orchestration/dispatching/index.ts +2 -0
  109. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  110. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  111. package/src/orchestration/dispatching/selector/index.ts +2 -0
  112. package/src/orchestration/index.ts +2 -0
  113. package/src/orchestration/scheduling/index.ts +2 -0
  114. package/src/orchestration/scheduling/scheduler.ts +103 -0
  115. package/src/orchestration/scheduling/task.ts +32 -0
  116. package/src/random/README.md +8 -7
  117. package/src/random/base.ts +66 -0
  118. package/src/random/index.ts +5 -1
  119. package/src/random/random-boolean.ts +40 -0
  120. package/src/random/random-integer.ts +60 -0
  121. package/src/random/random-number.ts +72 -0
  122. package/src/random/random-string.ts +66 -0
  123. package/src/request/README.md +108 -0
  124. package/src/request/fetch/base.ts +108 -0
  125. package/src/request/fetch/browser.ts +285 -0
  126. package/src/request/fetch/general.ts +20 -0
  127. package/src/request/fetch/index.ts +4 -0
  128. package/src/request/fetch/nodejs.ts +285 -0
  129. package/src/request/index.ts +2 -0
  130. package/src/request/request/base.ts +250 -0
  131. package/src/request/request/general.ts +64 -0
  132. package/src/request/request/index.ts +3 -0
  133. package/src/request/request/resource.ts +68 -0
  134. package/src/result/README.md +4 -0
  135. package/src/result/controller.ts +54 -0
  136. package/src/result/either.ts +193 -0
  137. package/src/result/index.ts +2 -0
  138. package/src/route/README.md +105 -0
  139. package/src/route/adapter/browser.ts +122 -0
  140. package/src/route/adapter/driver.ts +56 -0
  141. package/src/route/adapter/index.ts +2 -0
  142. package/src/route/index.ts +3 -0
  143. package/src/route/router/index.ts +2 -0
  144. package/src/route/router/route.ts +630 -0
  145. package/src/route/router/router.ts +1642 -0
  146. package/src/route/uri/hash.ts +308 -0
  147. package/src/route/uri/index.ts +7 -0
  148. package/src/route/uri/pathname.ts +376 -0
  149. package/src/route/uri/search.ts +413 -0
  150. package/src/socket/README.md +105 -0
  151. package/src/socket/client/index.ts +2 -0
  152. package/src/socket/client/socket-unit.ts +660 -0
  153. package/src/socket/client/socket.ts +203 -0
  154. package/src/socket/common/index.ts +2 -0
  155. package/src/socket/common/socket-unit-common.ts +23 -0
  156. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  157. package/src/socket/index.ts +3 -0
  158. package/src/socket/server/index.ts +3 -0
  159. package/src/socket/server/server.ts +183 -0
  160. package/src/socket/server/socket-unit.ts +449 -0
  161. package/src/socket/server/socket.ts +264 -0
  162. package/src/storage/table.ts +3 -3
  163. package/src/timer/expiration/expiration-manager.ts +3 -3
  164. package/src/timer/expiration/remaining-manager.ts +3 -3
  165. package/src/tube/README.md +99 -0
  166. package/src/tube/helper.ts +138 -0
  167. package/src/tube/index.ts +2 -0
  168. package/src/tube/tube.ts +880 -0
  169. package/src/weixin/README.md +1 -0
  170. package/src/weixin/index.ts +2 -0
  171. package/src/weixin/official-account/authorization.ts +159 -0
  172. package/src/weixin/official-account/index.ts +2 -0
  173. package/src/weixin/official-account/js-api.ts +134 -0
  174. package/src/weixin/open/index.ts +1 -0
  175. package/src/weixin/open/oauth2.ts +133 -0
  176. package/tests/unit/ai/ai.spec.ts +85 -0
  177. package/tests/unit/aio/content.spec.ts +105 -0
  178. package/tests/unit/aio/json.spec.ts +147 -0
  179. package/tests/unit/aio/prompt.spec.ts +111 -0
  180. package/tests/unit/basic/error.spec.ts +16 -4
  181. package/tests/unit/basic/schedule.spec.ts +74 -0
  182. package/tests/unit/basic/stream.spec.ts +90 -37
  183. package/tests/unit/credential/api-key.spec.ts +37 -0
  184. package/tests/unit/credential/bearer.spec.ts +23 -0
  185. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  186. package/tests/unit/credential/password.spec.ts +41 -0
  187. package/tests/unit/cron/cron.spec.ts +84 -0
  188. package/tests/unit/event/class-event-proxy.spec.ts +3 -3
  189. package/tests/unit/event/event-manager.spec.ts +3 -3
  190. package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
  191. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  192. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  193. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  194. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  195. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  196. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  197. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  198. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  199. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  200. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  201. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  202. package/tests/unit/http/api/api-server.spec.ts +363 -0
  203. package/tests/unit/http/api/api-test.spec.ts +117 -0
  204. package/tests/unit/http/api/api.spec.ts +121 -0
  205. package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
  206. package/tests/unit/json/repair.spec.ts +11 -0
  207. package/tests/unit/log/logger.spec.ts +19 -4
  208. package/tests/unit/openai/openai.spec.ts +64 -0
  209. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  210. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  211. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  212. package/tests/unit/random/base.spec.ts +58 -0
  213. package/tests/unit/random/random-boolean.spec.ts +25 -0
  214. package/tests/unit/random/random-integer.spec.ts +32 -0
  215. package/tests/unit/random/random-number.spec.ts +33 -0
  216. package/tests/unit/random/random-string.spec.ts +22 -0
  217. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  218. package/tests/unit/request/fetch/general.spec.ts +43 -0
  219. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  220. package/tests/unit/request/request/base.spec.ts +385 -0
  221. package/tests/unit/request/request/general.spec.ts +161 -0
  222. package/tests/unit/route/router/route.spec.ts +431 -0
  223. package/tests/unit/route/router/router.spec.ts +407 -0
  224. package/tests/unit/route/uri/hash.spec.ts +72 -0
  225. package/tests/unit/route/uri/pathname.spec.ts +147 -0
  226. package/tests/unit/route/uri/search.spec.ts +107 -0
  227. package/tests/unit/socket/client.spec.ts +208 -0
  228. package/tests/unit/socket/server.spec.ts +135 -0
  229. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  230. package/tests/unit/tube/helper.spec.ts +139 -0
  231. package/tests/unit/tube/tube.spec.ts +501 -0
  232. package/src/random/string.ts +0 -35
  233. package/tests/unit/random/string.spec.ts +0 -11
@@ -0,0 +1,385 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { BaseFetch, BaseRequest } from "#Source/request/index.ts"
4
+ import type { BaseFetchOptions, FetchFactory, Resource } from "#Source/request/index.ts"
5
+
6
+ type ExampleSuccessOutput = {
7
+ status: "success"
8
+ data: {
9
+ users: string[]
10
+ }
11
+ }
12
+
13
+ type ExampleErrorOutput = {
14
+ status: "error"
15
+ data: {
16
+ message: string
17
+ }
18
+ }
19
+
20
+ type ExamplePatchOutput = {
21
+ status: "patch"
22
+ errorKind: "network" | "unknown"
23
+ message: string
24
+ }
25
+
26
+ type ExampleResource = Resource & {
27
+ baseUrl: "https://api.example.com"
28
+ path: "/users"
29
+ method: "get"
30
+ input: {
31
+ query: {
32
+ page: number
33
+ }
34
+ body?: undefined
35
+ }
36
+ output: ExampleSuccessOutput | ExampleErrorOutput
37
+ successOutput: ExampleSuccessOutput
38
+ successData: ExampleSuccessOutput["data"]
39
+ errorOutput: ExampleErrorOutput
40
+ errorData: ExampleErrorOutput["data"]
41
+ patchOutput: ExamplePatchOutput
42
+ }
43
+
44
+ class TestRequest extends BaseRequest<ExampleResource> {
45
+ //
46
+ }
47
+
48
+ class TestResolvedFetch<T> extends BaseFetch<
49
+ { query: ExampleResource["input"]["query"]; body: ExampleResource["input"]["body"] },
50
+ { type: "json"; data: T }
51
+ > {
52
+ constructor(value: T) {
53
+ const options: BaseFetchOptions<
54
+ { query: ExampleResource["input"]["query"]; body: ExampleResource["input"]["body"] },
55
+ { type: "json"; data: T }
56
+ > = {
57
+ baseUrl: "https://api.example.com",
58
+ path: "/users",
59
+ method: "get",
60
+ }
61
+ super(options)
62
+ this.value = value
63
+ }
64
+
65
+ private value: T
66
+
67
+ async getJson(): Promise<T> {
68
+ return await Promise.resolve(this.value)
69
+ }
70
+
71
+ async getText(): Promise<never> {
72
+ await Promise.resolve()
73
+ throw new Error("Unused in request tests.")
74
+ }
75
+
76
+ async getBlob(): Promise<never> {
77
+ await Promise.resolve()
78
+ throw new Error("Unused in request tests.")
79
+ }
80
+
81
+ async getArrayBuffer(): Promise<never> {
82
+ await Promise.resolve()
83
+ throw new Error("Unused in request tests.")
84
+ }
85
+
86
+ async getStream(): Promise<never> {
87
+ await Promise.resolve()
88
+ throw new Error("Unused in request tests.")
89
+ }
90
+ }
91
+
92
+ class TestRejectingFetch extends BaseFetch<
93
+ { query: ExampleResource["input"]["query"]; body: ExampleResource["input"]["body"] },
94
+ { type: "json"; data: never }
95
+ > {
96
+ constructor(exception: unknown) {
97
+ const options: BaseFetchOptions<
98
+ { query: ExampleResource["input"]["query"]; body: ExampleResource["input"]["body"] },
99
+ { type: "json"; data: never }
100
+ > = {
101
+ baseUrl: "https://api.example.com",
102
+ path: "/users",
103
+ method: "get",
104
+ }
105
+ super(options)
106
+ this.exception = exception
107
+ }
108
+
109
+ private exception: unknown
110
+
111
+ async getJson(): Promise<never> {
112
+ await Promise.resolve()
113
+ throw this.exception
114
+ }
115
+
116
+ async getText(): Promise<never> {
117
+ await Promise.resolve()
118
+ throw new Error("Unused in request tests.")
119
+ }
120
+
121
+ async getBlob(): Promise<never> {
122
+ await Promise.resolve()
123
+ throw new Error("Unused in request tests.")
124
+ }
125
+
126
+ async getArrayBuffer(): Promise<never> {
127
+ await Promise.resolve()
128
+ throw new Error("Unused in request tests.")
129
+ }
130
+
131
+ async getStream(): Promise<never> {
132
+ await Promise.resolve()
133
+ throw new Error("Unused in request tests.")
134
+ }
135
+ }
136
+
137
+ test("BaseRequest.request emits lifecycle callbacks and events for success and error JSON outputs", async () => {
138
+ const successOutput: ExampleSuccessOutput = {
139
+ status: "success",
140
+ data: {
141
+ users: ["neo", "trinity"]
142
+ }
143
+ }
144
+ const errorOutput: ExampleErrorOutput = {
145
+ status: "error",
146
+ data: {
147
+ message: "denied"
148
+ }
149
+ }
150
+ let getFetchCallCount: number = 0
151
+ const getFetchCalls: BaseFetchOptions[] = []
152
+ const getFetch = (options: BaseFetchOptions): TestResolvedFetch<ExampleSuccessOutput> | TestResolvedFetch<ExampleErrorOutput> => {
153
+ getFetchCallCount = getFetchCallCount + 1
154
+ getFetchCalls.push(options)
155
+
156
+ if (getFetchCallCount === 1) {
157
+ return new TestResolvedFetch(successOutput)
158
+ }
159
+
160
+ return new TestResolvedFetch(errorOutput)
161
+ }
162
+ let modifyRequestOptionsCallCount: number = 0
163
+ const modifyRequestOptions = (options: {
164
+ baseUrl: ExampleResource["baseUrl"]
165
+ path: ExampleResource["path"]
166
+ method: ExampleResource["method"]
167
+ headers?: Record<string, string> | undefined
168
+ query: ExampleResource["input"]["query"]
169
+ }): {
170
+ baseUrl: ExampleResource["baseUrl"]
171
+ path: ExampleResource["path"]
172
+ method: ExampleResource["method"]
173
+ headers: Record<string, string>
174
+ query: ExampleResource["input"]["query"]
175
+ } => {
176
+ modifyRequestOptionsCallCount = modifyRequestOptionsCallCount + 1
177
+
178
+ return {
179
+ ...options,
180
+ headers: {
181
+ ...options.headers,
182
+ "X-Trace": "enabled"
183
+ }
184
+ }
185
+ }
186
+ const request = new TestRequest({
187
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
188
+ fetchFactory: getFetch as unknown as FetchFactory,
189
+ modifyRequestOptions,
190
+ generatePatchOutput: ({ errorKind, error }) => {
191
+ return {
192
+ status: "patch",
193
+ errorKind,
194
+ message: error.message,
195
+ }
196
+ }
197
+ })
198
+
199
+ const emittedOutputs: Array<ExampleSuccessOutput | ExampleErrorOutput> = []
200
+ const emittedSuccessOutputs: ExampleSuccessOutput[] = []
201
+ const emittedSuccessData: Array<ExampleSuccessOutput["data"]> = []
202
+ const emittedErrorOutputs: ExampleErrorOutput[] = []
203
+ const emittedErrorData: Array<ExampleErrorOutput["data"]> = []
204
+ request.event.subscribe("output", (output) => {
205
+ emittedOutputs.push(output)
206
+ })
207
+ request.event.subscribe("successOutput", (output) => {
208
+ emittedSuccessOutputs.push(output)
209
+ })
210
+ request.event.subscribe("successData", (data) => {
211
+ emittedSuccessData.push(data)
212
+ })
213
+ request.event.subscribe("errorOutput", (output) => {
214
+ emittedErrorOutputs.push(output)
215
+ })
216
+ request.event.subscribe("errorData", (data) => {
217
+ emittedErrorData.push(data)
218
+ })
219
+
220
+ const callbackOutputs: Array<ExampleSuccessOutput | ExampleErrorOutput> = []
221
+ const callbackSuccessOutputs: ExampleSuccessOutput[] = []
222
+ const callbackSuccessData: Array<ExampleSuccessOutput["data"]> = []
223
+ const callbackErrorOutputs: ExampleErrorOutput[] = []
224
+ const callbackErrorData: Array<ExampleErrorOutput["data"]> = []
225
+
226
+ const successResult = await request.request({
227
+ baseUrl: "https://api.example.com",
228
+ path: "/users",
229
+ method: "get",
230
+ query: {
231
+ page: 1
232
+ },
233
+ headers: {
234
+ Authorization: "Bearer token"
235
+ },
236
+ onOutput: (output) => {
237
+ callbackOutputs.push(output)
238
+ },
239
+ onSuccessOutput: (output) => {
240
+ callbackSuccessOutputs.push(output)
241
+ },
242
+ onSuccessData: (data) => {
243
+ callbackSuccessData.push(data)
244
+ },
245
+ })
246
+ const errorResult = await request.request({
247
+ baseUrl: "https://api.example.com",
248
+ path: "/users",
249
+ method: "get",
250
+ query: {
251
+ page: 2
252
+ },
253
+ onOutput: (output) => {
254
+ callbackOutputs.push(output)
255
+ },
256
+ onErrorOutput: (output) => {
257
+ callbackErrorOutputs.push(output)
258
+ },
259
+ onErrorData: (data) => {
260
+ callbackErrorData.push(data)
261
+ },
262
+ })
263
+
264
+ expect(successResult).toEqual(successOutput)
265
+ expect(errorResult).toEqual(errorOutput)
266
+ expect(modifyRequestOptionsCallCount).toBe(2)
267
+ expect(getFetchCallCount).toBe(2)
268
+ expect(getFetchCalls[0]?.path).toBe("/users")
269
+ expect(getFetchCalls[0]?.headers?.["Authorization"]).toBe("Bearer token")
270
+ expect(getFetchCalls[0]?.headers?.["X-Trace"]).toBe("enabled")
271
+ expect(getFetchCalls[1]?.path).toBe("/users")
272
+ expect(getFetchCalls[1]?.headers?.["X-Trace"]).toBe("enabled")
273
+ expect(callbackOutputs).toEqual([successOutput, errorOutput])
274
+ expect(callbackSuccessOutputs).toEqual([successOutput])
275
+ expect(callbackSuccessData).toEqual([successOutput.data])
276
+ expect(callbackErrorOutputs).toEqual([errorOutput])
277
+ expect(callbackErrorData).toEqual([errorOutput.data])
278
+ expect(emittedOutputs).toEqual([successOutput, errorOutput])
279
+ expect(emittedSuccessOutputs).toEqual([successOutput])
280
+ expect(emittedSuccessData).toEqual([successOutput.data])
281
+ expect(emittedErrorOutputs).toEqual([errorOutput])
282
+ expect(emittedErrorData).toEqual([errorOutput.data])
283
+ })
284
+
285
+ test("BaseRequest.request turns unsupported response types into unknown patch outputs", async () => {
286
+ const request = new TestRequest({
287
+ generatePatchOutput: ({ errorKind, error }) => {
288
+ return {
289
+ status: "patch",
290
+ errorKind,
291
+ message: error.message,
292
+ }
293
+ }
294
+ })
295
+
296
+ const result = await request.request({
297
+ baseUrl: "https://api.example.com",
298
+ path: "/users",
299
+ method: "get",
300
+ query: {
301
+ page: 1
302
+ },
303
+ responseType: "text"
304
+ })
305
+
306
+ expect(result).toEqual({
307
+ status: "patch",
308
+ errorKind: "unknown",
309
+ message: "Unsupported response type, please use \"json\" or improve implementation of BaseRequest.",
310
+ })
311
+ })
312
+
313
+ test("BaseRequest.request converts network failures into patch outputs", async () => {
314
+ const getFetch = (): TestRejectingFetch => {
315
+ return new TestRejectingFetch(new TypeError("fetch failed"))
316
+ }
317
+ const request = new TestRequest({
318
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
319
+ fetchFactory: getFetch as unknown as FetchFactory,
320
+ generatePatchOutput: ({ errorKind, error }) => {
321
+ return {
322
+ status: "patch",
323
+ errorKind,
324
+ message: error.message,
325
+ }
326
+ }
327
+ })
328
+ const patchOutputs: ExamplePatchOutput[] = []
329
+ request.event.subscribe("patchOutput", (patchOutput) => {
330
+ patchOutputs.push(patchOutput)
331
+ })
332
+
333
+ const callbackPatchOutputs: ExamplePatchOutput[] = []
334
+ const result = await request.request({
335
+ baseUrl: "https://api.example.com",
336
+ path: "/users",
337
+ method: "get",
338
+ query: {
339
+ page: 1
340
+ },
341
+ onPatchOutput: (patchOutput) => {
342
+ callbackPatchOutputs.push(patchOutput)
343
+ },
344
+ })
345
+
346
+ expect(result).toEqual({
347
+ status: "patch",
348
+ errorKind: "network",
349
+ message: "fetch failed",
350
+ })
351
+ expect(callbackPatchOutputs).toEqual([result])
352
+ expect(patchOutputs).toEqual([result])
353
+ })
354
+
355
+ test("BaseRequest.request stringifies non-Error failures into unknown patch outputs", async () => {
356
+ const getFetch = (): TestRejectingFetch => {
357
+ return new TestRejectingFetch("boom")
358
+ }
359
+ const request = new TestRequest({
360
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
361
+ fetchFactory: getFetch as unknown as FetchFactory,
362
+ generatePatchOutput: ({ errorKind, error }) => {
363
+ return {
364
+ status: "patch",
365
+ errorKind,
366
+ message: error.message,
367
+ }
368
+ }
369
+ })
370
+
371
+ const result = await request.request({
372
+ baseUrl: "https://api.example.com",
373
+ path: "/users",
374
+ method: "get",
375
+ query: {
376
+ page: 1
377
+ },
378
+ })
379
+
380
+ expect(result).toEqual({
381
+ status: "patch",
382
+ errorKind: "unknown",
383
+ message: "boom",
384
+ })
385
+ })
@@ -0,0 +1,161 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { BaseFetch, GeneralRequest } from "#Source/request/index.ts"
4
+ import type { BaseFetchOptions, FetchFactory, GeneralResource } from "#Source/request/index.ts"
5
+
6
+ type ExampleGeneralResource = GeneralResource & {
7
+ baseUrl: "https://api.example.com"
8
+ path: "/users"
9
+ method: "get"
10
+ input: {
11
+ query: {
12
+ page: number
13
+ }
14
+ body?: undefined
15
+ }
16
+ successData: {
17
+ users: string[]
18
+ }
19
+ errorData: {
20
+ message: string
21
+ }
22
+ }
23
+
24
+ class TestGeneralResolvedFetch extends BaseFetch<
25
+ { query: ExampleGeneralResource["input"]["query"]; body: ExampleGeneralResource["input"]["body"] },
26
+ { type: "json"; data: { status: "success"; data: ExampleGeneralResource["successData"] } }
27
+ > {
28
+ constructor() {
29
+ const options: BaseFetchOptions<
30
+ { query: ExampleGeneralResource["input"]["query"]; body: ExampleGeneralResource["input"]["body"] },
31
+ { type: "json"; data: { status: "success"; data: ExampleGeneralResource["successData"] } }
32
+ > = {
33
+ baseUrl: "https://api.example.com",
34
+ path: "/users",
35
+ method: "get",
36
+ }
37
+
38
+ super(options)
39
+ }
40
+
41
+ async getJson(): Promise<{ status: "success"; data: ExampleGeneralResource["successData"] }> {
42
+ return await Promise.resolve({
43
+ status: "success",
44
+ data: {
45
+ users: ["neo", "trinity"]
46
+ }
47
+ })
48
+ }
49
+
50
+ async getText(): Promise<never> {
51
+ await Promise.resolve()
52
+ throw new Error("Unused in GeneralRequest tests.")
53
+ }
54
+
55
+ async getBlob(): Promise<never> {
56
+ await Promise.resolve()
57
+ throw new Error("Unused in GeneralRequest tests.")
58
+ }
59
+
60
+ async getArrayBuffer(): Promise<never> {
61
+ await Promise.resolve()
62
+ throw new Error("Unused in GeneralRequest tests.")
63
+ }
64
+
65
+ async getStream(): Promise<never> {
66
+ await Promise.resolve()
67
+ throw new Error("Unused in GeneralRequest tests.")
68
+ }
69
+ }
70
+
71
+ class TestGeneralRejectingFetch extends BaseFetch<
72
+ { query: ExampleGeneralResource["input"]["query"]; body: ExampleGeneralResource["input"]["body"] },
73
+ { type: "json"; data: never }
74
+ > {
75
+ constructor() {
76
+ const options: BaseFetchOptions<
77
+ { query: ExampleGeneralResource["input"]["query"]; body: ExampleGeneralResource["input"]["body"] },
78
+ { type: "json"; data: never }
79
+ > = {
80
+ baseUrl: "https://api.example.com",
81
+ path: "/users",
82
+ method: "get",
83
+ }
84
+
85
+ super(options)
86
+ }
87
+
88
+ async getJson(): Promise<never> {
89
+ await Promise.resolve()
90
+ throw new TypeError("fetch failed")
91
+ }
92
+
93
+ async getText(): Promise<never> {
94
+ await Promise.resolve()
95
+ throw new Error("Unused in GeneralRequest tests.")
96
+ }
97
+
98
+ async getBlob(): Promise<never> {
99
+ await Promise.resolve()
100
+ throw new Error("Unused in GeneralRequest tests.")
101
+ }
102
+
103
+ async getArrayBuffer(): Promise<never> {
104
+ await Promise.resolve()
105
+ throw new Error("Unused in GeneralRequest tests.")
106
+ }
107
+
108
+ async getStream(): Promise<never> {
109
+ await Promise.resolve()
110
+ throw new Error("Unused in GeneralRequest tests.")
111
+ }
112
+ }
113
+
114
+ test("GeneralRequest.request keeps success outputs and generates unknown patch outputs for failures", async () => {
115
+ let fetchFactoryCallCount: number = 0
116
+ const fetchFactory = (): TestGeneralResolvedFetch | TestGeneralRejectingFetch => {
117
+ fetchFactoryCallCount = fetchFactoryCallCount + 1
118
+
119
+ if (fetchFactoryCallCount === 1) {
120
+ return new TestGeneralResolvedFetch()
121
+ }
122
+
123
+ return new TestGeneralRejectingFetch()
124
+ }
125
+ const request = new GeneralRequest<ExampleGeneralResource>({
126
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
127
+ fetchFactory: fetchFactory as unknown as FetchFactory,
128
+ })
129
+
130
+ const successResult = await request.request({
131
+ baseUrl: "https://api.example.com",
132
+ path: "/users",
133
+ method: "get",
134
+ query: {
135
+ page: 1
136
+ },
137
+ })
138
+ const patchResult = await request.request({
139
+ baseUrl: "https://api.example.com",
140
+ path: "/users",
141
+ method: "get",
142
+ query: {
143
+ page: 2
144
+ },
145
+ })
146
+
147
+ expect(successResult).toEqual({
148
+ status: "success",
149
+ data: {
150
+ users: ["neo", "trinity"]
151
+ }
152
+ })
153
+ expect(patchResult).toEqual({
154
+ status: "unknown",
155
+ data: {
156
+ type: "error",
157
+ errorKind: "network",
158
+ error: new TypeError("fetch failed")
159
+ }
160
+ })
161
+ })