@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,191 @@
1
+ import type { AddressInfo } from "node:net"
2
+
3
+ import { afterEach, expect, test } from "vitest"
4
+
5
+ import { Http } from "#Source/index.ts"
6
+
7
+ afterEach(async () => {
8
+ await Promise.resolve()
9
+ })
10
+
11
+ test("ApiHostNodeHttp exposes usable URLs and forwards request data to ApiCoreNodeHttp", async () => {
12
+ const apiHost = Http.createApiHostNodeHttp({ port: 0 })
13
+
14
+ apiHost.attachApiCoreHandler(async (apiCore) => {
15
+ const path = await apiCore.getRequestPath()
16
+ const method = await apiCore.getRequestMethod()
17
+ const search = await apiCore.getRequestSearch()
18
+ const bodyJson = await apiCore.getRequestBodyJson()
19
+
20
+ await apiCore.json({
21
+ path,
22
+ method,
23
+ search,
24
+ bodyJson,
25
+ })
26
+ })
27
+
28
+ const startResult = await apiHost.start()
29
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
30
+ const address = apiHost.safeGetServer().address() as AddressInfo
31
+
32
+ expect(startResult.urlList).toContain(`http://127.0.0.1:${address.port}`)
33
+ expect(startResult.urlList).toContain(`http://localhost:${address.port}`)
34
+ expect(startResult.urlList).toContain(`http://[::1]:${address.port}`)
35
+
36
+ const response = await fetch(`http://127.0.0.1:${address.port}/users?page=2`, {
37
+ method: "POST",
38
+ headers: {
39
+ "Content-Type": "application/json",
40
+ },
41
+ body: JSON.stringify({ name: "Mobius" }),
42
+ })
43
+
44
+ await expect(response.json()).resolves.toEqual({
45
+ path: "/users",
46
+ method: "POST",
47
+ search: "?page=2",
48
+ bodyJson: { name: "Mobius" },
49
+ })
50
+
51
+ await apiHost.close()
52
+
53
+ expect(apiHost.isRunning()).toBe(false)
54
+ })
55
+
56
+ test("ApiCoreNodeHttp returns a stable error response when the handler throws", async () => {
57
+ const apiHost = Http.createApiHostNodeHttp({ port: 0 })
58
+
59
+ apiHost.attachApiCoreHandler(() => {
60
+ throw new Error("boom")
61
+ })
62
+
63
+ await apiHost.start()
64
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
65
+ const address = apiHost.safeGetServer().address() as AddressInfo
66
+
67
+ const response = await fetch(`http://127.0.0.1:${address.port}/error`, {
68
+ method: "GET",
69
+ })
70
+
71
+ expect(response.status).toBe(500)
72
+ await expect(response.json()).resolves.toEqual({
73
+ status: "error",
74
+ data: {
75
+ reason: "internal_server_error",
76
+ },
77
+ })
78
+
79
+ await apiHost.close()
80
+ })
81
+
82
+ test("ApiCoreNodeHttp treats an empty request body as an empty object", async () => {
83
+ const apiHost = Http.createApiHostNodeHttp({ port: 0 })
84
+
85
+ apiHost.attachApiCoreHandler(async (apiCore) => {
86
+ await apiCore.json({
87
+ bodyJson: await apiCore.getRequestBodyJson(),
88
+ })
89
+ })
90
+
91
+ await apiHost.start()
92
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
93
+ const address = apiHost.safeGetServer().address() as AddressInfo
94
+
95
+ const response = await fetch(`http://127.0.0.1:${address.port}/empty`, {
96
+ method: "GET",
97
+ })
98
+
99
+ await expect(response.json()).resolves.toEqual({
100
+ bodyJson: {},
101
+ })
102
+
103
+ await apiHost.close()
104
+ })
105
+
106
+ test("ApiCoreNodeHttp exposes normalized headers and parses multipart form data", async () => {
107
+ const apiHost = Http.createApiHostNodeHttp({ port: 0 })
108
+
109
+ apiHost.attachApiCoreHandler(async (apiCore) => {
110
+ const headers = await apiCore.getRequestHeaders()
111
+ const formData = await apiCore.getRequestBodyFormData()
112
+ const files = await apiCore.getRequestFiles()
113
+
114
+ await apiCore.json({
115
+ contentType: headers["content-type"],
116
+ customHeader: await apiCore.getRequestHeader("X-Custom-Header"),
117
+ formData: formData.map((entry) => {
118
+ if (entry.value instanceof File) {
119
+ return {
120
+ name: entry.name,
121
+ value: {
122
+ kind: "file",
123
+ name: entry.value.name,
124
+ size: entry.value.size,
125
+ type: entry.value.type,
126
+ },
127
+ }
128
+ }
129
+
130
+ return {
131
+ name: entry.name,
132
+ value: entry.value,
133
+ }
134
+ }),
135
+ files: files.map((file) => {
136
+ return {
137
+ name: file.name,
138
+ fileName: file.value.name,
139
+ size: file.value.size,
140
+ type: file.value.type,
141
+ }
142
+ }),
143
+ })
144
+ })
145
+
146
+ await apiHost.start()
147
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
148
+ const address = apiHost.safeGetServer().address() as AddressInfo
149
+
150
+ const formData = new FormData()
151
+ formData.append("user", "Mobius")
152
+ formData.append("avatar", new File(["hello"], "avatar.txt", { type: "text/plain" }))
153
+
154
+ const response = await fetch(`http://127.0.0.1:${address.port}/upload`, {
155
+ method: "POST",
156
+ headers: {
157
+ "X-Custom-Header": "alpha",
158
+ },
159
+ body: formData,
160
+ })
161
+
162
+ await expect(response.json()).resolves.toEqual({
163
+ contentType: [expect.stringContaining("multipart/form-data")],
164
+ customHeader: ["alpha"],
165
+ formData: [
166
+ {
167
+ name: "user",
168
+ value: "Mobius",
169
+ },
170
+ {
171
+ name: "avatar",
172
+ value: {
173
+ kind: "file",
174
+ name: "avatar.txt",
175
+ size: 5,
176
+ type: "text/plain",
177
+ },
178
+ },
179
+ ],
180
+ files: [
181
+ {
182
+ name: "avatar",
183
+ fileName: "avatar.txt",
184
+ size: 5,
185
+ type: "text/plain",
186
+ },
187
+ ],
188
+ })
189
+
190
+ await apiHost.close()
191
+ })
@@ -0,0 +1,71 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import {
4
+ assertId,
5
+ generateId,
6
+ idToString,
7
+ isEqualId,
8
+ isId,
9
+ isSameId,
10
+ } from "#Source/identifier/index.ts"
11
+
12
+ test("generateId is seed-stable and no-seed calls are uncached", () => {
13
+ const first = generateId("user:42")
14
+ const second = generateId("user:42")
15
+ const third = generateId("user:43")
16
+ const withoutSeed1 = generateId()
17
+ const withoutSeed2 = generateId()
18
+ const undefinedSeed1 = generateId(undefined)
19
+ const undefinedSeed2 = generateId(undefined)
20
+
21
+ expect(first).toBe(second)
22
+ expect(first).not.toBe(third)
23
+ expect(withoutSeed1).not.toBe(withoutSeed2)
24
+ expect(undefinedSeed1).not.toBe(undefinedSeed2)
25
+ expect(withoutSeed1).not.toBe(undefinedSeed1)
26
+ })
27
+
28
+ test("isId validates generated Id values", () => {
29
+ const id = generateId(1)
30
+
31
+ expect(isId(id)).toBe(true)
32
+ expect(isId(null)).toBe(false)
33
+ expect(isId(undefined)).toBe(false)
34
+ expect(isId("plain-string")).toBe(false)
35
+ expect(isId({})).toBe(false)
36
+ })
37
+
38
+ test("assertId throws for non-Id inputs", () => {
39
+ const id = generateId(Symbol("seed"))
40
+
41
+ expect(() => assertId(id)).not.toThrow()
42
+ expect(() => assertId("plain-string")).toThrow(TypeError)
43
+ expect(() => assertId(1)).toThrow(TypeError)
44
+ })
45
+
46
+ test("isSameId compares by instance identity", () => {
47
+ const id = generateId("same")
48
+ const sameReference = id
49
+ const another = generateId("another")
50
+
51
+ expect(isSameId(id, sameReference)).toBe(true)
52
+ expect(isSameId(id, another)).toBe(false)
53
+ })
54
+
55
+ test("isEqualId compares internal ID value", () => {
56
+ const first = generateId("equal-seed")
57
+ const second = generateId("equal-seed")
58
+ const third = generateId("different-seed")
59
+
60
+ expect(isEqualId(first, second)).toBe(true)
61
+ expect(isEqualId(first, third)).toBe(false)
62
+ })
63
+
64
+ test("idToString serializes Id to readable label", () => {
65
+ const id = generateId("to-string")
66
+ const asString = idToString(id)
67
+
68
+ expect(asString.startsWith("Symbol-ID-")).toBe(true)
69
+ // oxlint-disable-next-line typescript/no-base-to-string
70
+ expect(asString).toBe(String(id))
71
+ })
@@ -0,0 +1,85 @@
1
+ import { afterEach, expect, test, vi } from "vitest"
2
+
3
+ import { assertUuid, generateUuidV4, generateUuidV4FromUrl, generateUuidV7, getUuidVersion, isUuid } from "#Source/identifier/index.ts"
4
+
5
+ afterEach(() => {
6
+ vi.restoreAllMocks()
7
+ })
8
+
9
+ test("isUuid validates UUID input", () => {
10
+ expect(isUuid("550e8400-e29b-41d4-a716-446655440000")).toBe(true)
11
+ expect(isUuid("550E8400-E29B-41D4-A716-446655440000")).toBe(true)
12
+ expect(isUuid("123e4567-e89b-12d3-a456-426614174000")).toBe(true)
13
+
14
+ expect(isUuid("not-a-uuid")).toBe(false)
15
+ expect(isUuid("550e8400e29b41d4a716446655440000")).toBe(false)
16
+ expect(isUuid("550e8400-e29b-91d4-a716-446655440000")).toBe(false)
17
+ expect(isUuid("550e8400-e29b-41d4-c716-446655440000")).toBe(false)
18
+ })
19
+
20
+ test("assertUuid throws on malformed input", () => {
21
+ expect(() => assertUuid("550e8400-e29b-41d4-a716-446655440000")).not.toThrow()
22
+ expect(() => assertUuid("not-a-uuid")).toThrow(TypeError)
23
+ expect(() => assertUuid("550e8400e29b41d4a716446655440000")).toThrow(TypeError)
24
+ })
25
+
26
+ test("getUuidVersion returns UUID version and throws on malformed input", () => {
27
+ expect(getUuidVersion("550e8400-e29b-41d4-a716-446655440000")).toBe(4)
28
+ expect(getUuidVersion("123e4567-e89b-12d3-a456-426614174000")).toBe(1)
29
+
30
+ expect(() => getUuidVersion("not-a-uuid")).toThrow(TypeError)
31
+ })
32
+
33
+ test("generateUuid returns RFC 4122 version-4 UUID format", () => {
34
+ const UUID_V4_REGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
35
+ const value1 = generateUuidV4()
36
+ const value2 = generateUuidV4()
37
+
38
+ expect(value1).toMatch(UUID_V4_REGEXP)
39
+ expect(value2).toMatch(UUID_V4_REGEXP)
40
+ expect(value1).not.toBe(value2)
41
+ })
42
+
43
+ test("generateUuidV4FromUrl extracts UUID from object URL and revokes URL", () => {
44
+ const mockedUuid = "1e34c01e-ab7c-4e60-b6f3-a6101e8c0d2d"
45
+ const mockedObjectUrl = `blob:https://www.cigaret.world/${mockedUuid}`
46
+ const createObjectUrlSpy = vi.spyOn(URL, "createObjectURL").mockReturnValue(mockedObjectUrl)
47
+ const revokeObjectUrlSpy = vi.spyOn(URL, "revokeObjectURL").mockImplementation(() => {
48
+ return undefined
49
+ })
50
+
51
+ const value = generateUuidV4FromUrl()
52
+
53
+ expect(value).toBe(mockedUuid)
54
+ expect(isUuid(value)).toBe(true)
55
+ expect(createObjectUrlSpy).toHaveBeenCalledTimes(1)
56
+ expect(revokeObjectUrlSpy).toHaveBeenCalledTimes(1)
57
+ expect(revokeObjectUrlSpy).toHaveBeenCalledWith(mockedObjectUrl)
58
+ })
59
+
60
+ test("generateUuidV7 returns UUIDv7 format and is monotonic within the same millisecond", () => {
61
+ const UUID_V7_REGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
62
+
63
+ vi.spyOn(Date, "now").mockReturnValue(1_700_000_000_000)
64
+ vi.spyOn(crypto, "getRandomValues").mockImplementation(buffer => {
65
+ if (buffer === null) {
66
+ return buffer
67
+ }
68
+
69
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
70
+ const target = buffer as Uint8Array
71
+ target.fill(0)
72
+ return buffer
73
+ })
74
+
75
+ const value1 = generateUuidV7()
76
+ const value2 = generateUuidV7()
77
+ const value3 = generateUuidV7()
78
+
79
+ expect(value1).toMatch(UUID_V7_REGEXP)
80
+ expect(value2).toMatch(UUID_V7_REGEXP)
81
+ expect(value3).toMatch(UUID_V7_REGEXP)
82
+
83
+ expect(value1 < value2).toBe(true)
84
+ expect(value2 < value3).toBe(true)
85
+ })
@@ -0,0 +1,11 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { repairJson } from "#Source/json/index.ts"
4
+
5
+ test("repairJson repairs common JSON-like text issues and throws for unrecoverable input", () => {
6
+ expect(repairJson("{foo:1,}")).toBe('{"foo":1}')
7
+ expect(repairJson("{'name':'mobius',}")).toBe('{"name":"mobius"}')
8
+ expect(repairJson('{"items":[1,2,],}')).toBe('{"items":[1,2]}')
9
+
10
+ expect(() => repairJson('{"a":1,,"b":2}')).toThrow()
11
+ })
@@ -0,0 +1,33 @@
1
+ import { afterEach, expect, test, vi } from "vitest"
2
+
3
+ import {
4
+ ConsoleDebugLogEmitter,
5
+ ConsoleErrorLogEmitter,
6
+ ConsoleInfoLogEmitter,
7
+ ConsoleLogLogEmitter,
8
+ ConsoleWarnLogEmitter,
9
+ } from "#Source/log/index.ts"
10
+
11
+ afterEach(() => {
12
+ vi.restoreAllMocks()
13
+ })
14
+
15
+ test("console log emitters format tags and messages before output", () => {
16
+ const logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined)
17
+ const infoSpy = vi.spyOn(console, "info").mockImplementation(() => undefined)
18
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => undefined)
19
+ const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined)
20
+ const debugSpy = vi.spyOn(console, "debug").mockImplementation(() => undefined)
21
+
22
+ new ConsoleLogLogEmitter({ tags: ["scope", "unit"], messages: ["hello", 1] }).emit()
23
+ new ConsoleInfoLogEmitter({ tags: ["scope"], messages: ["info"] }).emit()
24
+ new ConsoleWarnLogEmitter({ tags: ["scope"], messages: ["warn"] }).emit()
25
+ new ConsoleErrorLogEmitter({ tags: ["scope"], messages: ["error"] }).emit()
26
+ new ConsoleDebugLogEmitter({ tags: ["scope"], messages: ["debug"] }).emit()
27
+
28
+ expect(logSpy).toHaveBeenCalledWith("[scope][unit] hello 1")
29
+ expect(infoSpy).toHaveBeenCalledWith("[scope] info")
30
+ expect(warnSpy).toHaveBeenCalledWith("[scope] warn")
31
+ expect(errorSpy).toHaveBeenCalledWith("[scope] error")
32
+ expect(debugSpy).toHaveBeenCalledWith("[scope] debug")
33
+ })
@@ -0,0 +1,40 @@
1
+ import { expect, test, vi } from "vitest"
2
+
3
+ import { LogScheduler, getGlobalLogScheduler } from "#Source/log/index.ts"
4
+
5
+ test("LogScheduler enqueue dispatches immediately in immediate strategy", () => {
6
+ const scheduler = new LogScheduler({ strategy: "immediate" })
7
+ const emit1 = vi.fn()
8
+ const emit2 = vi.fn()
9
+
10
+ scheduler.enqueue([
11
+ { record: { type: "log", tags: ["a"], messages: ["one"] }, emit: emit1 },
12
+ { record: { type: "info", tags: ["a"], messages: ["two"] }, emit: emit2 },
13
+ ])
14
+
15
+ expect(emit1).toHaveBeenCalledTimes(1)
16
+ expect(emit2).toHaveBeenCalledTimes(1)
17
+ })
18
+
19
+ test("LogScheduler run flushes queued tasks", () => {
20
+ const scheduler = new LogScheduler({ strategy: "immediate" })
21
+ const emit1 = vi.fn()
22
+ const emit2 = vi.fn()
23
+
24
+ scheduler.enqueue([
25
+ { record: { type: "log", tags: ["a"], messages: ["one"] }, emit: emit1 },
26
+ { record: { type: "info", tags: ["a"], messages: ["two"] }, emit: emit2 },
27
+ ])
28
+ scheduler.run()
29
+
30
+ expect(emit1).toHaveBeenCalledTimes(1)
31
+ expect(emit2).toHaveBeenCalledTimes(1)
32
+ expect(scheduler.hasTasks()).toEqual(false)
33
+ })
34
+
35
+ test("getGlobalLogScheduler returns singleton instance", () => {
36
+ const scheduler1 = getGlobalLogScheduler()
37
+ const scheduler2 = getGlobalLogScheduler()
38
+
39
+ expect(scheduler1).toBe(scheduler2)
40
+ })
@@ -0,0 +1,7 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { LOG_TYPES } from "#Source/log/index.ts"
4
+
5
+ test("LOG_TYPES exposes supported log levels", () => {
6
+ expect(LOG_TYPES).toEqual(["log", "info", "warn", "error", "debug"])
7
+ })
@@ -0,0 +1,237 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import type { LogEmitter, LogEmitterItem } from "#Source/log/index.ts"
4
+ import {
5
+ Logger,
6
+ getGlobalLogger,
7
+ } from "#Source/log/index.ts"
8
+
9
+ const makeMemoryEmitter = (sink: string[]): LogEmitterItem => {
10
+ class InternalMemoryEmitter implements LogEmitter {
11
+ readonly tags: string[]
12
+ readonly messages: unknown[]
13
+ readonly formattedMessages: string[]
14
+
15
+ constructor(options: { tags?: string[] | undefined, messages?: unknown[] | undefined }) {
16
+ this.tags = options.tags ?? []
17
+ this.messages = options.messages ?? []
18
+ this.formattedMessages = [
19
+ this.tags.map(tag => `[${tag}]`).join(""),
20
+ this.messages.map(message => String(message)).join(" "),
21
+ ]
22
+ }
23
+
24
+ emit(): void {
25
+ sink.push(this.formattedMessages.join(" "))
26
+ }
27
+ }
28
+
29
+ return {
30
+ logType: "info",
31
+ LogEmitter: InternalMemoryEmitter,
32
+ }
33
+ }
34
+
35
+ test("Logger.fromOptions returns provided logger and otherwise derives from global logger", () => {
36
+ const explicit = new Logger({ name: "Explicit" })
37
+ const resolvedExplicit = Logger.fromOptions({ logger: explicit })
38
+ const resolvedDerived = Logger.fromOptions({}).setDefaultName("Derived")
39
+
40
+ expect(resolvedExplicit).toBe(explicit)
41
+ expect(resolvedDerived.hasParent()).toBe(true)
42
+ expect(resolvedDerived.getNameTags()).toEqual([getGlobalLogger().getName(), "Derived"])
43
+ })
44
+
45
+ test("Logger.derive creates child logger with inherited configs", () => {
46
+ const parent = new Logger({ name: "Parent" })
47
+ const child = Logger.derive(parent).setDefaultName("Child")
48
+
49
+ expect(child.hasParent()).toBe(true)
50
+ expect(child.getNameTags()).toEqual(["Parent", "Child"])
51
+ })
52
+
53
+ test("Logger name APIs prefer explicit name and fall back to defaultName", () => {
54
+ const logger = new Logger({})
55
+
56
+ expect(logger.getName()).toBe("Unnamed")
57
+
58
+ logger.setDefaultName("DefaultLogger")
59
+ expect(logger.getName()).toBe("DefaultLogger")
60
+
61
+ logger.setName("ExplicitLogger")
62
+ expect(logger.getName()).toBe("ExplicitLogger")
63
+
64
+ logger.setName(undefined)
65
+ expect(logger.getName()).toBe("DefaultLogger")
66
+ })
67
+
68
+ test("Logger config APIs merge and apply parent configs", () => {
69
+ const parent = new Logger({
70
+ name: "Parent",
71
+ configs: {
72
+ enabled: true,
73
+ autoSend: true,
74
+ filter: (): boolean => true,
75
+ emitters: [],
76
+ },
77
+ })
78
+ const child = new Logger({
79
+ name: "Child",
80
+ parent,
81
+ configs: {
82
+ enabled: false,
83
+ },
84
+ })
85
+
86
+ expect(child.getConfigs().enabled).toBe(true)
87
+
88
+ child.setConfigs({ enabled: false })
89
+ expect(child.getConfigs().enabled).toBe(false)
90
+
91
+ child.useParentConfigs()
92
+ expect(child.getConfigs().enabled).toBe(true)
93
+ })
94
+
95
+ test("Logger emitter APIs add, deduplicate and remove emitters", () => {
96
+ const sink: string[] = []
97
+ const emitter = makeMemoryEmitter(sink)
98
+ const logger = new Logger({
99
+ name: "EmitterLogger",
100
+ configs: {
101
+ emitters: [],
102
+ enabled: true,
103
+ autoSend: true,
104
+ filter: (): boolean => true,
105
+ },
106
+ })
107
+
108
+ logger.addLogEmitters([emitter, emitter])
109
+ expect(logger.getAllLogEmitters()).toHaveLength(1)
110
+ expect(logger.getLogEmitters("info")).toHaveLength(1)
111
+
112
+ logger.info("hello")
113
+ expect(sink).toHaveLength(1)
114
+
115
+ logger.removeLogEmitters([emitter])
116
+ expect(logger.getAllLogEmitters()).toHaveLength(0)
117
+ })
118
+
119
+ test("Logger tags APIs manage instance, session and once tags", () => {
120
+ const sink: string[] = []
121
+ const logger = new Logger({
122
+ name: "TagLogger",
123
+ configs: {
124
+ emitters: [makeMemoryEmitter(sink)],
125
+ enabled: true,
126
+ autoSend: true,
127
+ filter: (): boolean => true,
128
+ },
129
+ })
130
+
131
+ logger.addInstanceTags(["instance"]).tagStart(["session"]).addOnceTags(["once"])
132
+ logger.info("first")
133
+ logger.info("second")
134
+
135
+ expect(sink[0]).toContain("[instance]")
136
+ expect(sink[0]).toContain("[session]")
137
+ expect(sink[0]).toContain("[once]")
138
+ expect(sink[1]).toContain("[instance]")
139
+ expect(sink[1]).toContain("[session]")
140
+ expect(sink[1]).not.toContain("[once]")
141
+
142
+ logger.removeInstanceTags(["instance"]).tagEnd()
143
+ expect(logger.getInstanceTags()).toEqual([])
144
+ expect(logger.getSessionTags()).toEqual([])
145
+ })
146
+
147
+ test("Logger.autoTag adds temporary tags during disposable scope", () => {
148
+ const sink: string[] = []
149
+ const logger = new Logger({
150
+ name: "AutoTagLogger",
151
+ configs: {
152
+ emitters: [makeMemoryEmitter(sink)],
153
+ enabled: true,
154
+ autoSend: true,
155
+ filter: (): boolean => true,
156
+ },
157
+ })
158
+
159
+ const disposable = logger.autoTag(["scoped"])
160
+ logger.info("within")
161
+ disposable[Symbol.dispose]()
162
+ logger.info("outside")
163
+
164
+ expect(sink[0]).toContain("[scoped]")
165
+ expect(sink[1]).not.toContain("[scoped]")
166
+ })
167
+
168
+ test("Logger logging APIs respect enabled and filter configs", () => {
169
+ const sink: string[] = []
170
+ const logger = new Logger({
171
+ name: "FilterLogger",
172
+ configs: {
173
+ emitters: [makeMemoryEmitter(sink)],
174
+ enabled: false,
175
+ autoSend: true,
176
+ filter: (): boolean => true,
177
+ },
178
+ })
179
+
180
+ logger.log("a").info("b").warn("c").error("d").debug("e")
181
+ expect(sink).toHaveLength(0)
182
+
183
+ logger.setConfigs({ enabled: true, filter: () => false })
184
+ logger.info("blocked")
185
+ expect(sink).toHaveLength(0)
186
+
187
+ logger.setConfigs({ filter: () => true })
188
+ logger.info("allowed")
189
+ expect(sink).toHaveLength(1)
190
+ })
191
+
192
+ test("Logger.send flushes queued logs when autoSend is disabled", () => {
193
+ const sink: string[] = []
194
+ const logger = new Logger({
195
+ name: "BatchLogger",
196
+ configs: {
197
+ emitters: [makeMemoryEmitter(sink)],
198
+ enabled: true,
199
+ autoSend: false,
200
+ filter: (): boolean => true,
201
+ },
202
+ })
203
+
204
+ logger.info("queued")
205
+ expect(sink).toHaveLength(0)
206
+
207
+ logger.send()
208
+ expect(sink).toHaveLength(1)
209
+ })
210
+
211
+ test("Logger.batch creates manual child logger and supports disposal send", () => {
212
+ const sink: string[] = []
213
+ const logger = new Logger({
214
+ name: "ParentLogger",
215
+ configs: {
216
+ emitters: [makeMemoryEmitter(sink)],
217
+ enabled: true,
218
+ autoSend: true,
219
+ filter: (): boolean => true,
220
+ },
221
+ })
222
+
223
+ const child = logger.batch("batch-id")
224
+ child.info("queued")
225
+ expect(sink).toHaveLength(0)
226
+
227
+ child[Symbol.dispose]()
228
+ expect(sink).toHaveLength(1)
229
+ expect(sink[0]).toContain("[batch-id]")
230
+ })
231
+
232
+ test("getGlobalLogger returns singleton instance", () => {
233
+ const logger1 = getGlobalLogger()
234
+ const logger2 = getGlobalLogger()
235
+
236
+ expect(logger1).toBe(logger2)
237
+ })