@planet-matrix/mobius-model 0.6.0 → 0.10.1

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