@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,413 @@
1
+ /**
2
+ * 判断给定值是否为字符串。
3
+ *
4
+ * @example
5
+ * ```
6
+ * // Expect: true
7
+ * const example1 = internalIsString("page=1")
8
+ *
9
+ * // Expect: false
10
+ * const example2 = internalIsString({ page: "1" })
11
+ * ```
12
+ */
13
+ const internalIsString = (value: unknown): value is string => {
14
+ return typeof value === "string"
15
+ }
16
+
17
+ /**
18
+ * 判断给定值是否为普通对象。
19
+ *
20
+ * @example
21
+ * ```
22
+ * // Expect: true
23
+ * const example1 = internalIsObject({ page: "1" })
24
+ *
25
+ * // Expect: false
26
+ * const example2 = internalIsObject(["page"])
27
+ * ```
28
+ */
29
+ const internalIsObject = (value: unknown): value is Record<string, string> => {
30
+ return typeof value === "object" && value !== null && Array.isArray(value) === false
31
+ }
32
+
33
+ /**
34
+ * 将单个 query 拆分为 key 和 value。
35
+ *
36
+ * @example
37
+ * ```
38
+ * // Expect: ["page", "1"]
39
+ * const example1 = internalSplitQueryEntry("page=1")
40
+ *
41
+ * // Expect: ["formula", "a=b"]
42
+ * const example2 = internalSplitQueryEntry("formula=a=b")
43
+ * ```
44
+ */
45
+ const internalSplitQueryEntry = (query: string): [key: string, value: string] => {
46
+ const internalEqualSignIndex = query.indexOf("=")
47
+
48
+ if (internalEqualSignIndex === -1) {
49
+ const key = query
50
+ const value = ""
51
+ return [key, value]
52
+ }
53
+
54
+ const key = query.slice(0, internalEqualSignIndex)
55
+ const value = query.slice(internalEqualSignIndex + 1)
56
+ return [key, value]
57
+ }
58
+
59
+ /**
60
+ * 不带 `?` 前缀的 query 字符串。
61
+ *
62
+ * @example
63
+ * ```
64
+ * // Expect: "page=1"
65
+ * const example1: QueryString = "page=1"
66
+ * ```
67
+ */
68
+ export type QueryString = string
69
+
70
+ /**
71
+ * 查询参数对象。
72
+ *
73
+ * @example
74
+ * ```
75
+ * // Expect: { page: "1" }
76
+ * const example1: QueryObject = { page: "1" }
77
+ * ```
78
+ */
79
+ export type QueryObject = Record<string, string>
80
+
81
+ /**
82
+ * 查询参数支持的输入或输出形态。
83
+ *
84
+ * @example
85
+ * ```
86
+ * // Expect: "page=1"
87
+ * const example1: QueryUnion = "page=1"
88
+ *
89
+ * // Expect: { page: "1" }
90
+ * const example2: QueryUnion = { page: "1" }
91
+ * ```
92
+ */
93
+ export type QueryUnion = QueryString | QueryObject
94
+
95
+ export type SearchStringLoose = string
96
+ /**
97
+ * 带 `?` 前缀的 search 字符串。
98
+ *
99
+ * @example
100
+ * ```
101
+ * // Expect: "?page=1"
102
+ * const example1: SearchStringStrict = "?page=1"
103
+ * ```
104
+ */
105
+ export type SearchStringStrict = `?${string}`
106
+
107
+ export type SearchUnion = QueryString | QueryObject | SearchStringLoose | SearchStringStrict
108
+
109
+ /**
110
+ * 规范化 search 字符串,确保其带有 `?` 前缀。
111
+ *
112
+ * @example
113
+ * ```
114
+ * // Expect: "?name=cigaret"
115
+ * const example1 = neatenSearch("?name=cigaret")
116
+ *
117
+ * // Expect: "?name=cigaret"
118
+ * const example2 = neatenSearch("name=cigaret")
119
+ * ```
120
+ */
121
+ export const neatenSearch = (target: string): SearchStringStrict => {
122
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
123
+ return target.startsWith("?") ? (target as SearchStringStrict) : `?${target}`
124
+ }
125
+
126
+ /**
127
+ * 规范化 query 字符串,确保其不带 `?` 前缀。
128
+ *
129
+ * @example
130
+ * ```
131
+ * // Expect: "name=cigaret"
132
+ * const example1 = neatenQueryString("?name=cigaret")
133
+ *
134
+ * // Expect: "name=cigaret"
135
+ * const example2 = neatenQueryString("name=cigaret")
136
+ * ```
137
+ */
138
+ export const neatenQueryString = (target: string): QueryString => {
139
+ return target.startsWith("?") ? target.slice(1) : target
140
+ }
141
+
142
+ /**
143
+ * 将 query 字符串解析为查询参数对象。
144
+ *
145
+ * @example
146
+ * ```
147
+ * // Expect: { name: "cigaret" }
148
+ * const example1 = queryStringToQueryObject("name=cigaret")
149
+ *
150
+ * // Expect: { formula: "a=b" }
151
+ * const example2 = queryStringToQueryObject("formula=a%3Db")
152
+ * ```
153
+ */
154
+ export const queryStringToQueryObject = (target: QueryString): QueryObject => {
155
+ const queryString = neatenQueryString(target)
156
+ const querys = queryString.split("&")
157
+ const queryObject: QueryObject = {}
158
+
159
+ querys.forEach((query) => {
160
+ if (query !== "") {
161
+ const [rawKey, rawValue] = internalSplitQueryEntry(query)
162
+ queryObject[decodeURIComponent(rawKey)] = decodeURIComponent(rawValue)
163
+ }
164
+ })
165
+
166
+ return queryObject
167
+ }
168
+
169
+ /**
170
+ * 将查询参数对象序列化为 query 字符串。
171
+ *
172
+ * @example
173
+ * ```
174
+ * // Expect: "name=cigaret"
175
+ * const example1 = queryObjectToQueryString({ name: "cigaret" })
176
+ *
177
+ * // Expect: "formula=a%3Db"
178
+ * const example2 = queryObjectToQueryString({ formula: "a=b" })
179
+ * ```
180
+ */
181
+ export const queryObjectToQueryString = (target: QueryObject): QueryString => {
182
+ const queryString = Object.entries(target)
183
+ .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
184
+ .join("&")
185
+
186
+ return queryString
187
+ }
188
+
189
+ /**
190
+ * 将 search 字符串转换为 query 字符串。
191
+ *
192
+ * @example
193
+ * ```
194
+ * // Expect: "page=1"
195
+ * const example1 = searchToQueryString("?page=1")
196
+ * ```
197
+ */
198
+ export const searchToQueryString = (target: SearchStringLoose): QueryString => {
199
+ return neatenQueryString(target)
200
+ }
201
+
202
+ /**
203
+ * 将 search 字符串解析为查询参数对象。
204
+ *
205
+ * @example
206
+ * ```
207
+ * // Expect: { page: "1", size: "20" }
208
+ * const example1 = searchToQueryObject("?page=1&size=20")
209
+ * ```
210
+ */
211
+ export const searchToQueryObject = (target: SearchStringLoose): QueryObject => {
212
+ return queryStringToQueryObject(searchToQueryString(target))
213
+ }
214
+
215
+ /**
216
+ * 将 query 字符串转换为 search 字符串。
217
+ *
218
+ * @example
219
+ * ```
220
+ * // Expect: "?page=1"
221
+ * const example1 = queryStringToSearch("page=1")
222
+ * ```
223
+ */
224
+ export const queryStringToSearch = (target: QueryString): SearchStringStrict => {
225
+ return neatenSearch(target)
226
+ }
227
+
228
+ /**
229
+ * 将查询参数对象转换为 search 字符串。
230
+ *
231
+ * @example
232
+ * ```
233
+ * // Expect: "?page=1&size=20"
234
+ * const example1 = queryObjectToSearch({ page: "1", size: "20" })
235
+ * ```
236
+ */
237
+ export const queryObjectToSearch = (target: QueryObject): SearchStringStrict => {
238
+ return queryStringToSearch(queryObjectToQueryString(target))
239
+ }
240
+
241
+ /**
242
+ * 将查询参数统一转换为 search 字符串。
243
+ *
244
+ * @example
245
+ * ```
246
+ * // Expect: "?page=1"
247
+ * const example1 = toSearch("page=1")
248
+ *
249
+ * // Expect: "?page=1"
250
+ * const example2 = toSearch({ page: "1" })
251
+ * ```
252
+ */
253
+ export const toSearch = (target: SearchUnion): SearchStringStrict => {
254
+ if (internalIsString(target)) {
255
+ return neatenSearch(target)
256
+ }
257
+ else if (internalIsObject(target)) {
258
+ return queryObjectToSearch(target)
259
+ }
260
+ else {
261
+ throw (new TypeError("\"target\" is expected to be type of \"string\" | \"object\"."))
262
+ }
263
+ }
264
+
265
+ /**
266
+ * 将查询参数统一转换为 query 字符串。
267
+ *
268
+ * @example
269
+ * ```
270
+ * // Expect: "page=1"
271
+ * const example1 = toQueryString("?page=1")
272
+ *
273
+ * // Expect: "page=1"
274
+ * const example2 = toQueryString({ page: "1" })
275
+ * ```
276
+ */
277
+ export const toQueryString = (target: SearchUnion): QueryString => {
278
+ if (internalIsString(target)) {
279
+ return neatenQueryString(target)
280
+ }
281
+ else if (internalIsObject(target)) {
282
+ return queryObjectToQueryString(target)
283
+ }
284
+ else {
285
+ throw (new TypeError("\"target\" is expected to be type of \"string\" | \"object\"."))
286
+ }
287
+ }
288
+
289
+ /**
290
+ * 将查询参数统一转换为查询参数对象。
291
+ *
292
+ * @example
293
+ * ```
294
+ * // Expect: { page: "1" }
295
+ * const example1 = toQueryObject("?page=1")
296
+ *
297
+ * // Expect: { page: "1" }
298
+ * const example2 = toQueryObject({ page: "1" })
299
+ * ```
300
+ */
301
+ export const toQueryObject = (target: SearchUnion): QueryObject => {
302
+ if (internalIsString(target)) {
303
+ return queryStringToQueryObject(target)
304
+ }
305
+ else if (internalIsObject(target)) {
306
+ return target
307
+ }
308
+ else {
309
+ throw (new TypeError("\"target\" is expected to be type of \"string\" | \"object\"."))
310
+ }
311
+ }
312
+
313
+ /**
314
+ * 宽松包含关系:查询参数的键集合是目标查询参数键集合的子集。
315
+ *
316
+ * @example
317
+ * ```
318
+ * // Expect: true
319
+ * const example1 = isSearchLooseIncludes("?a=1&b=2", ["a"])
320
+ *
321
+ * // Expect: false
322
+ * const example2 = isSearchLooseIncludes("?a=1&b=2", ["a", "c"])
323
+ * ```
324
+ */
325
+ export const isSearchLooseIncludes = (search: SearchUnion, query: string | string[]): boolean => {
326
+ const existKeys = Object.keys(toQueryObject(search))
327
+ const queryKeys = internalIsString(query) ? [query] : query
328
+ return queryKeys.every(queryKey => existKeys.includes(queryKey))
329
+ }
330
+
331
+ /**
332
+ * 严格包含关系:查询参数的键集合与目标查询参数键集合完全相同。
333
+ *
334
+ * @example
335
+ * ```
336
+ * // Expect: true
337
+ * const example1 = isSearchStrictIncludes("?a=1&b=2", ["a", "b"])
338
+ *
339
+ * // Expect: false
340
+ * const example2 = isSearchStrictIncludes("?a=1&b=2", ["a"])
341
+ * ```
342
+ */
343
+ export const isSearchStrictIncludes = (search: SearchUnion, query: string | string[]): boolean => {
344
+ const existKeys = Object.keys(toQueryObject(search)).toSorted()
345
+ const queryKeys = (internalIsString(query) ? [query] : query).toSorted()
346
+
347
+ if (existKeys.length !== queryKeys.length) {
348
+ return false
349
+ }
350
+
351
+ return existKeys.every((key, index) => key === queryKeys[index])
352
+ }
353
+
354
+ /**
355
+ * 包含关系:查询参数的键集合必须包含目标查询参数键集合,并且不能包含除目标查询参数键集合之外的其他键。
356
+ *
357
+ * @example
358
+ * ```
359
+ * // Expect: true
360
+ * const example1 = isSearchIncludes("?a=1&b=2", { a: "required", b: "optional" })
361
+ *
362
+ * // Expect: false
363
+ * const example2 = isSearchIncludes("?a=1&b=2", { a: "required" })
364
+ * ```
365
+ */
366
+ export const isSearchIncludes = (search: SearchUnion, query: Record<string, "required" | "optional">): boolean => {
367
+ const existKeys = Object.keys(toQueryObject(search))
368
+ const requiredKeys = Object.entries(query).filter(([, value]) => value === "required").map(([key]) => key)
369
+ const optionalKeys = Object.entries(query).filter(([, value]) => value === "optional").map(([key]) => key)
370
+
371
+ const requiredKeysExist = requiredKeys.every(requiredKey => existKeys.includes(requiredKey))
372
+ const extraKeysExist = existKeys.some(existKey => !requiredKeys.includes(existKey) && !optionalKeys.includes(existKey))
373
+
374
+ return requiredKeysExist && !extraKeysExist
375
+ }
376
+
377
+ /**
378
+ * 宽松相等:查询参数的键集合是目标查询参数键集合的子集,并且对应的值相等。
379
+ *
380
+ * @example
381
+ * ```
382
+ * // Expect: true
383
+ * const example1 = isSearchLooseEqual("?a=1&b=2", "?a=1")
384
+ *
385
+ * // Expect: false
386
+ * const example2 = isSearchLooseEqual("?a=1&b=2", "?a=1&c=3")
387
+ * ```
388
+ */
389
+ export const isSearchLooseEqual = (searchA: SearchUnion, searchB: SearchUnion): boolean => {
390
+ const queryObjectA = toQueryObject(searchA)
391
+ const queryObjectB = toQueryObject(searchB)
392
+ return Object.entries(queryObjectB).every(([key, value]) => queryObjectA[key] === value)
393
+ }
394
+
395
+ /**
396
+ * 严格相等:查询参数的键集合与目标查询参数键集合完全相同,并且对应的值相等。
397
+ *
398
+ * @example
399
+ * ```
400
+ * // Expect: true
401
+ * const example1 = isSearchStrictEqual("?a=1&b=2", "?b=2&a=1")
402
+ *
403
+ * // Expect: false
404
+ * const example2 = isSearchStrictEqual("?a=1&b=2", "?a=1")
405
+ * ```
406
+ */
407
+ export const isSearchStrictEqual = (searchA: SearchUnion, searchB: SearchUnion): boolean => {
408
+ const queryObjectA = toQueryObject(searchA)
409
+ const queryObjectB = toQueryObject(searchB)
410
+ const lengthEqual = Object.keys(queryObjectA).length === Object.keys(queryObjectB).length
411
+ const valueEqual = Object.entries(queryObjectB).every(([key, value]) => queryObjectA[key] === value)
412
+ return lengthEqual && valueEqual
413
+ }
@@ -0,0 +1,79 @@
1
+ # Singleton
2
+
3
+ ## Description
4
+
5
+ Singleton 模块用于提供单例(singleton)相关的基础能力,主要承载惰性初始化(lazy initialization)、结果缓存以及具名共享实例的组织语义。
6
+
7
+ 它关注的不是“如何制造全局变量”,而是“当某个值确实只应初始化一次并被稳定复用时,应如何用清楚、可维护的方式表达这种共享关系”。因此,这个模块更适合用来建模实例生命周期与访问边界,而不是无差别地为任何对象增加全局缓存入口。
8
+
9
+ ## For Understanding
10
+
11
+ 理解 Singleton 模块时,重点不在“单例”这个词本身,而在“共享值的生命周期由谁负责、初始化在什么时候发生、调用方如何稳定访问它”。这个模块解决的是共享实例的建模问题:某个值是否应在首次读取时才创建、是否应在后续始终复用、是否需要通过名称组织多个共享项。
12
+
13
+ 因此,它适合放在那些需要明确所有权和初始化时机的边界中,例如基础配置、昂贵对象构造结果、应用级共享资源或一组可按名称访问的依赖项。它不适合表达的,则是带有复杂销毁规则、作用域切换规则或线程级隔离要求的资源管理问题;那类问题通常需要更完整的生命周期模型,不能仅靠“只创建一次”来概括。
14
+
15
+ 还需要注意的是,单例只是一种共享策略,不应被误解为默认设计。只有当值的复用边界、初始化副作用和缓存语义都足够清楚时,它才适合进入这个模块。否则,过早把普通依赖硬塞进单例模型,只会把状态管理问题隐藏起来,而不会真正减少复杂度。
16
+
17
+ ## For Using
18
+
19
+ 当你需要把某个值设计为“首次访问时初始化,之后持续复用”的共享实例时,可以使用这个模块。它适合那些初始化成本较高、创建时机需要延后、并且复用边界明确的场景。
20
+
21
+ 从使用角度看,这个模块大致包含两类能力。一类是惰性工厂能力,用来把一次性初始化逻辑包装成可复用入口,使值只在真正需要时才被创建。另一类是具名单例集合能力,用来把多个共享实例组织成一个稳定的命名集合,便于调用方按名称读取相应的缓存结果。
22
+
23
+ 更合理的使用方式,是把单例能力放在应用或模块边界附近,并让初始化逻辑保持尽量确定。特别是在初始化会接触 I/O、环境变量、全局对象或其它外部资源时,应先明确这些副作用是否真的适合缓存。测试场景中也应明确控制缓存状态的生命周期,避免不同用例之间因为共享状态而互相影响。
24
+
25
+ ## For Contributing
26
+
27
+ 贡献 Singleton 模块时,应优先判断新增能力解决的是不是稳定的共享生命周期问题,而不是某个局部实现里一时方便的缓存技巧。这个模块的公共语义应围绕“值只初始化一次并被复用”以及“多个共享项如何被命名和访问”展开,而不是替调用方偷偷管理越来越复杂的状态机。
28
+
29
+ 在继续扩展时,应守住几个边界。不要把作用域管理、资源销毁、并发隔离或依赖注入容器的完整语义混入 Singleton 模块;这些问题虽然与共享实例有关,但通常比单例语义更大。也不要为了省掉几次参数传递,就把本应显式注入的依赖升级为全局单例。只有当共享关系本身就是模块想表达的稳定模型时,新增能力才值得公开。
30
+
31
+ ### JSDoc 注释格式要求
32
+
33
+ - 每个公开导出的目标(类型、函数、变量、类等)都应包含 JSDoc 注释,让人在不跳转实现的情况下就能理解用途。
34
+ - JSDoc 注释第一行应为清晰且简洁的描述,该描述优先使用中文(英文也可以)。
35
+ - 如果描述后还有其他内容,应在描述后加一个空行。
36
+ - 如果有示例,应使用 `@example` 标签,后接三重反引号代码块(不带语言标识)。
37
+ - 如果有示例,应包含多个场景,展示不同用法,尤其要覆盖常见组合方式或边界输入。
38
+ - 如果有示例,应使用注释格式说明每个场景:`// Expect: <result>`。
39
+ - 如果有示例,应将结果赋值给 `example1`、`example2` 之类的变量,以保持示例易读。
40
+ - 如果有示例,`// Expect: <result>` 应该位于 `example1`、`example2` 之前,以保持示例的逻辑清晰。
41
+ - 如果有示例,应优先使用确定性示例;避免断言精确的随机输出。
42
+ - 如果函数返回结构化字符串,应展示其预期格式特征。
43
+ - 如果有参考资料,应将 `@see` 放在 `@example` 代码块之后,并用一个空行分隔。
44
+
45
+ ### 实现规范要求
46
+
47
+ - 不同程序元素之间使用一个空行分隔,保持结构清楚。这里的程序元素,通常指函数、类型、常量,以及直接服务于它们的辅助元素。
48
+ - 某程序元素独占的辅助元素与该程序元素本身视为一个整体,不要在它们之间添加空行。
49
+ - 程序元素的辅助元素应该放置在该程序元素的上方,以保持阅读时的逻辑顺序。
50
+ - 若辅助元素被多个程序元素共享,则应将其视为独立的程序元素,放在这些程序元素中第一个相关目标的上方,并与后续程序元素之间保留一个空行。
51
+ - 辅助元素也应该像其它程序元素一样,保持清晰的命名和适当的注释,以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
52
+ - 辅助元素的命名必须以前缀 `internal` 开头(或 `Internal`,大小写不敏感)。
53
+ - 辅助元素永远不要公开导出。
54
+ - 被模块内多个不同文件中的程序元素共享的辅助元素,应该放在一个单独的文件中,例如 `./src/singleton/internal.ts`。
55
+ - 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时,才应将其视为子模块。
56
+ - 子模块包含多个文件时,应该为其单独创建子文件夹,并为其创建单独的 Barrel 文件;父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
57
+ - 子模块不需要有自己的 `README.md`。
58
+ - 子模块可以有自己的 `internal.ts` 文件,多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中,单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
59
+ - 对模块依赖关系的要求(通常是不循环依赖或不反向依赖)与对 DRY 的要求可能产生冲突。此时,若复用的代码数量不大,可以适当牺牲 DRY,复制粘贴并保留必要的注释说明;若复用的代码数量较大,则可以将其抽象到新的文件或子模块中,如 `common.ts`,并在需要的地方导入使用。
60
+
61
+ - 与单例相关的实现应优先围绕初始化时机、复用边界与具名共享语义组织,避免把销毁策略、作用域切换或依赖注入容器语义混入模块边界。
62
+
63
+ ### 导出策略要求
64
+
65
+ - 保持内部辅助项和内部符号为私有,不要让外部接入依赖临时性的内部结构。
66
+ - 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
67
+ - Barrel 文件应命名为 `index.ts`,放在模块目录根部,并且所有公共 API 都应从该文件导出。
68
+ - 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的单例语义,而不是某段实现细节的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
69
+
70
+ ### 测试要求
71
+
72
+ - 若程序元素是函数,则只为该函数编写一个测试,如果该函数需要测试多个用例,应放在同一个测试中。
73
+ - 若程序元素是类,则至少要为该类的每一个方法编写一个测试,如果该方法需要测试多个用例,应放在同一个测试中。
74
+ - 若程序元素是类,除了为该类的每一个方法编写至少一个测试之外,还可以为该类编写任意多个测试,以覆盖该类的不同使用场景或边界情况。
75
+ - 若编写测试时需要用到辅助元素(Mock 或 Spy 等),可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单,则可以直接放在每一个测试内部,优先保证每个测试的独立性,而不是追求极致 DRY;若辅助元素较为复杂或需要在多个测试中复用,则可以放在测试文件顶部,供该测试文件中的所有测试使用。
76
+ - 测试顺序应与源文件中被测试目标的原始顺序保持一致。
77
+ - 若该模块不需要测试,必须在说明文件中明确说明该模块不需要测试,并说明理由。一般来说,只有在该模块没有可执行的公共函数、只承载类型层表达,或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时,才适合这样处理。
78
+ - 模块的单元测试文件目录是 `./tests/unit/singleton`,若模块包含子模块,则子模块的单元测试文件目录为 `./tests/unit/singleton/<sub-module-name>`。
79
+ - 对单例相关能力,应优先覆盖首次初始化、重复访问复用、不同名称隔离、错误分支以及测试间状态控制等场景。
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 定义惰性单例工厂函数类型。
3
+ */
4
+ export type SingletonFactory<T> = () => T
5
+
6
+ /**
7
+ * 创建一个会缓存首次结果的单例工厂。
8
+ *
9
+ * @example
10
+ * ```
11
+ * // Scenario 1: object values are created once and then reused.
12
+ * let calls = 0
13
+ * const getConfig = getSingletonFactory(() => {
14
+ * calls += 1
15
+ * return { env: "test" }
16
+ * })
17
+ *
18
+ * const example1 = getConfig()
19
+ * const example2 = getConfig()
20
+ * const example3 = example1 === example2
21
+ * const example4 = calls
22
+ *
23
+ * // Expect: true
24
+ * example3
25
+ * // Expect: 1
26
+ * example4
27
+ *
28
+ * // Scenario 2: primitive values are also cached after the first read.
29
+ * let count = 0
30
+ * const getPort = getSingletonFactory(() => {
31
+ * count += 1
32
+ * return 3000
33
+ * })
34
+ *
35
+ * const example5 = getPort()
36
+ * const example6 = getPort()
37
+ * const example7 = count
38
+ *
39
+ * // Expect: 3000
40
+ * example5
41
+ * // Expect: 3000
42
+ * example6
43
+ * // Expect: 1
44
+ * example7
45
+ * ```
46
+ */
47
+ export const getSingletonFactory = <T>(make: () => T): SingletonFactory<T> => {
48
+ let singleton: T | undefined = undefined
49
+ return () => {
50
+ if (singleton === undefined) {
51
+ singleton = make()
52
+ }
53
+ return singleton
54
+ }
55
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./factory.ts"
2
+ export * from "./manager.ts"