@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,107 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import {
4
+ isSearchIncludes,
5
+ isSearchLooseEqual,
6
+ isSearchLooseIncludes,
7
+ isSearchStrictEqual,
8
+ isSearchStrictIncludes,
9
+ neatenQueryString,
10
+ neatenSearch,
11
+ queryObjectToQueryString,
12
+ queryObjectToSearch,
13
+ queryStringToQueryObject,
14
+ queryStringToSearch,
15
+ searchToQueryObject,
16
+ searchToQueryString,
17
+ toQueryObject,
18
+ toQueryString,
19
+ toSearch,
20
+ } from "#Source/route/index.ts"
21
+
22
+ test("neatenSearch ensures the search string starts with a question mark", () => {
23
+ expect(neatenSearch("a=1&b=2&c=3")).toBe("?a=1&b=2&c=3")
24
+ expect(neatenSearch("?a=1&b=2&c=3")).toBe("?a=1&b=2&c=3")
25
+ })
26
+
27
+ test("neatenQueryString removes a leading question mark when present", () => {
28
+ expect(neatenQueryString("?name=mobius")).toBe("name=mobius")
29
+ expect(neatenQueryString("name=mobius")).toBe("name=mobius")
30
+ })
31
+
32
+ test("queryStringToQueryObject decodes keys and values and preserves later equals signs", () => {
33
+ expect(queryStringToQueryObject("?na%20me=mobius%20model&formula=a%3Db&empty="))
34
+ .toEqual({ "na me": "mobius model", formula: "a=b", empty: "" })
35
+ expect(queryStringToQueryObject("a=1&b=2&c=3")).toEqual({ a: "1", b: "2", c: "3" })
36
+ expect(queryStringToQueryObject("?a=1&b=2&c=3")).toEqual({ a: "1", b: "2", c: "3" })
37
+ })
38
+
39
+ test("queryObjectToQueryString encodes keys and values into a query string", () => {
40
+ expect(queryObjectToQueryString({ "na me": "mobius model", formula: "a=b" }))
41
+ .toBe("na%20me=mobius%20model&formula=a%3Db")
42
+ expect(queryObjectToQueryString({ a: "1", b: "2", c: "3" })).toBe("a=1&b=2&c=3")
43
+ })
44
+
45
+ test("searchToQueryString aliases query-string normalization for search input", () => {
46
+ expect(searchToQueryString("?a=1&b=2&c=3")).toBe("a=1&b=2&c=3")
47
+ })
48
+
49
+ test("searchToQueryObject parses search input into a query object", () => {
50
+ expect(searchToQueryObject("?name=mobius&mode=stable")).toEqual({ name: "mobius", mode: "stable" })
51
+ expect(searchToQueryObject("?a=1&b=2&c=3")).toEqual({ a: "1", b: "2", c: "3" })
52
+ })
53
+
54
+ test("queryStringToSearch aliases search normalization for query-string input", () => {
55
+ expect(queryStringToSearch("a=1&b=2&c=3")).toBe("?a=1&b=2&c=3")
56
+ })
57
+
58
+ test("queryObjectToSearch serializes a query object into search format", () => {
59
+ expect(queryObjectToSearch({ name: "mobius model", mode: "a=b" })).toBe("?name=mobius%20model&mode=a%3Db")
60
+ expect(queryObjectToSearch({ a: "1", b: "2", c: "3" })).toBe("?a=1&b=2&c=3")
61
+ })
62
+
63
+ test("toSearch returns search format for both strings and objects", () => {
64
+ expect(toSearch("a=1&b=2&c=3")).toBe("?a=1&b=2&c=3")
65
+ expect(toSearch("?a=1&b=2&c=3")).toBe("?a=1&b=2&c=3")
66
+ expect(toSearch({ a: "1", b: "2", c: "3" })).toBe("?a=1&b=2&c=3")
67
+ })
68
+
69
+ test("toQueryString returns query-string format for both strings and objects", () => {
70
+ expect(toQueryString("a=1&b=2&c=3")).toBe("a=1&b=2&c=3")
71
+ expect(toQueryString("?a=1&b=2&c=3")).toBe("a=1&b=2&c=3")
72
+ expect(toQueryString({ a: "1", b: "2", c: "3" })).toBe("a=1&b=2&c=3")
73
+ })
74
+
75
+ test("queryObject returns parsed query objects for both strings and objects", () => {
76
+ const queryObject = { a: "1", b: "2", c: "3" }
77
+
78
+ expect(toQueryObject("a=1&b=2&c=3")).toEqual({ a: "1", b: "2", c: "3" })
79
+ expect(toQueryObject("?a=1&b=2&c=3")).toEqual({ a: "1", b: "2", c: "3" })
80
+ expect(toQueryObject(queryObject)).toBe(queryObject)
81
+ expect(toQueryObject("")).toEqual({})
82
+ })
83
+
84
+ test("isSearchLooseIncludes checks whether all requested query keys exist", () => {
85
+ expect(isSearchLooseIncludes("?a=1&b=2", ["a"])).toBe(true)
86
+ expect(isSearchLooseIncludes("?a=1&b=2", ["a", "c"])).toBe(false)
87
+ })
88
+
89
+ test("isSearchStrictIncludes checks whether query key sets are identical", () => {
90
+ expect(isSearchStrictIncludes("?a=1&b=2", ["b", "a"])).toBe(true)
91
+ expect(isSearchStrictIncludes("?ab=1&c=2", ["a", "bc"])).toBe(false)
92
+ })
93
+
94
+ test("isSearchIncludes supports required and optional query keys", () => {
95
+ expect(isSearchIncludes("?a=1&b=2", { a: "required", b: "optional" })).toBe(true)
96
+ expect(isSearchIncludes("?a=1&b=2", { a: "required" })).toBe(false)
97
+ })
98
+
99
+ test("isSearchLooseEqual checks whether target query values are a matching subset", () => {
100
+ expect(isSearchLooseEqual("?a=1&b=2", "?a=1")).toBe(true)
101
+ expect(isSearchLooseEqual("?a=1&b=2", { a: "1", c: "3" })).toBe(false)
102
+ })
103
+
104
+ test("isSearchStrictEqual checks whether query key sets and values are identical", () => {
105
+ expect(isSearchStrictEqual("?a=1&b=2", "?b=2&a=1")).toBe(true)
106
+ expect(isSearchStrictEqual("?a=1&b=2", "?a=1")).toBe(false)
107
+ })
@@ -0,0 +1,49 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import type { SingletonItem } from "#Source/singleton/index.ts"
4
+ import {
5
+ getSingletonFactory,
6
+ singletonCollection,
7
+ singletonItem,
8
+ } from "#Source/singleton/index.ts"
9
+
10
+ test("getSingletonFactory caches first produced value", () => {
11
+ let makeCalls = 0
12
+ const createSingleton = getSingletonFactory(() => {
13
+ makeCalls = makeCalls + 1
14
+ return { value: "singleton" }
15
+ })
16
+
17
+ const first = createSingleton()
18
+ const second = createSingleton()
19
+
20
+ expect(first).toBe(second)
21
+ expect(first.value).toBe("singleton")
22
+ expect(makeCalls).toBe(1)
23
+ })
24
+
25
+ test("singletonItem creates named lazy singleton item", () => {
26
+ let valueCalls = 0
27
+ const item = singletonItem("config", () => {
28
+ valueCalls = valueCalls + 1
29
+ return { enabled: true }
30
+ })
31
+
32
+ const first = item.getValue()
33
+ const second = item.getValue()
34
+
35
+ expect(item.getName()).toBe("config")
36
+ expect(first).toBe(second)
37
+ expect(first.enabled).toBe(true)
38
+ expect(valueCalls).toBe(1)
39
+ })
40
+
41
+ test("singletonCollection resolves named items and throws for missing names", () => {
42
+ const portItem: SingletonItem<string, number> = singletonItem("port", () => 3_000)
43
+ const modeItem: SingletonItem<string, string> = singletonItem("mode", () => "test")
44
+ const collection = singletonCollection([portItem, modeItem])
45
+
46
+ expect(collection.getItem("port")).toBe(3_000)
47
+ expect(collection.getItem("mode")).toBe("test")
48
+ expect(() => collection.getItem("missing")).toThrow("GlobalService: item missing not found")
49
+ })
@@ -0,0 +1,208 @@
1
+ import { afterEach, expect, test, vi } from "vitest"
2
+
3
+ import { Client } from "#Source/socket/index.ts"
4
+
5
+ type FakeEventMap = {
6
+ close: { code: number; reason: string }
7
+ error: Event
8
+ message: { data: string }
9
+ open: Event
10
+ }
11
+
12
+ class FakeWebSocket {
13
+ static readonly CONNECTING = 0
14
+ static readonly OPEN = 1
15
+ static readonly CLOSING = 2
16
+ static readonly CLOSED = 3
17
+ static instances: FakeWebSocket[] = []
18
+
19
+ readonly url: string
20
+ readonly sentMessages: string[]
21
+ readyState: number
22
+
23
+ private readonly listeners: {
24
+ [K in keyof FakeEventMap]: Set<(event: FakeEventMap[K]) => void>
25
+ }
26
+
27
+ constructor(url: string) {
28
+ this.url = url
29
+ this.sentMessages = []
30
+ this.readyState = FakeWebSocket.CONNECTING
31
+ this.listeners = {
32
+ close: new Set(),
33
+ error: new Set(),
34
+ message: new Set(),
35
+ open: new Set(),
36
+ }
37
+
38
+ FakeWebSocket.instances.push(this)
39
+ }
40
+
41
+ addEventListener<K extends keyof FakeEventMap>(type: K, listener: (event: FakeEventMap[K]) => void): void {
42
+ this.listeners[type].add(listener)
43
+ }
44
+
45
+ removeEventListener<K extends keyof FakeEventMap>(type: K, listener: (event: FakeEventMap[K]) => void): void {
46
+ this.listeners[type].delete(listener)
47
+ }
48
+
49
+ send(message: string): void {
50
+ if (this.readyState !== FakeWebSocket.OPEN) {
51
+ throw new Error("FakeWebSocket is not open.")
52
+ }
53
+
54
+ this.sentMessages.push(message)
55
+ }
56
+
57
+ close(): void {
58
+ if (this.readyState === FakeWebSocket.CLOSED) {
59
+ return
60
+ }
61
+
62
+ this.readyState = FakeWebSocket.CLOSED
63
+ this.emit("close", { code: 1_000, reason: "" })
64
+ }
65
+
66
+ simulateOpen(): void {
67
+ this.readyState = FakeWebSocket.OPEN
68
+ this.emit("open", new Event("open"))
69
+ }
70
+
71
+ simulateMessage(message: unknown): void {
72
+ this.emit("message", { data: JSON.stringify(message) })
73
+ }
74
+
75
+ simulateError(): void {
76
+ this.emit("error", new Event("error"))
77
+ }
78
+
79
+ private emit<K extends keyof FakeEventMap>(type: K, event: FakeEventMap[K]): void {
80
+ for (const listener of this.listeners[type]) {
81
+ listener(event)
82
+ }
83
+ }
84
+ }
85
+
86
+ afterEach(() => {
87
+ FakeWebSocket.instances = []
88
+ vi.unstubAllGlobals()
89
+ vi.restoreAllMocks()
90
+ })
91
+
92
+ test("Client.SocketUnit queues work until ready, deduplicates status events, and keeps runtime listeners after error", async () => {
93
+ vi.stubGlobal("WebSocket", FakeWebSocket)
94
+
95
+ const socketUnit = new Client.SocketUnit<{ type: string }>({
96
+ url: "ws://socket.test",
97
+ })
98
+ const statuses: string[] = []
99
+ const messages: Array<{ type: string }> = []
100
+
101
+ socketUnit.eventManager.subscribe("status", (status) => {
102
+ statuses.push(status)
103
+ })
104
+ socketUnit.eventManager.subscribe("message", (message) => {
105
+ messages.push(message)
106
+ })
107
+
108
+ socketUnit.setClientId("client-1")
109
+ socketUnit.sendMessage({ type: "queued-before-open" })
110
+
111
+ expect(socketUnit.getSnapshot().pendingActionCount).toBe(1)
112
+
113
+ const openPromise = socketUnit.open()
114
+ const firstWebSocket = FakeWebSocket.instances[0]!
115
+
116
+ expect(socketUnit.getStatus()).toBe("CONNECTING")
117
+
118
+ firstWebSocket.simulateOpen()
119
+ await openPromise
120
+
121
+ expect(firstWebSocket.sentMessages).toEqual([
122
+ JSON.stringify({ type: "queued-before-open" }),
123
+ ])
124
+ expect(statuses).toEqual(["CONNECTING", "OPEN"])
125
+
126
+ firstWebSocket.simulateMessage({ type: "message-1" })
127
+ firstWebSocket.simulateMessage({ type: "message-2" })
128
+ firstWebSocket.simulateError()
129
+ firstWebSocket.simulateMessage({ type: "message-3" })
130
+
131
+ expect(messages).toEqual([
132
+ { type: "message-1" },
133
+ { type: "message-2" },
134
+ { type: "message-3" },
135
+ ])
136
+ expect(statuses).toEqual(["CONNECTING", "OPEN"])
137
+
138
+ await expect(socketUnit.safeOpen()).rejects.toThrow("Socket is already open")
139
+
140
+ await socketUnit.close()
141
+ expect(socketUnit.getStatus()).toBe("UNINSTANTIATED")
142
+
143
+ let resetCount = 0
144
+ await socketUnit.reset(() => {
145
+ resetCount = resetCount + 1
146
+ })
147
+
148
+ expect(resetCount).toBe(1)
149
+ expect(socketUnit.getSnapshot()).toMatchObject({
150
+ clientId: undefined,
151
+ pendingActionCount: 0,
152
+ status: "UNINSTANTIATED",
153
+ })
154
+ })
155
+
156
+ test("Client.Socket emits connect close and message events from stable ready-to-work transitions", async () => {
157
+ vi.stubGlobal("WebSocket", FakeWebSocket)
158
+
159
+ const socket = new Client.Socket<{ type: string }>({
160
+ url: "ws://socket.test",
161
+ })
162
+ const connectClientIds: Array<string | undefined> = []
163
+ const closeClientIds: Array<string | undefined> = []
164
+ const forwardedMessages: Array<{ clientId: string; message: { type: string } }> = []
165
+
166
+ socket.eventManager.subscribe("connect", ({ clientId }) => {
167
+ connectClientIds.push(clientId)
168
+ })
169
+ socket.eventManager.subscribe("close", ({ clientId }) => {
170
+ closeClientIds.push(clientId)
171
+ })
172
+ socket.eventManager.subscribe("message", (payload) => {
173
+ forwardedMessages.push(payload)
174
+ })
175
+
176
+ socket.setClientId("client-2")
177
+ socket.sendMessage({ type: "queued-before-open" })
178
+
179
+ const openPromise = socket.open()
180
+ const firstWebSocket = FakeWebSocket.instances[0]!
181
+ firstWebSocket.simulateOpen()
182
+ await openPromise
183
+
184
+ expect(connectClientIds).toEqual(["client-2"])
185
+ expect(firstWebSocket.sentMessages).toEqual([
186
+ JSON.stringify({ type: "queued-before-open" }),
187
+ ])
188
+
189
+ firstWebSocket.simulateMessage({ type: "hello" })
190
+ firstWebSocket.simulateMessage({ type: "hello-again" })
191
+
192
+ expect(forwardedMessages).toEqual([
193
+ { clientId: "client-2", message: { type: "hello" } },
194
+ { clientId: "client-2", message: { type: "hello-again" } },
195
+ ])
196
+ expect(connectClientIds).toEqual(["client-2"])
197
+
198
+ await socket.close()
199
+ expect(closeClientIds).toEqual(["client-2"])
200
+
201
+ socket.reset()
202
+
203
+ expect(socket.getSnapshot()).toMatchObject({
204
+ clientId: "client-2",
205
+ isReadyToWork: false,
206
+ status: "UNINSTANTIATED",
207
+ })
208
+ })
@@ -0,0 +1,135 @@
1
+ import type { AddressInfo } from "node:net"
2
+
3
+ import { afterEach, expect, test, vi } from "vitest"
4
+ import { WebSocket as WsClient } from "ws"
5
+
6
+ import { Server } from "#Source/socket/index.ts"
7
+
8
+ afterEach(() => {
9
+ vi.restoreAllMocks()
10
+ })
11
+
12
+ test("Server.createWebSocketServer returns usable local URLs and stable lifecycle helpers", async () => {
13
+ const webSocketServer = Server.createWebSocketServer({ port: 0 })
14
+
15
+ expect(webSocketServer.isRunning()).toBe(false)
16
+
17
+ const runningResult = await webSocketServer.run()
18
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
19
+ const address = webSocketServer.safeGetServer().address() as AddressInfo
20
+
21
+ expect(runningResult.server).toBe(webSocketServer.safeGetServer())
22
+ expect(runningResult.urlList).toContain(`ws://127.0.0.1:${address.port}`)
23
+ expect(runningResult.urlList).toContain(`ws://localhost:${address.port}`)
24
+ expect(runningResult.urlList).toContain(`ws://[::1]:${address.port}`)
25
+ expect(webSocketServer.isRunning()).toBe(true)
26
+
27
+ await webSocketServer.close()
28
+
29
+ expect(webSocketServer.isRunning()).toBe(false)
30
+ })
31
+
32
+ test("Server.Socket attach does not take over an already running server lifecycle", async () => {
33
+ const webSocketServer = Server.createWebSocketServer({ port: 0 })
34
+ await webSocketServer.run()
35
+
36
+ const socket = new Server.Socket<{ type: string }>({
37
+ webSocketServer,
38
+ onMessage: () => {
39
+ // no op
40
+ },
41
+ })
42
+
43
+ socket.attach()
44
+
45
+ expect(socket.getSnapshot()).toMatchObject({
46
+ isRunning: true,
47
+ isStarted: true,
48
+ managesWebSocketServerLifecycle: false,
49
+ })
50
+
51
+ await socket.close()
52
+
53
+ expect(webSocketServer.isRunning()).toBe(true)
54
+
55
+ await webSocketServer.close()
56
+ })
57
+
58
+ test("Server.Socket exposes stable connect timing and forwards messages for active connections", async () => {
59
+ const webSocketServer = Server.createWebSocketServer({ port: 0 })
60
+ const socket = new Server.Socket<{ type: string }>({
61
+ webSocketServer,
62
+ onMessage: () => {
63
+ // no-op
64
+ },
65
+ })
66
+
67
+ const connectPromise = new Promise<string>((resolve) => {
68
+ socket.eventManager.subscribe("connect", ({ clientId }) => {
69
+ const socketUnitSnapshot = socket.getSocketUnitSnapshot(clientId)
70
+
71
+ expect(socketUnitSnapshot).toMatchObject({
72
+ clientId,
73
+ isReadyToWork: true,
74
+ status: "OPEN",
75
+ })
76
+
77
+ resolve(clientId)
78
+ })
79
+ })
80
+ const inboundMessagePromise = new Promise<{ clientId: string; message: { type: string } }>((resolve) => {
81
+ socket.eventManager.subscribe("message", (payload) => {
82
+ resolve(payload)
83
+ })
84
+ })
85
+ const closePromise = new Promise<string>((resolve) => {
86
+ socket.eventManager.subscribe("close", ({ clientId }) => {
87
+ resolve(clientId)
88
+ })
89
+ })
90
+
91
+ await socket.open()
92
+
93
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
94
+ const address = webSocketServer.safeGetServer().address() as AddressInfo
95
+ const client = new WsClient(`ws://127.0.0.1:${address.port}`)
96
+
97
+ await new Promise<void>((resolve, reject) => {
98
+ client.once("open", () => {
99
+ resolve()
100
+ })
101
+ client.once("error", reject)
102
+ })
103
+
104
+ const clientId = await connectPromise
105
+
106
+ client.send(JSON.stringify({ type: "hello-server" }))
107
+
108
+ await expect(inboundMessagePromise).resolves.toEqual({
109
+ clientId,
110
+ message: { type: "hello-server" },
111
+ })
112
+
113
+ const outboundMessagePromise = new Promise<string>((resolve, reject) => {
114
+ client.once("message", (message) => {
115
+ resolve(message.toString())
116
+ })
117
+ client.once("error", reject)
118
+ })
119
+
120
+ socket.sendMessage(clientId, { type: "hello-client" })
121
+
122
+ await expect(outboundMessagePromise).resolves.toBe(JSON.stringify({ type: "hello-client" }))
123
+
124
+ client.close()
125
+
126
+ await expect(closePromise).resolves.toBe(clientId)
127
+
128
+ await socket.close()
129
+
130
+ expect(socket.getSnapshot()).toMatchObject({
131
+ activeSocketUnitCount: 0,
132
+ isRunning: false,
133
+ isStarted: false,
134
+ })
135
+ })