@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,363 @@
1
+ import { expect, test, vi } from "vitest"
2
+ import type { StandardSchemaV1 } from "@standard-schema/spec"
3
+
4
+ import type {
5
+ ApiCoreHandler,
6
+ ApiCoreHeaders,
7
+ ApiCoreRequestFormData,
8
+ ApiCoreStream,
9
+ ApiHostStartResult,
10
+ ApiHandlerContext,
11
+ } from "#Source/http/index.ts"
12
+ import {
13
+ ApiCore,
14
+ ApiHost,
15
+ ApiServer,
16
+ SuccessResult,
17
+ createApi,
18
+ createApiSchema,
19
+ createApiServer,
20
+ } from "#Source/http/index.ts"
21
+
22
+ const isRecord = (value: unknown): value is Record<string, unknown> => {
23
+ return typeof value === "object" && value !== null
24
+ }
25
+
26
+ const createSchema = <Output>(
27
+ validate: (value: unknown) => StandardSchemaV1.Result<Output>,
28
+ ): StandardSchemaV1<unknown, Output> => {
29
+ return {
30
+ "~standard": {
31
+ version: 1 as const,
32
+ vendor: "mobius-http-test",
33
+ validate,
34
+ },
35
+ }
36
+ }
37
+
38
+ const inputQuerySchema = createSchema<{ page: string }>((value) => {
39
+ if (isRecord(value) && typeof value["page"] === "string") {
40
+ return {
41
+ value: {
42
+ page: value["page"],
43
+ },
44
+ }
45
+ }
46
+
47
+ return {
48
+ issues: [{ message: "page is required" }],
49
+ }
50
+ })
51
+
52
+ const inputBodySchema = createSchema<{ name: string }>((value) => {
53
+ if (isRecord(value) && typeof value["name"] === "string") {
54
+ return {
55
+ value: {
56
+ name: value["name"],
57
+ },
58
+ }
59
+ }
60
+
61
+ return {
62
+ issues: [{ message: "name is required" }],
63
+ }
64
+ })
65
+
66
+ const outputSuccessSchema = createSchema<{ status: "success" }>((value) => {
67
+ if (typeof value === "object" && value !== null && (value as { status?: unknown }).status === "success") {
68
+ return {
69
+ value: {
70
+ status: "success",
71
+ },
72
+ }
73
+ }
74
+
75
+ return {
76
+ issues: [{ message: "status must be success" }],
77
+ }
78
+ })
79
+
80
+ const outputErrorSchema = createSchema<{ status: "error" }>((value) => {
81
+ if (typeof value === "object" && value !== null && (value as { status?: unknown }).status === "error") {
82
+ return {
83
+ value: {
84
+ status: "error",
85
+ },
86
+ }
87
+ }
88
+
89
+ return {
90
+ issues: [{ message: "status must be error" }],
91
+ }
92
+ })
93
+
94
+ interface TestApiCoreOptions {
95
+ url: string
96
+ method: string
97
+ headers?: ApiCoreHeaders | undefined
98
+ bodyText?: string | undefined
99
+ formData?: ApiCoreRequestFormData | undefined
100
+ }
101
+
102
+ class TestApiCore extends ApiCore {
103
+ readonly jsonPayloads: unknown[] = []
104
+ errorCallCount = 0
105
+
106
+ private readonly url: string
107
+ private readonly method: string
108
+ private readonly headers: ApiCoreHeaders
109
+ private readonly bodyText: string
110
+ private readonly formData: ApiCoreRequestFormData
111
+
112
+ constructor(options: TestApiCoreOptions) {
113
+ super()
114
+
115
+ this.url = options.url
116
+ this.method = options.method
117
+ this.headers = options.headers ?? {}
118
+ this.bodyText = options.bodyText ?? ""
119
+ this.formData = options.formData ?? []
120
+ }
121
+
122
+ async getRequestHeaders(): Promise<ApiCoreHeaders> {
123
+ return await Promise.resolve(this.headers)
124
+ }
125
+
126
+ async getRequestMethod(): Promise<string> {
127
+ return await Promise.resolve(this.method)
128
+ }
129
+
130
+ getRequestUrl(): string {
131
+ return this.url
132
+ }
133
+
134
+ async getRequestBodyText(): Promise<string> {
135
+ return await Promise.resolve(this.bodyText)
136
+ }
137
+
138
+ async getRequestBodyFormData(): Promise<ApiCoreRequestFormData> {
139
+ return await Promise.resolve(this.formData)
140
+ }
141
+
142
+ async text(): Promise<void> {
143
+ return await Promise.resolve()
144
+ }
145
+
146
+ async json(data: unknown): Promise<void> {
147
+ this.jsonPayloads.push(data)
148
+ return await Promise.resolve()
149
+ }
150
+
151
+ async stream(): Promise<ApiCoreStream> {
152
+ return await Promise.resolve({
153
+ write: async () => {
154
+ await Promise.resolve()
155
+ },
156
+ end: async () => {
157
+ await Promise.resolve()
158
+ },
159
+ })
160
+ }
161
+
162
+ async error(): Promise<void> {
163
+ this.errorCallCount = this.errorCallCount + 1
164
+ return await Promise.resolve()
165
+ }
166
+ }
167
+
168
+ class TestApiHost extends ApiHost<{ id: string }> {
169
+ apiCoreHandler: ApiCoreHandler | undefined
170
+ startCallCount = 0
171
+
172
+ constructor() {
173
+ super({ port: 0 })
174
+ }
175
+
176
+ attachApiCoreHandler(apiCoreHandler: ApiCoreHandler): void {
177
+ this.apiCoreHandler = apiCoreHandler
178
+ }
179
+
180
+ async start(): Promise<ApiHostStartResult> {
181
+ this.startCallCount = this.startCallCount + 1
182
+ this.server = { id: "server-1" }
183
+
184
+ return await Promise.resolve({
185
+ urlList: ["http://127.0.0.1:0"],
186
+ })
187
+ }
188
+
189
+ async close(): Promise<void> {
190
+ this.server = undefined
191
+ await Promise.resolve()
192
+ }
193
+
194
+ async dispatch(apiCore: ApiCore): Promise<void> {
195
+ if (this.apiCoreHandler === undefined) {
196
+ throw new Error("ApiCore handler is not attached.")
197
+ }
198
+
199
+ await this.apiCoreHandler(apiCore)
200
+ }
201
+ }
202
+
203
+ class TestSuccessResult extends SuccessResult<{ status: "success" }> {
204
+ private readonly handleCallback: (apiCore: ApiCore) => Promise<void>
205
+
206
+ constructor(handleCallback: (apiCore: ApiCore) => Promise<void>) {
207
+ super({})
208
+ this.handleCallback = handleCallback
209
+ }
210
+
211
+ async handle(apiCore: ApiCore): Promise<void> {
212
+ await this.handleCallback(apiCore)
213
+ }
214
+ }
215
+
216
+ const testApiSchema = createApiSchema<
217
+ string,
218
+ "get" | "post",
219
+ StandardSchemaV1<unknown, unknown>,
220
+ StandardSchemaV1<unknown, unknown>,
221
+ StandardSchemaV1<unknown, unknown>,
222
+ StandardSchemaV1<unknown, unknown>
223
+ >({
224
+ path: "/users",
225
+ method: "post",
226
+ inputQuerySchema,
227
+ inputBodySchema,
228
+ outputSuccessSchema,
229
+ outputErrorSchema,
230
+ })
231
+
232
+ test("ApiServer.start delegates to the configured ApiHost", async () => {
233
+ const apiHost = new TestApiHost()
234
+ const apiServer = createApiServer({
235
+ apiList: [],
236
+ apiHost,
237
+ })
238
+
239
+ const result = await apiServer.start()
240
+
241
+ expect(result).toEqual({
242
+ urlList: ["http://127.0.0.1:0"],
243
+ })
244
+ expect(apiHost.startCallCount).toBe(1)
245
+ })
246
+
247
+ test("ApiServer dispatches matched requests, validates input, and handles the Api result", async () => {
248
+ const apiHost = new TestApiHost()
249
+ const apiHandler = vi.fn(async (
250
+ context: ApiHandlerContext<
251
+ string,
252
+ "get" | "post",
253
+ StandardSchemaV1<unknown, unknown>,
254
+ StandardSchemaV1<unknown, unknown>,
255
+ StandardSchemaV1<unknown, unknown>,
256
+ StandardSchemaV1<unknown, unknown>
257
+ >,
258
+ ): Promise<TestSuccessResult> => {
259
+ await Promise.resolve()
260
+ return new TestSuccessResult(async (apiCore) => {
261
+ await apiCore.json({
262
+ path: context.path,
263
+ method: context.method,
264
+ inputQuery: context.inputQuery,
265
+ inputBody: context.inputBody,
266
+ })
267
+ })
268
+ })
269
+ const apiServer = createApiServer({
270
+ apiList: [createApi({ apiSchema: testApiSchema, apiHandler })],
271
+ apiHost,
272
+ })
273
+ expect(apiServer).toBeInstanceOf(ApiServer)
274
+
275
+ const apiCore = new TestApiCore({
276
+ url: "http://127.0.0.1/users?page=2",
277
+ method: "POST",
278
+ bodyText: '{"name":"Mobius"}',
279
+ })
280
+
281
+ await apiHost.dispatch(apiCore)
282
+
283
+ expect(apiHandler).toHaveBeenCalledTimes(1)
284
+ expect(apiHandler).toHaveBeenCalledWith(expect.objectContaining({
285
+ path: "/users",
286
+ method: "post",
287
+ inputQuery: {
288
+ page: "2",
289
+ },
290
+ inputBody: {
291
+ name: "Mobius",
292
+ },
293
+ apiCore,
294
+ }))
295
+ expect(apiCore.jsonPayloads).toEqual([
296
+ {
297
+ path: "/users",
298
+ method: "post",
299
+ inputQuery: {
300
+ page: "2",
301
+ },
302
+ inputBody: {
303
+ name: "Mobius",
304
+ },
305
+ },
306
+ ])
307
+ expect(apiCore.errorCallCount).toBe(0)
308
+ })
309
+
310
+ test("ApiServer calls ApiCore.error when no Api matches the request", async () => {
311
+ const apiHost = new TestApiHost()
312
+ const apiServer = createApiServer({
313
+ apiList: [],
314
+ apiHost,
315
+ })
316
+ expect(apiServer).toBeInstanceOf(ApiServer)
317
+
318
+ const apiCore = new TestApiCore({
319
+ url: "http://127.0.0.1/missing?page=2",
320
+ method: "GET",
321
+ })
322
+
323
+ await apiHost.dispatch(apiCore)
324
+
325
+ expect(apiCore.errorCallCount).toBe(1)
326
+ expect(apiCore.jsonPayloads).toEqual([])
327
+ })
328
+
329
+ test("ApiServer calls ApiCore.error when input validation fails", async () => {
330
+ const apiHost = new TestApiHost()
331
+ const apiHandler = vi.fn(async (
332
+ context: ApiHandlerContext<
333
+ string,
334
+ "get" | "post",
335
+ StandardSchemaV1<unknown, unknown>,
336
+ StandardSchemaV1<unknown, unknown>,
337
+ StandardSchemaV1<unknown, unknown>,
338
+ StandardSchemaV1<unknown, unknown>
339
+ >,
340
+ ): Promise<TestSuccessResult> => {
341
+ await Promise.resolve()
342
+ return new TestSuccessResult(async (apiCore) => {
343
+ await apiCore.json(context)
344
+ })
345
+ })
346
+ const apiServer = createApiServer({
347
+ apiList: [createApi({ apiSchema: testApiSchema, apiHandler })],
348
+ apiHost,
349
+ })
350
+ expect(apiServer).toBeInstanceOf(ApiServer)
351
+
352
+ const apiCore = new TestApiCore({
353
+ url: "http://127.0.0.1/users",
354
+ method: "POST",
355
+ bodyText: '{}',
356
+ })
357
+
358
+ await apiHost.dispatch(apiCore)
359
+
360
+ expect(apiHandler).not.toHaveBeenCalled()
361
+ expect(apiCore.errorCallCount).toBe(1)
362
+ expect(apiCore.jsonPayloads).toEqual([])
363
+ })
@@ -0,0 +1,117 @@
1
+ import { expect, test, vi } from "vitest"
2
+
3
+ import type { StandardSchemaV1 } from "@standard-schema/spec"
4
+ import { createApiSchema, createApiTest, testApi } from "#Source/http/index.ts"
5
+
6
+ const createSchema = <Output>(
7
+ validate: (value: unknown) => StandardSchemaV1.Result<Output>,
8
+ ): StandardSchemaV1<unknown, Output> => {
9
+ return {
10
+ "~standard": {
11
+ version: 1 as const,
12
+ vendor: "mobius-http-test",
13
+ validate,
14
+ },
15
+ }
16
+ }
17
+
18
+ const passthroughSchema = createSchema<unknown>((value) => {
19
+ return {
20
+ value,
21
+ }
22
+ })
23
+
24
+ const apiSchema = createApiSchema({
25
+ path: "/health",
26
+ method: "get",
27
+ inputQuerySchema: passthroughSchema,
28
+ inputBodySchema: passthroughSchema,
29
+ outputSuccessSchema: passthroughSchema,
30
+ outputErrorSchema: passthroughSchema,
31
+ })
32
+
33
+ const beforeApiTestHandler = vi.fn(async () => {
34
+ await Promise.resolve()
35
+ return {
36
+ token: "before",
37
+ }
38
+ })
39
+
40
+ const apiTestHandler = vi.fn(async () => {
41
+ await Promise.resolve()
42
+ return {
43
+ token: "during",
44
+ }
45
+ })
46
+
47
+ const afterApiTestHandler = vi.fn(async () => {
48
+ await Promise.resolve()
49
+ return {
50
+ token: "after",
51
+ }
52
+ })
53
+
54
+ const apiTest = createApiTest({
55
+ apiSchema,
56
+ beforeApiTestHandler,
57
+ apiTestHandler,
58
+ afterApiTestHandler,
59
+ })
60
+
61
+ test("ApiTest.getApiSchema returns the configured schema", () => {
62
+ expect(apiTest.getApiSchema()).toBe(apiSchema)
63
+ })
64
+
65
+ test("ApiTest.getBeforeApiTestHandler returns the configured before handler", () => {
66
+ expect(apiTest.getBeforeApiTestHandler()).toBe(beforeApiTestHandler)
67
+ })
68
+
69
+ test("ApiTest.getApiTestHandler returns the configured test handler", () => {
70
+ expect(apiTest.getApiTestHandler()).toBe(apiTestHandler)
71
+ })
72
+
73
+ test("ApiTest.getAfterApiTestHandler returns the configured after handler", () => {
74
+ expect(apiTest.getAfterApiTestHandler()).toBe(afterApiTestHandler)
75
+ })
76
+
77
+ test("testApi executes the configured handlers in order and forwards intermediate results", async () => {
78
+ const callOrder: string[] = []
79
+
80
+ const orchestratedApiTest = createApiTest({
81
+ apiSchema,
82
+ beforeApiTestHandler: async (context) => {
83
+ await Promise.resolve()
84
+ callOrder.push(`before:${context.apiSchema.getPath()}`)
85
+ return {
86
+ requestId: "r1",
87
+ }
88
+ },
89
+ apiTestHandler: async (context) => {
90
+ await Promise.resolve()
91
+ callOrder.push(`test:${context.beforeApiTestResult.requestId}`)
92
+ return {
93
+ statusCode: 200,
94
+ }
95
+ },
96
+ afterApiTestHandler: async (context) => {
97
+ await Promise.resolve()
98
+ callOrder.push(`after:${context.apiTestResult.statusCode}`)
99
+ return {
100
+ summary: `${context.apiSchema.getMethod()} ${context.beforeApiTestResult.requestId}`,
101
+ }
102
+ },
103
+ })
104
+
105
+ const result = await testApi({
106
+ apiTest: orchestratedApiTest,
107
+ })
108
+
109
+ expect(callOrder).toEqual([
110
+ "before:/health",
111
+ "test:r1",
112
+ "after:200",
113
+ ])
114
+ expect(result).toEqual({
115
+ summary: "get r1",
116
+ })
117
+ })
@@ -0,0 +1,121 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import type { StandardSchemaV1 } from "@standard-schema/spec"
4
+ import type { ApiHandler } from "#Source/http/index.ts"
5
+ import { SuccessResult, createApi, createApiSchema } from "#Source/http/index.ts"
6
+
7
+ const isRecord = (value: unknown): value is Record<string, unknown> => {
8
+ return typeof value === "object" && value !== null
9
+ }
10
+
11
+ const createSchema = <Output>(
12
+ validate: (value: unknown) => StandardSchemaV1.Result<Output>,
13
+ ): StandardSchemaV1<unknown, Output> => {
14
+ return {
15
+ "~standard": {
16
+ version: 1 as const,
17
+ vendor: "mobius-http-test",
18
+ validate,
19
+ },
20
+ }
21
+ }
22
+
23
+ class TestSuccessResult extends SuccessResult<{ status: "success" }> {
24
+ constructor() {
25
+ super({})
26
+ }
27
+
28
+ async handle(): Promise<void> {
29
+ await Promise.resolve()
30
+ }
31
+ }
32
+
33
+ const inputQuerySchema = createSchema<{ page: string }>((value) => {
34
+ if (isRecord(value) && typeof value["page"] === "string") {
35
+ return {
36
+ value: {
37
+ page: value["page"],
38
+ },
39
+ }
40
+ }
41
+
42
+ return {
43
+ issues: [{ message: "page is required" }],
44
+ }
45
+ })
46
+
47
+ const inputBodySchema = createSchema<{ name: string }>((value) => {
48
+ if (isRecord(value) && typeof value["name"] === "string") {
49
+ return {
50
+ value: {
51
+ name: value["name"],
52
+ },
53
+ }
54
+ }
55
+
56
+ return {
57
+ issues: [{ message: "name is required" }],
58
+ }
59
+ })
60
+
61
+ const outputSuccessSchema = createSchema<{ status: "success" }>((value) => {
62
+ if (typeof value === "object" && value !== null && (value as { status?: unknown }).status === "success") {
63
+ return {
64
+ value: {
65
+ status: "success",
66
+ },
67
+ }
68
+ }
69
+
70
+ return {
71
+ issues: [{ message: "status must be success" }],
72
+ }
73
+ })
74
+
75
+ const outputErrorSchema = createSchema<{ status: "error" }>((value) => {
76
+ if (typeof value === "object" && value !== null && (value as { status?: unknown }).status === "error") {
77
+ return {
78
+ value: {
79
+ status: "error",
80
+ },
81
+ }
82
+ }
83
+
84
+ return {
85
+ issues: [{ message: "status must be error" }],
86
+ }
87
+ })
88
+
89
+ const apiSchema = createApiSchema({
90
+ path: "/users",
91
+ method: "post",
92
+ inputQuerySchema,
93
+ inputBodySchema,
94
+ outputSuccessSchema,
95
+ outputErrorSchema,
96
+ })
97
+
98
+ const apiHandler: ApiHandler<
99
+ "/users",
100
+ "post",
101
+ typeof inputQuerySchema,
102
+ typeof inputBodySchema,
103
+ typeof outputSuccessSchema,
104
+ typeof outputErrorSchema
105
+ > = async () => {
106
+ await Promise.resolve()
107
+ return new TestSuccessResult()
108
+ }
109
+
110
+ const api = createApi({
111
+ apiSchema,
112
+ apiHandler,
113
+ })
114
+
115
+ test("Api.getApiSchema returns the configured schema", () => {
116
+ expect(api.getApiSchema()).toBe(apiSchema)
117
+ })
118
+
119
+ test("Api.getApiHandler returns the configured handler", () => {
120
+ expect(api.getApiHandler()).toBe(apiHandler)
121
+ })