@planet-matrix/mobius-model 0.5.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 (379) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +123 -36
  3. package/dist/index.js +715 -4
  4. package/dist/index.js.map +981 -13
  5. package/oxlint.config.ts +6 -0
  6. package/package.json +36 -18
  7. package/src/abort/README.md +92 -0
  8. package/src/abort/abort-manager.ts +278 -0
  9. package/src/abort/abort-signal-listener-manager.ts +81 -0
  10. package/src/abort/index.ts +2 -0
  11. package/src/ai/README.md +1 -0
  12. package/src/ai/ai.ts +107 -0
  13. package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
  14. package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
  15. package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
  16. package/src/ai/chat-completion-ai/index.ts +7 -0
  17. package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
  18. package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
  19. package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
  20. package/src/ai/embedding-ai/embedding-ai.ts +63 -0
  21. package/src/ai/embedding-ai/embedding.ts +50 -0
  22. package/src/ai/embedding-ai/index.ts +4 -0
  23. package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
  24. package/src/ai/index.ts +4 -0
  25. package/src/aio/README.md +100 -0
  26. package/src/aio/content.ts +141 -0
  27. package/src/aio/index.ts +3 -0
  28. package/src/aio/json.ts +127 -0
  29. package/src/aio/prompt.ts +246 -0
  30. package/src/basic/README.md +72 -116
  31. package/src/basic/error.ts +19 -5
  32. package/src/basic/function.ts +83 -64
  33. package/src/basic/index.ts +1 -0
  34. package/src/basic/is.ts +152 -71
  35. package/src/basic/promise.ts +29 -8
  36. package/src/basic/schedule.ts +111 -0
  37. package/src/basic/stream.ts +135 -25
  38. package/src/basic/string.ts +2 -33
  39. package/src/color/README.md +105 -0
  40. package/src/color/index.ts +3 -0
  41. package/src/color/internal.ts +42 -0
  42. package/src/color/rgb/analyze.ts +236 -0
  43. package/src/color/rgb/construct.ts +130 -0
  44. package/src/color/rgb/convert.ts +227 -0
  45. package/src/color/rgb/derive.ts +303 -0
  46. package/src/color/rgb/index.ts +6 -0
  47. package/src/color/rgb/internal.ts +208 -0
  48. package/src/color/rgb/parse.ts +302 -0
  49. package/src/color/rgb/serialize.ts +144 -0
  50. package/src/color/types.ts +57 -0
  51. package/src/color/xyz/analyze.ts +80 -0
  52. package/src/color/xyz/construct.ts +19 -0
  53. package/src/color/xyz/convert.ts +71 -0
  54. package/src/color/xyz/index.ts +3 -0
  55. package/src/color/xyz/internal.ts +23 -0
  56. package/src/credential/README.md +107 -0
  57. package/src/credential/api-key.ts +158 -0
  58. package/src/credential/bearer.ts +73 -0
  59. package/src/credential/index.ts +4 -0
  60. package/src/credential/json-web-token.ts +96 -0
  61. package/src/credential/password.ts +170 -0
  62. package/src/cron/README.md +86 -0
  63. package/src/cron/cron.ts +87 -0
  64. package/src/cron/index.ts +1 -0
  65. package/src/css/README.md +93 -0
  66. package/src/css/class.ts +559 -0
  67. package/src/css/index.ts +1 -0
  68. package/src/drizzle/README.md +1 -0
  69. package/src/drizzle/drizzle.ts +1 -0
  70. package/src/drizzle/helper.ts +47 -0
  71. package/src/drizzle/index.ts +5 -0
  72. package/src/drizzle/infer.ts +52 -0
  73. package/src/drizzle/kysely.ts +8 -0
  74. package/src/drizzle/pagination.ts +200 -0
  75. package/src/email/README.md +1 -0
  76. package/src/email/index.ts +1 -0
  77. package/src/email/resend.ts +25 -0
  78. package/src/encoding/README.md +66 -79
  79. package/src/encoding/base64.ts +13 -4
  80. package/src/environment/README.md +97 -0
  81. package/src/environment/basic.ts +26 -0
  82. package/src/environment/device.ts +311 -0
  83. package/src/environment/feature.ts +285 -0
  84. package/src/environment/geo.ts +337 -0
  85. package/src/environment/index.ts +7 -0
  86. package/src/environment/runtime.ts +400 -0
  87. package/src/environment/snapshot.ts +60 -0
  88. package/src/environment/variable.ts +239 -0
  89. package/src/event/README.md +90 -0
  90. package/src/event/class-event-proxy.ts +229 -0
  91. package/src/event/common.ts +29 -0
  92. package/src/event/event-manager.ts +203 -0
  93. package/src/event/index.ts +4 -0
  94. package/src/event/instance-event-proxy.ts +187 -0
  95. package/src/event/internal.ts +24 -0
  96. package/src/exception/README.md +96 -0
  97. package/src/exception/browser.ts +219 -0
  98. package/src/exception/index.ts +4 -0
  99. package/src/exception/nodejs.ts +169 -0
  100. package/src/exception/normalize.ts +106 -0
  101. package/src/exception/types.ts +99 -0
  102. package/src/form/README.md +25 -0
  103. package/src/form/index.ts +1 -0
  104. package/src/form/inputor-controller/base.ts +874 -0
  105. package/src/form/inputor-controller/boolean.ts +39 -0
  106. package/src/form/inputor-controller/file.ts +39 -0
  107. package/src/form/inputor-controller/form.ts +181 -0
  108. package/src/form/inputor-controller/helper.ts +117 -0
  109. package/src/form/inputor-controller/index.ts +17 -0
  110. package/src/form/inputor-controller/multi-select.ts +99 -0
  111. package/src/form/inputor-controller/number.ts +116 -0
  112. package/src/form/inputor-controller/select.ts +109 -0
  113. package/src/form/inputor-controller/text.ts +82 -0
  114. package/src/http/READMD.md +1 -0
  115. package/src/http/api/api-core.ts +84 -0
  116. package/src/http/api/api-handler.ts +79 -0
  117. package/src/http/api/api-host.ts +47 -0
  118. package/src/http/api/api-result.ts +56 -0
  119. package/src/http/api/api-schema.ts +154 -0
  120. package/src/http/api/api-server.ts +130 -0
  121. package/src/http/api/api-test.ts +142 -0
  122. package/src/http/api/api-type.ts +37 -0
  123. package/src/http/api/api.ts +81 -0
  124. package/src/http/api/index.ts +11 -0
  125. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  126. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  127. package/src/http/api-adapter/api-result-arktype.ts +297 -0
  128. package/src/http/api-adapter/api-result-zod.ts +286 -0
  129. package/src/http/api-adapter/index.ts +5 -0
  130. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  131. package/src/http/bin/gen-api-list/index.ts +1 -0
  132. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  133. package/src/http/bin/gen-api-test/index.ts +1 -0
  134. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  135. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  136. package/src/http/bin/gen-api-type/index.ts +2 -0
  137. package/src/http/bin/index.ts +2 -0
  138. package/src/http/index.ts +3 -0
  139. package/src/huawei/README.md +1 -0
  140. package/src/huawei/index.ts +2 -0
  141. package/src/huawei/moderation/index.ts +1 -0
  142. package/src/huawei/moderation/moderation.ts +355 -0
  143. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  144. package/src/huawei/obs/index.ts +1 -0
  145. package/src/huawei/obs/obs.ts +42 -0
  146. package/src/identifier/README.md +92 -0
  147. package/src/identifier/id.ts +119 -0
  148. package/src/identifier/index.ts +2 -0
  149. package/src/identifier/uuid.ts +187 -0
  150. package/src/index.ts +33 -1
  151. package/src/json/README.md +92 -0
  152. package/src/json/index.ts +1 -0
  153. package/src/json/repair.ts +18 -0
  154. package/src/log/README.md +79 -0
  155. package/src/log/index.ts +5 -0
  156. package/src/log/log-emitter.ts +72 -0
  157. package/src/log/log-record.ts +10 -0
  158. package/src/log/log-scheduler.ts +74 -0
  159. package/src/log/log-type.ts +8 -0
  160. package/src/log/logger.ts +554 -0
  161. package/src/openai/README.md +1 -0
  162. package/src/openai/index.ts +1 -0
  163. package/src/openai/openai.ts +510 -0
  164. package/src/orchestration/README.md +91 -0
  165. package/src/orchestration/coordination/barrier.ts +214 -0
  166. package/src/orchestration/coordination/count-down-latch.ts +215 -0
  167. package/src/orchestration/coordination/errors.ts +98 -0
  168. package/src/orchestration/coordination/index.ts +16 -0
  169. package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
  170. package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
  171. package/src/orchestration/coordination/keyed-lock.ts +168 -0
  172. package/src/orchestration/coordination/mutex.ts +257 -0
  173. package/src/orchestration/coordination/permit.ts +127 -0
  174. package/src/orchestration/coordination/read-write-lock.ts +444 -0
  175. package/src/orchestration/coordination/semaphore.ts +280 -0
  176. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  177. package/src/orchestration/dispatching/index.ts +2 -0
  178. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  179. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  180. package/src/orchestration/dispatching/selector/index.ts +2 -0
  181. package/src/orchestration/index.ts +3 -0
  182. package/src/orchestration/scheduling/index.ts +2 -0
  183. package/src/orchestration/scheduling/scheduler.ts +103 -0
  184. package/src/orchestration/scheduling/task.ts +32 -0
  185. package/src/random/README.md +56 -86
  186. package/src/random/base.ts +66 -0
  187. package/src/random/index.ts +5 -1
  188. package/src/random/random-boolean.ts +40 -0
  189. package/src/random/random-integer.ts +60 -0
  190. package/src/random/random-number.ts +72 -0
  191. package/src/random/random-string.ts +66 -0
  192. package/src/reactor/README.md +4 -0
  193. package/src/reactor/reactor-core/primitive.ts +9 -9
  194. package/src/reactor/reactor-core/reactive-system.ts +5 -5
  195. package/src/request/README.md +108 -0
  196. package/src/request/fetch/base.ts +108 -0
  197. package/src/request/fetch/browser.ts +285 -0
  198. package/src/request/fetch/general.ts +20 -0
  199. package/src/request/fetch/index.ts +4 -0
  200. package/src/request/fetch/nodejs.ts +285 -0
  201. package/src/request/index.ts +2 -0
  202. package/src/request/request/base.ts +250 -0
  203. package/src/request/request/general.ts +64 -0
  204. package/src/request/request/index.ts +3 -0
  205. package/src/request/request/resource.ts +68 -0
  206. package/src/result/README.md +4 -0
  207. package/src/result/controller.ts +54 -0
  208. package/src/result/either.ts +193 -0
  209. package/src/result/index.ts +2 -0
  210. package/src/route/README.md +105 -0
  211. package/src/route/adapter/browser.ts +122 -0
  212. package/src/route/adapter/driver.ts +56 -0
  213. package/src/route/adapter/index.ts +2 -0
  214. package/src/route/index.ts +3 -0
  215. package/src/route/router/index.ts +2 -0
  216. package/src/route/router/route.ts +630 -0
  217. package/src/route/router/router.ts +1642 -0
  218. package/src/route/uri/hash.ts +308 -0
  219. package/src/route/uri/index.ts +7 -0
  220. package/src/route/uri/pathname.ts +376 -0
  221. package/src/route/uri/search.ts +413 -0
  222. package/src/singleton/README.md +79 -0
  223. package/src/singleton/factory.ts +55 -0
  224. package/src/singleton/index.ts +2 -0
  225. package/src/singleton/manager.ts +204 -0
  226. package/src/socket/README.md +105 -0
  227. package/src/socket/client/index.ts +2 -0
  228. package/src/socket/client/socket-unit.ts +660 -0
  229. package/src/socket/client/socket.ts +203 -0
  230. package/src/socket/common/index.ts +2 -0
  231. package/src/socket/common/socket-unit-common.ts +23 -0
  232. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  233. package/src/socket/index.ts +3 -0
  234. package/src/socket/server/index.ts +3 -0
  235. package/src/socket/server/server.ts +183 -0
  236. package/src/socket/server/socket-unit.ts +449 -0
  237. package/src/socket/server/socket.ts +264 -0
  238. package/src/storage/README.md +107 -0
  239. package/src/storage/index.ts +1 -0
  240. package/src/storage/table.ts +449 -0
  241. package/src/timer/README.md +86 -0
  242. package/src/timer/expiration/expiration-manager.ts +594 -0
  243. package/src/timer/expiration/index.ts +3 -0
  244. package/src/timer/expiration/min-heap.ts +208 -0
  245. package/src/timer/expiration/remaining-manager.ts +241 -0
  246. package/src/timer/index.ts +1 -0
  247. package/src/tube/README.md +99 -0
  248. package/src/tube/helper.ts +138 -0
  249. package/src/tube/index.ts +2 -0
  250. package/src/tube/tube.ts +880 -0
  251. package/src/type/README.md +54 -307
  252. package/src/type/class.ts +2 -2
  253. package/src/type/index.ts +14 -14
  254. package/src/type/is.ts +265 -2
  255. package/src/type/object.ts +37 -0
  256. package/src/type/string.ts +7 -2
  257. package/src/type/tuple.ts +6 -6
  258. package/src/type/union.ts +16 -0
  259. package/src/web/README.md +77 -0
  260. package/src/web/capture.ts +35 -0
  261. package/src/web/clipboard.ts +97 -0
  262. package/src/web/dom.ts +117 -0
  263. package/src/web/download.ts +16 -0
  264. package/src/web/event.ts +46 -0
  265. package/src/web/index.ts +10 -0
  266. package/src/web/local-storage.ts +113 -0
  267. package/src/web/location.ts +28 -0
  268. package/src/web/permission.ts +172 -0
  269. package/src/web/script-loader.ts +432 -0
  270. package/src/weixin/README.md +1 -0
  271. package/src/weixin/index.ts +2 -0
  272. package/src/weixin/official-account/authorization.ts +159 -0
  273. package/src/weixin/official-account/index.ts +2 -0
  274. package/src/weixin/official-account/js-api.ts +134 -0
  275. package/src/weixin/open/index.ts +1 -0
  276. package/src/weixin/open/oauth2.ts +133 -0
  277. package/tests/unit/abort/abort-manager.spec.ts +225 -0
  278. package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
  279. package/tests/unit/ai/ai.spec.ts +85 -0
  280. package/tests/unit/aio/content.spec.ts +105 -0
  281. package/tests/unit/aio/json.spec.ts +147 -0
  282. package/tests/unit/aio/prompt.spec.ts +111 -0
  283. package/tests/unit/basic/array.spec.ts +1 -1
  284. package/tests/unit/basic/error.spec.ts +16 -4
  285. package/tests/unit/basic/schedule.spec.ts +74 -0
  286. package/tests/unit/basic/stream.spec.ts +91 -38
  287. package/tests/unit/basic/string.spec.ts +0 -9
  288. package/tests/unit/color/rgb/analyze.spec.ts +110 -0
  289. package/tests/unit/color/rgb/construct.spec.ts +56 -0
  290. package/tests/unit/color/rgb/convert.spec.ts +60 -0
  291. package/tests/unit/color/rgb/derive.spec.ts +103 -0
  292. package/tests/unit/color/rgb/parse.spec.ts +66 -0
  293. package/tests/unit/color/rgb/serialize.spec.ts +46 -0
  294. package/tests/unit/color/xyz/analyze.spec.ts +33 -0
  295. package/tests/unit/color/xyz/construct.spec.ts +10 -0
  296. package/tests/unit/color/xyz/convert.spec.ts +18 -0
  297. package/tests/unit/credential/api-key.spec.ts +37 -0
  298. package/tests/unit/credential/bearer.spec.ts +23 -0
  299. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  300. package/tests/unit/credential/password.spec.ts +41 -0
  301. package/tests/unit/cron/cron.spec.ts +84 -0
  302. package/tests/unit/css/class.spec.ts +157 -0
  303. package/tests/unit/environment/basic.spec.ts +20 -0
  304. package/tests/unit/environment/device.spec.ts +146 -0
  305. package/tests/unit/environment/feature.spec.ts +388 -0
  306. package/tests/unit/environment/geo.spec.ts +111 -0
  307. package/tests/unit/environment/runtime.spec.ts +364 -0
  308. package/tests/unit/environment/snapshot.spec.ts +4 -0
  309. package/tests/unit/environment/variable.spec.ts +190 -0
  310. package/tests/unit/event/class-event-proxy.spec.ts +225 -0
  311. package/tests/unit/event/event-manager.spec.ts +246 -0
  312. package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
  313. package/tests/unit/exception/browser.spec.ts +213 -0
  314. package/tests/unit/exception/nodejs.spec.ts +144 -0
  315. package/tests/unit/exception/normalize.spec.ts +57 -0
  316. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  317. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  318. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  319. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  320. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  321. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  322. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  323. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  324. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  325. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  326. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  327. package/tests/unit/http/api/api-server.spec.ts +363 -0
  328. package/tests/unit/http/api/api-test.spec.ts +117 -0
  329. package/tests/unit/http/api/api.spec.ts +121 -0
  330. package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
  331. package/tests/unit/identifier/id.spec.ts +71 -0
  332. package/tests/unit/identifier/uuid.spec.ts +85 -0
  333. package/tests/unit/json/repair.spec.ts +11 -0
  334. package/tests/unit/log/log-emitter.spec.ts +33 -0
  335. package/tests/unit/log/log-scheduler.spec.ts +40 -0
  336. package/tests/unit/log/log-type.spec.ts +7 -0
  337. package/tests/unit/log/logger.spec.ts +237 -0
  338. package/tests/unit/openai/openai.spec.ts +64 -0
  339. package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
  340. package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
  341. package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
  342. package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
  343. package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
  344. package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
  345. package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
  346. package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
  347. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  348. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  349. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  350. package/tests/unit/random/base.spec.ts +58 -0
  351. package/tests/unit/random/random-boolean.spec.ts +25 -0
  352. package/tests/unit/random/random-integer.spec.ts +32 -0
  353. package/tests/unit/random/random-number.spec.ts +33 -0
  354. package/tests/unit/random/random-string.spec.ts +22 -0
  355. package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
  356. package/tests/unit/reactor/preact-signal.spec.ts +1 -2
  357. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  358. package/tests/unit/request/fetch/general.spec.ts +43 -0
  359. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  360. package/tests/unit/request/request/base.spec.ts +385 -0
  361. package/tests/unit/request/request/general.spec.ts +161 -0
  362. package/tests/unit/route/router/route.spec.ts +431 -0
  363. package/tests/unit/route/router/router.spec.ts +407 -0
  364. package/tests/unit/route/uri/hash.spec.ts +72 -0
  365. package/tests/unit/route/uri/pathname.spec.ts +147 -0
  366. package/tests/unit/route/uri/search.spec.ts +107 -0
  367. package/tests/unit/singleton/singleton.spec.ts +49 -0
  368. package/tests/unit/socket/client.spec.ts +208 -0
  369. package/tests/unit/socket/server.spec.ts +135 -0
  370. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  371. package/tests/unit/storage/table.spec.ts +620 -0
  372. package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
  373. package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
  374. package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
  375. package/tests/unit/tube/helper.spec.ts +139 -0
  376. package/tests/unit/tube/tube.spec.ts +501 -0
  377. package/.oxlintrc.json +0 -5
  378. package/src/random/uuid.ts +0 -103
  379. package/tests/unit/random/uuid.spec.ts +0 -37
@@ -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
+ })