@planet-matrix/mobius-model 0.6.0 → 0.10.1

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 (258) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/oxlint.config.ts +1 -2
  3. package/package.json +29 -17
  4. package/scripts/build.ts +2 -52
  5. package/src/ai/README.md +1 -0
  6. package/src/ai/ai.ts +107 -0
  7. package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
  8. package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
  9. package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
  10. package/src/ai/chat-completion-ai/index.ts +7 -0
  11. package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
  12. package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
  13. package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
  14. package/src/ai/embedding-ai/embedding-ai.ts +63 -0
  15. package/src/ai/embedding-ai/embedding.ts +50 -0
  16. package/src/ai/embedding-ai/index.ts +4 -0
  17. package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
  18. package/src/ai/index.ts +4 -0
  19. package/src/aio/README.md +100 -0
  20. package/src/aio/content.ts +141 -0
  21. package/src/aio/index.ts +3 -0
  22. package/src/aio/json.ts +127 -0
  23. package/src/aio/prompt.ts +246 -0
  24. package/src/basic/README.md +20 -15
  25. package/src/basic/error.ts +19 -5
  26. package/src/basic/function.ts +2 -2
  27. package/src/basic/index.ts +1 -0
  28. package/src/basic/promise.ts +141 -71
  29. package/src/basic/schedule.ts +111 -0
  30. package/src/basic/stream.ts +135 -25
  31. package/src/credential/README.md +107 -0
  32. package/src/credential/api-key.ts +158 -0
  33. package/src/credential/bearer.ts +73 -0
  34. package/src/credential/index.ts +4 -0
  35. package/src/credential/json-web-token.ts +96 -0
  36. package/src/credential/password.ts +170 -0
  37. package/src/cron/README.md +86 -0
  38. package/src/cron/cron.ts +87 -0
  39. package/src/cron/index.ts +1 -0
  40. package/src/drizzle/README.md +1 -0
  41. package/src/drizzle/drizzle.ts +1 -0
  42. package/src/drizzle/helper.ts +47 -0
  43. package/src/drizzle/index.ts +5 -0
  44. package/src/drizzle/infer.ts +52 -0
  45. package/src/drizzle/kysely.ts +8 -0
  46. package/src/drizzle/pagination.ts +198 -0
  47. package/src/email/README.md +1 -0
  48. package/src/email/index.ts +1 -0
  49. package/src/email/resend.ts +25 -0
  50. package/src/event/class-event-proxy.ts +5 -6
  51. package/src/event/common.ts +13 -3
  52. package/src/event/event-manager.ts +3 -3
  53. package/src/event/instance-event-proxy.ts +5 -6
  54. package/src/event/internal.ts +4 -4
  55. package/src/exception/README.md +28 -19
  56. package/src/exception/error/error.ts +123 -0
  57. package/src/exception/error/index.ts +2 -0
  58. package/src/exception/error/match.ts +38 -0
  59. package/src/exception/error/must-fix.ts +17 -0
  60. package/src/exception/index.ts +2 -0
  61. package/src/file-system/find.ts +53 -0
  62. package/src/file-system/index.ts +2 -0
  63. package/src/file-system/path.ts +76 -0
  64. package/src/file-system/resolve.ts +22 -0
  65. package/src/form/README.md +25 -0
  66. package/src/form/index.ts +1 -0
  67. package/src/form/inputor-controller/base.ts +861 -0
  68. package/src/form/inputor-controller/boolean.ts +39 -0
  69. package/src/form/inputor-controller/file.ts +39 -0
  70. package/src/form/inputor-controller/form.ts +179 -0
  71. package/src/form/inputor-controller/helper.ts +117 -0
  72. package/src/form/inputor-controller/index.ts +17 -0
  73. package/src/form/inputor-controller/multi-select.ts +99 -0
  74. package/src/form/inputor-controller/number.ts +116 -0
  75. package/src/form/inputor-controller/select.ts +109 -0
  76. package/src/form/inputor-controller/text.ts +82 -0
  77. package/src/http/READMD.md +1 -0
  78. package/src/http/api/api-core.ts +84 -0
  79. package/src/http/api/api-handler.ts +79 -0
  80. package/src/http/api/api-host.ts +47 -0
  81. package/src/http/api/api-result.ts +56 -0
  82. package/src/http/api/api-schema.ts +154 -0
  83. package/src/http/api/api-server.ts +130 -0
  84. package/src/http/api/api-test.ts +142 -0
  85. package/src/http/api/api-type.ts +34 -0
  86. package/src/http/api/api.ts +81 -0
  87. package/src/http/api/index.ts +11 -0
  88. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  89. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  90. package/src/http/api-adapter/api-result-arktype.ts +294 -0
  91. package/src/http/api-adapter/api-result-zod.ts +286 -0
  92. package/src/http/api-adapter/index.ts +5 -0
  93. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  94. package/src/http/bin/gen-api-list/index.ts +1 -0
  95. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  96. package/src/http/bin/gen-api-test/index.ts +1 -0
  97. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  98. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  99. package/src/http/bin/gen-api-type/index.ts +2 -0
  100. package/src/http/bin/index.ts +2 -0
  101. package/src/http/index.ts +3 -0
  102. package/src/huawei/README.md +1 -0
  103. package/src/huawei/index.ts +2 -0
  104. package/src/huawei/moderation/index.ts +1 -0
  105. package/src/huawei/moderation/moderation.ts +355 -0
  106. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  107. package/src/huawei/obs/index.ts +1 -0
  108. package/src/huawei/obs/obs.ts +42 -0
  109. package/src/index.ts +21 -2
  110. package/src/json/README.md +92 -0
  111. package/src/json/index.ts +1 -0
  112. package/src/json/repair.ts +18 -0
  113. package/src/log/logger.ts +15 -4
  114. package/src/openai/README.md +1 -0
  115. package/src/openai/index.ts +1 -0
  116. package/src/openai/openai.ts +509 -0
  117. package/src/orchestration/README.md +9 -7
  118. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  119. package/src/orchestration/dispatching/index.ts +2 -0
  120. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  121. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  122. package/src/orchestration/dispatching/selector/index.ts +2 -0
  123. package/src/orchestration/index.ts +2 -0
  124. package/src/orchestration/scheduling/index.ts +2 -0
  125. package/src/orchestration/scheduling/scheduler.ts +103 -0
  126. package/src/orchestration/scheduling/task.ts +32 -0
  127. package/src/random/README.md +8 -7
  128. package/src/random/base.ts +66 -0
  129. package/src/random/index.ts +5 -1
  130. package/src/random/random-boolean.ts +40 -0
  131. package/src/random/random-integer.ts +60 -0
  132. package/src/random/random-number.ts +72 -0
  133. package/src/random/random-string.ts +66 -0
  134. package/src/request/README.md +108 -0
  135. package/src/request/fetch/base.ts +108 -0
  136. package/src/request/fetch/browser.ts +280 -0
  137. package/src/request/fetch/general.ts +20 -0
  138. package/src/request/fetch/index.ts +4 -0
  139. package/src/request/fetch/nodejs.ts +280 -0
  140. package/src/request/index.ts +2 -0
  141. package/src/request/request/base.ts +246 -0
  142. package/src/request/request/general.ts +63 -0
  143. package/src/request/request/index.ts +3 -0
  144. package/src/request/request/resource.ts +68 -0
  145. package/src/result/README.md +4 -0
  146. package/src/result/controller.ts +58 -0
  147. package/src/result/either.ts +363 -0
  148. package/src/result/generator.ts +168 -0
  149. package/src/result/index.ts +3 -0
  150. package/src/route/README.md +105 -0
  151. package/src/route/adapter/browser.ts +122 -0
  152. package/src/route/adapter/driver.ts +56 -0
  153. package/src/route/adapter/index.ts +2 -0
  154. package/src/route/index.ts +3 -0
  155. package/src/route/router/index.ts +2 -0
  156. package/src/route/router/route.ts +630 -0
  157. package/src/route/router/router.ts +1641 -0
  158. package/src/route/uri/hash.ts +307 -0
  159. package/src/route/uri/index.ts +7 -0
  160. package/src/route/uri/pathname.ts +376 -0
  161. package/src/route/uri/search.ts +412 -0
  162. package/src/service/README.md +1 -0
  163. package/src/service/index.ts +1 -0
  164. package/src/service/service.ts +110 -0
  165. package/src/socket/README.md +105 -0
  166. package/src/socket/client/index.ts +2 -0
  167. package/src/socket/client/socket-unit.ts +658 -0
  168. package/src/socket/client/socket.ts +203 -0
  169. package/src/socket/common/index.ts +2 -0
  170. package/src/socket/common/socket-unit-common.ts +23 -0
  171. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  172. package/src/socket/index.ts +3 -0
  173. package/src/socket/server/index.ts +3 -0
  174. package/src/socket/server/server.ts +183 -0
  175. package/src/socket/server/socket-unit.ts +448 -0
  176. package/src/socket/server/socket.ts +264 -0
  177. package/src/storage/table.ts +3 -3
  178. package/src/timer/expiration/expiration-manager.ts +3 -3
  179. package/src/timer/expiration/remaining-manager.ts +3 -3
  180. package/src/tube/README.md +99 -0
  181. package/src/tube/helper.ts +137 -0
  182. package/src/tube/index.ts +2 -0
  183. package/src/tube/tube.ts +880 -0
  184. package/src/weixin/README.md +1 -0
  185. package/src/weixin/index.ts +2 -0
  186. package/src/weixin/official-account/authorization.ts +157 -0
  187. package/src/weixin/official-account/index.ts +2 -0
  188. package/src/weixin/official-account/js-api.ts +132 -0
  189. package/src/weixin/open/index.ts +1 -0
  190. package/src/weixin/open/oauth2.ts +131 -0
  191. package/tests/unit/ai/ai.spec.ts +85 -0
  192. package/tests/unit/aio/content.spec.ts +105 -0
  193. package/tests/unit/aio/json.spec.ts +146 -0
  194. package/tests/unit/aio/prompt.spec.ts +111 -0
  195. package/tests/unit/basic/error.spec.ts +16 -4
  196. package/tests/unit/basic/promise.spec.ts +158 -50
  197. package/tests/unit/basic/schedule.spec.ts +74 -0
  198. package/tests/unit/basic/stream.spec.ts +90 -37
  199. package/tests/unit/credential/api-key.spec.ts +36 -0
  200. package/tests/unit/credential/bearer.spec.ts +23 -0
  201. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  202. package/tests/unit/credential/password.spec.ts +40 -0
  203. package/tests/unit/cron/cron.spec.ts +84 -0
  204. package/tests/unit/event/class-event-proxy.spec.ts +3 -3
  205. package/tests/unit/event/event-manager.spec.ts +3 -3
  206. package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
  207. package/tests/unit/exception/error/error.spec.ts +83 -0
  208. package/tests/unit/exception/error/match.spec.ts +81 -0
  209. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  210. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  211. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  212. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  213. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  214. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  215. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  216. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  217. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  218. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  219. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  220. package/tests/unit/http/api/api-server.spec.ts +363 -0
  221. package/tests/unit/http/api/api-test.spec.ts +117 -0
  222. package/tests/unit/http/api/api.spec.ts +121 -0
  223. package/tests/unit/http/api-adapter/node-http.spec.ts +187 -0
  224. package/tests/unit/identifier/uuid.spec.ts +0 -1
  225. package/tests/unit/json/repair.spec.ts +11 -0
  226. package/tests/unit/log/logger.spec.ts +19 -4
  227. package/tests/unit/openai/openai.spec.ts +64 -0
  228. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  229. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  230. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  231. package/tests/unit/random/base.spec.ts +58 -0
  232. package/tests/unit/random/random-boolean.spec.ts +25 -0
  233. package/tests/unit/random/random-integer.spec.ts +32 -0
  234. package/tests/unit/random/random-number.spec.ts +33 -0
  235. package/tests/unit/random/random-string.spec.ts +22 -0
  236. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  237. package/tests/unit/request/fetch/general.spec.ts +43 -0
  238. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  239. package/tests/unit/request/request/base.spec.ts +382 -0
  240. package/tests/unit/request/request/general.spec.ts +160 -0
  241. package/tests/unit/result/controller.spec.ts +82 -0
  242. package/tests/unit/result/either.spec.ts +377 -0
  243. package/tests/unit/result/generator.spec.ts +273 -0
  244. package/tests/unit/route/router/route.spec.ts +430 -0
  245. package/tests/unit/route/router/router.spec.ts +407 -0
  246. package/tests/unit/route/uri/hash.spec.ts +72 -0
  247. package/tests/unit/route/uri/pathname.spec.ts +146 -0
  248. package/tests/unit/route/uri/search.spec.ts +107 -0
  249. package/tests/unit/socket/client.spec.ts +208 -0
  250. package/tests/unit/socket/server.spec.ts +133 -0
  251. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  252. package/tests/unit/tube/helper.spec.ts +139 -0
  253. package/tests/unit/tube/tube.spec.ts +501 -0
  254. package/vite.config.ts +2 -1
  255. package/dist/index.js +0 -50
  256. package/dist/index.js.map +0 -209
  257. package/src/random/string.ts +0 -35
  258. package/tests/unit/random/string.spec.ts +0 -11
@@ -0,0 +1,264 @@
1
+ import type { IncomingMessage } from "node:http"
2
+
3
+ import type { WebSocket } from "ws"
4
+
5
+ import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts"
6
+ import { Logger } from "#Source/log/index.ts"
7
+ import { generateUuidV4FromUrl } from "#Source/identifier/index.ts"
8
+ import type { BuildEvents } from "#Source/event/index.ts"
9
+ import { EventManager } from "#Source/event/index.ts"
10
+
11
+ import type { WebSocketServer } from "./server.ts"
12
+ import type {
13
+ SocketUnitHeartbeatOptions,
14
+ } from "../common/index.ts"
15
+ import type {
16
+ InitialMessageBuilder,
17
+ SocketUnitSnapshot,
18
+ } from "./socket-unit.ts"
19
+ import {
20
+ SocketUnit,
21
+ } from "./socket-unit.ts"
22
+
23
+ /**
24
+ * 表示服务端 Socket 接入成功时的事件负载。
25
+ */
26
+ export interface SocketConnectedEvent {
27
+ clientId: string
28
+ }
29
+
30
+ /**
31
+ * 表示服务端 Socket 连接关闭时的事件负载。
32
+ */
33
+ export interface SocketClosedEvent {
34
+ clientId: string
35
+ }
36
+
37
+ /**
38
+ * 表示服务端 Socket 派发业务消息时的事件负载。
39
+ */
40
+ export interface SocketMessageEvent<Message> {
41
+ clientId: string
42
+ message: Message
43
+ }
44
+
45
+ /**
46
+ * 表示服务端 Socket 对外派发的事件表。
47
+ */
48
+ export type SocketEvents<Message> = BuildEvents<{
49
+ connect: (payload: SocketConnectedEvent) => void
50
+ close: (payload: SocketClosedEvent) => void
51
+ message: (payload: SocketMessageEvent<Message>) => void
52
+ }>
53
+
54
+ /**
55
+ * 表示服务端 Socket 的诊断快照。
56
+ */
57
+ export interface SocketSnapshot {
58
+ isStarted: boolean
59
+ isRunning: boolean
60
+ managesWebSocketServerLifecycle: boolean
61
+ activeSocketUnitCount: number
62
+ clientIds: string[]
63
+ socketUnitSnapshots: SocketUnitSnapshot[]
64
+ }
65
+
66
+ /**
67
+ * 表示服务端 Socket 的构造参数。
68
+ */
69
+ export interface SocketOptions<Message> extends LoggerFriendlyOptions {
70
+ webSocketServer: WebSocketServer
71
+
72
+ enableInitialMessage?: boolean | undefined
73
+ initialMessageBuilder?: InitialMessageBuilder<Message> | undefined
74
+ heartbeat?: SocketUnitHeartbeatOptions<Message> | undefined
75
+ onMessage?: ((payload: SocketMessageEvent<Message>) => void) | undefined
76
+ }
77
+
78
+ /**
79
+ * 管理服务端接入的多个 SocketUnit,并对外提供统一事件面。
80
+ */
81
+ export class Socket<Message> implements LoggerFriendly {
82
+ private readonly options: SocketOptions<Message>
83
+
84
+ readonly logger: Logger
85
+ eventManager: EventManager<SocketEvents<Message>>
86
+
87
+ private isStarted: boolean
88
+ private managesWebSocketServerLifecycle: boolean
89
+ private socketUnitMap: Map<string, SocketUnit<Message>>
90
+ private connectionListener: ((webSocket: WebSocket, incomingMessage: IncomingMessage) => void) | undefined
91
+
92
+ constructor(options: SocketOptions<Message>) {
93
+ this.options = options
94
+
95
+ this.logger = Logger.fromOptions(options).setDefaultName("Socket")
96
+ this.eventManager = new EventManager<SocketEvents<Message>>()
97
+
98
+ this.isStarted = false
99
+ this.managesWebSocketServerLifecycle = false
100
+ this.socketUnitMap = new Map()
101
+ this.connectionListener = undefined
102
+ }
103
+
104
+ /**
105
+ * 启动服务端 Socket,并在必要时托管底层服务生命周期。
106
+ */
107
+ async open(): Promise<void> {
108
+ if (this.isStarted === true) {
109
+ throw new Error("Socket has already opened.")
110
+ }
111
+
112
+ if (this.options.webSocketServer.isRunning() === false) {
113
+ await this.options.webSocketServer.run()
114
+ this.managesWebSocketServerLifecycle = true
115
+ }
116
+ else {
117
+ this.managesWebSocketServerLifecycle = false
118
+ }
119
+
120
+ try {
121
+ this.attach()
122
+ }
123
+ catch (exception) {
124
+ if (this.managesWebSocketServerLifecycle === true) {
125
+ this.managesWebSocketServerLifecycle = false
126
+ await this.options.webSocketServer.close()
127
+ }
128
+ throw exception
129
+ }
130
+ }
131
+
132
+ /**
133
+ * 将当前 Socket 挂接到一个已在运行的 WebSocketServer 上。
134
+ */
135
+ attach(): void {
136
+ if (this.isStarted === true) {
137
+ throw new Error("Socket has already attached.")
138
+ }
139
+
140
+ const server = this.options.webSocketServer.safeGetServer()
141
+ const connectionListener = (webSocket: WebSocket, incomingMessage: IncomingMessage): void => {
142
+ this.handleConnection(webSocket, incomingMessage)
143
+ }
144
+
145
+ server.on("connection", connectionListener)
146
+ this.connectionListener = connectionListener
147
+ this.isStarted = true
148
+ }
149
+
150
+ /**
151
+ * 关闭后重新挂接到服务上。
152
+ */
153
+ async reopen(): Promise<void> {
154
+ await this.close()
155
+ await this.open()
156
+ }
157
+
158
+ /**
159
+ * 关闭当前 Socket 及其托管的全部连接。
160
+ */
161
+ async close(): Promise<void> {
162
+ const closeTasks: Array<Promise<void>> = []
163
+
164
+ if (this.isStarted === true) {
165
+ this.removeConnectionListener()
166
+ this.isStarted = false
167
+ }
168
+
169
+ const socketUnits = [...this.socketUnitMap.values()]
170
+ closeTasks.push(...socketUnits.map(async (socketUnit) => {
171
+ await socketUnit.close()
172
+ }))
173
+
174
+ if (closeTasks.length !== 0) {
175
+ await Promise.all(closeTasks)
176
+ }
177
+
178
+ if (this.managesWebSocketServerLifecycle === true) {
179
+ this.managesWebSocketServerLifecycle = false
180
+ await this.options.webSocketServer.close()
181
+ }
182
+ }
183
+
184
+ /**
185
+ * 返回当前服务端 Socket 的聚合快照。
186
+ */
187
+ getSnapshot(): SocketSnapshot {
188
+ const socketUnitSnapshots = [...this.socketUnitMap.values()].map((item) => {
189
+ return item.getSnapshot()
190
+ })
191
+
192
+ return {
193
+ isStarted: this.isStarted,
194
+ isRunning: this.options.webSocketServer.isRunning(),
195
+ managesWebSocketServerLifecycle: this.managesWebSocketServerLifecycle,
196
+ activeSocketUnitCount: socketUnitSnapshots.length,
197
+ clientIds: socketUnitSnapshots.map((item) => {
198
+ return item.clientId
199
+ }),
200
+ socketUnitSnapshots,
201
+ }
202
+ }
203
+
204
+ /**
205
+ * 返回指定 clientId 对应的 SocketUnit 快照。
206
+ */
207
+ getSocketUnitSnapshot(clientId: string): SocketUnitSnapshot | undefined {
208
+ return this.socketUnitMap.get(clientId)?.getSnapshot()
209
+ }
210
+
211
+ /**
212
+ * 向指定连接发送业务消息。
213
+ */
214
+ sendMessage(clientId: string, message: Message): void {
215
+ const socketUnit = this.socketUnitMap.get(clientId)
216
+ if (socketUnit === undefined) {
217
+ this.logger.warn(`Business message send skipped because client was not found: ${clientId}`)
218
+ return
219
+ }
220
+
221
+ socketUnit.sendMessage(message)
222
+ }
223
+
224
+ protected handleConnection(webSocket: WebSocket, incomingMessage: IncomingMessage): void {
225
+ const clientId: string = generateUuidV4FromUrl()
226
+ const socketUnit = new SocketUnit<Message>({
227
+ logger: Logger.derive(this.logger).setName(`SocketUnit-${clientId}`),
228
+ clientId,
229
+ webSocket,
230
+ incomingMessage,
231
+ enableInitialMessage: this.options.enableInitialMessage,
232
+ initialMessageBuilder: this.options.initialMessageBuilder,
233
+ heartbeat: this.options.heartbeat,
234
+ onMessage: (payload) => {
235
+ this.eventManager.emit("message", payload)
236
+ this.options.onMessage?.(payload)
237
+ },
238
+ onClose: (closedClientId) => {
239
+ this.socketUnitMap.delete(closedClientId)
240
+ this.eventManager.emit("close", { clientId: closedClientId })
241
+ },
242
+ })
243
+ this.socketUnitMap.set(clientId, socketUnit)
244
+ this.logger.log(`WebSocket connected: ${clientId}`)
245
+ socketUnit.start()
246
+ this.eventManager.emit("connect", { clientId })
247
+ }
248
+
249
+ protected removeConnectionListener(): void {
250
+ const connectionListener = this.connectionListener
251
+ this.connectionListener = undefined
252
+
253
+ if (connectionListener === undefined) {
254
+ return
255
+ }
256
+
257
+ if (this.options.webSocketServer.isRunning() === false) {
258
+ return
259
+ }
260
+
261
+ const server = this.options.webSocketServer.safeGetServer()
262
+ server.off("connection", connectionListener)
263
+ }
264
+ }
@@ -1,13 +1,13 @@
1
- import type { BaseEvents } from "#Source/event/index.ts"
1
+ import type { BuildEvents } from "#Source/event/index.ts"
2
2
  import { EventManager } from "#Source/event/index.ts"
3
3
  import { Mutex } from "#Source/orchestration/index.ts"
4
4
 
5
5
  /**
6
6
  * 表式运行时存储结构的事件集合。
7
7
  */
8
- export interface TableEvents<Row extends BaseRow = BaseRow> extends BaseEvents {
8
+ export type TableEvents<Row extends BaseRow = BaseRow> = BuildEvents<{
9
9
  valueChanged: (rows: Row[]) => void
10
- }
10
+ }>
11
11
 
12
12
  /**
13
13
  * 表行数据的基础结构。
@@ -1,4 +1,4 @@
1
- import type { BaseEvents } from "#Source/event/index.ts"
1
+ import type { BuildEvents } from "#Source/event/index.ts"
2
2
  import { EventManager } from "#Source/event/index.ts"
3
3
 
4
4
  import { MinHeap } from "./min-heap.ts"
@@ -33,12 +33,12 @@ export type ExpirationDict<ExpirationName extends string> = {
33
33
  *
34
34
  * 当管理器处于暂停状态时,事件不会立即发出,而是延后并折叠为恢复时的最新快照。
35
35
  */
36
- export interface ExpirationManagerEvents<ExpirationName extends string> extends BaseEvents {
36
+ export type ExpirationManagerEvents<ExpirationName extends string> = BuildEvents<{
37
37
  /**
38
38
  * 在保留的过期状态快照发生可观察变化时发出最新结果。
39
39
  */
40
40
  expirationState: (expirationState: ExpirationState<ExpirationName>) => void
41
- }
41
+ }>
42
42
 
43
43
  /**
44
44
  * Hold one coalesced emission payload derived from current manager state.
@@ -1,6 +1,6 @@
1
1
  import { EventManager } from "#Source/event/index.ts"
2
2
 
3
- import type { BaseEvents, SubscriberEntry } from "#Source/event/index.ts"
3
+ import type { BuildEvents, SubscriberEntry } from "#Source/event/index.ts"
4
4
  import type { ExpirationManager, ExpirationManagerEvents } from "./expiration-manager.ts"
5
5
 
6
6
  /**
@@ -18,12 +18,12 @@ export type RemainingDict<ExpirationName extends string> = {
18
18
  /**
19
19
  * 描述 `RemainingManager` 对外发出的事件表。
20
20
  */
21
- export interface RemainingManagerEvents<ExpirationName extends string> extends BaseEvents {
21
+ export type RemainingManagerEvents<ExpirationName extends string> = BuildEvents<{
22
22
  /**
23
23
  * 在剩余时间快照发生变化时发出最新结果。
24
24
  */
25
25
  remaining: (remaining: RemainingDict<ExpirationName>) => void
26
- }
26
+ }>
27
27
 
28
28
  /**
29
29
  * 描述创建 `RemainingManager` 时可用的配置项。
@@ -0,0 +1,99 @@
1
+ # Tube
2
+
3
+ ## Description
4
+
5
+ Tube 模块提供围绕运行时数据通道(runtime data conduit)的通用建模能力,用于把一段会经历开启、启动、接收数据、出现错误、结束与关闭等生命周期的数据传递过程,组织为可观察、可组合且可选择重放历史记录的稳定公共语义。
6
+
7
+ 它关注的不是某个具体传输协议、宿主流实现或消息中间件接口本身,而是“当一段数据传递过程需要在运行时被明确建模为一个有生命周期、有事件面、可回放最近数据、并可与其它边界协作的通道对象时,应暴露什么样的长期语义”这一类更一般的问题。因此,这个模块更适合被理解为一组数据通道模型,而不是事件总线工具箱、Node.js Stream 包装层,或为了临时转发数据而拼接出来的便捷函数集合。
8
+
9
+ ## For Understanding
10
+
11
+ 理解 Tube 模块时,应先把它看作“有生命周期的数据通道”建模层,而不是把任何会发出事件或传递值的对象都简单归并到这里。它真正要表达的,不只是“某个值能不能被推送出去”,还包括一段通道是否已经打开、是否已经开始接收数据、是否已经变为非空、是否已经发生错误、是否已经结束,以及这些状态变化如何以清楚的事件面暴露给外部。
12
+
13
+ Tube 的核心价值,在于把一段原本容易散落在回调、标志位和局部变量里的通道过程,收束为一份稳定模型。这样做的重点不是单纯提供一个 `pushData` 方法,而是让“生命周期顺序”“数据到达语义”“错误传播语义”“历史回放语义”“与其它通道或宿主流互转的边界”都可以被统一理解和长期维护。
14
+
15
+ 理解这个模块时,特别要守住几条边界:
16
+
17
+ - Tube 模块表达的是运行时数据通道及其生命周期,而不是传输协议、Socket 连接、HTTP 请求、文件读取或浏览器流 API 本身。
18
+ - 它适合描述通道何时开始可接收数据、何时真正进入流动状态、何时首次变为非空、何时结束以及如何对这些变化做出订阅。
19
+ - 它可以与 `ReadableStream` 等宿主流结构互相转换,也可以连接上下游通道,但这些都应被理解为围绕通道模型发生的集成接点,而不是让宿主 API 细节反过来定义 Tube 的公共语义。
20
+ - 它不应直接承担背压(backpressure)协商、传输重试、协议握手、序列化格式、连接保活、分布式消息确认或业务专属路由规则等职责。
21
+ - 数据历史回放的价值,在于帮助新订阅者接入一个已经运行中的通道,而不是把 Tube 退化成无限责任的缓存容器;因此扩展时应优先围绕“通道中的历史视图”思考,而不是围绕“持久存储”思考。
22
+
23
+ ## For Using
24
+
25
+ 当你希望在应用内部表达一段真正有生命周期的数据传递过程,而不是把数据流转逻辑拆散到多个事件回调、Promise 链或宿主流对象的临时包装里时,可以使用 Tube 模块。
26
+
27
+ 从使用角度看,这个模块大致可以分为三类能力:
28
+
29
+ - 生命周期控制能力:用于表达通道的打开、启动、结束、关闭以及错误发生后的自动联动行为,适合把一段通道过程收束为稳定状态机。
30
+ - 事件与数据订阅能力:用于观察 `open`、`start`、`wet`、`error`、`end`、`close` 与数据到达本身,并根据需要为新订阅者重放最近的历史数据。
31
+ - 集成辅助能力:用于连接上下游 Tube、把 Tube 收束为 Promise,或与 `ReadableStream` 等宿主流结构互相转换。
32
+
33
+ 更合适的接入方式,通常是先判断你的核心对象是不是“一段需要被长期观察和组合的数据通道过程”。如果你需要的是:
34
+
35
+ - 某个生产者持续把数据推给多个订阅方,并且这些订阅方还需要感知通道生命周期;
36
+ - 一个已经开始流动的通道要允许新接入者按需看到最近历史,而不是只看到未来数据;
37
+ - 你希望把宿主流结构转换为一份带稳定生命周期语义的对象,再与其它模型协作;
38
+ - 上下游两个处理阶段之间需要一个清楚的数据通道边界,而不是若干零散回调直接耦合;
39
+
40
+ 那么这个模块就是合适的边界。
41
+
42
+ 相反,如果你的需求只是宿主环境里某个具体流 API 的底层控制、传输协议细节、连接状态机、消息编码格式、流量控制或业务专属转发规则,那么这些通常不应直接落在 Tube 模块本身,而应放在对应宿主模块、适配层或更外层系统中。
43
+
44
+ ## For Contributing
45
+
46
+ 为 Tube 模块贡献内容时,优先判断新增能力是否真的在澄清“运行时数据通道如何被建模为稳定生命周期对象”这一问题,而不是只是在补一个临时好用的转发函数或宿主 API 包装。这个模块应长期服务于“通道状态如何表达”“数据与错误如何被送入并传播”“订阅者如何观察生命周期与数据”“历史如何被有边界地回放”“通道如何与其它模型形成集成接点”这几类问题。
47
+
48
+ 扩展这个模块时,应特别警惕以下倾向:把 Tube 退化成普通事件总线;把某个宿主流实现的局部行为直接暴露为公共承诺;把连接、协议、背压、重试、路由或业务缓存逻辑无差别地下沉到 Tube 内部;或者为了当前实现方便而公开内部状态结构。文档应说明哪些通道语义是长期成立的,以及为什么这些语义成立,而不是复述某个版本里恰好采用的局部实现技巧。
49
+
50
+ 当前已经落地的 `Tube` 应被理解为 Tube 模块下的核心通道模型,而 `connectTube`、`tubeToPromise`、`tubeToReadableStream` 与 `readableStreamToTube` 则是围绕这一模型形成的集成辅助函数。后续若增加子模块或更多辅助能力,应优先判断它们是否仍然服务于“清楚、稳定的数据通道语义”,而不是把其它更大的问题域误并到这个模块里。
51
+
52
+ ### JSDoc 注释格式要求
53
+
54
+ - 每个公开导出的目标(类型、函数、变量、类等)都应包含 JSDoc 注释,让人在不跳转实现的情况下就能理解用途。
55
+ - JSDoc 注释第一行应为清晰且简洁的描述,该描述优先使用中文(英文也可以)。
56
+ - 如果描述后还有其他内容,应在描述后加一个空行。
57
+ - 如果有示例,应使用 `@example` 标签,后接三重反引号代码块(不带语言标识)。
58
+ - 如果有示例,应包含多个场景,展示不同用法,尤其要覆盖常见组合方式或边界输入。
59
+ - 如果有示例,应使用注释格式说明每个场景:`// Expect: <result>`。
60
+ - 如果有示例,应将结果赋值给 `example1`、`example2` 之类的变量,以保持示例易读。
61
+ - 如果有示例,`// Expect: <result>` 应该位于 `example1`、`example2` 之前,以保持示例的逻辑清晰。
62
+ - 如果有示例,应优先使用确定性示例;避免断言精确的随机输出。
63
+ - 如果函数返回结构化字符串,应展示其预期格式特征。
64
+ - 如果有参考资料,应将 `@see` 放在 `@example` 代码块之后,并用一个空行分隔。
65
+
66
+ ### 实现规范要求
67
+
68
+ - 不同程序元素之间使用一个空行分隔,保持结构清楚。这里的程序元素,通常指函数、类型、常量,以及直接服务于它们的辅助元素。
69
+ - 某程序元素独占的辅助元素与该程序元素本身视为一个整体,不要在它们之间添加空行。
70
+ - 程序元素的辅助元素应该放置在该程序元素的上方,以保持阅读时的逻辑顺序。
71
+ - 若辅助元素被多个程序元素共享,则应将其视为独立的程序元素,放在这些程序元素中第一个相关目标的上方,并与后续程序元素之间保留一个空行。
72
+ - 辅助元素也应该像其它程序元素一样,保持清晰的命名和适当的注释,以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
73
+ - 辅助元素的命名必须以前缀 `internal` 开头(或 `Internal`,大小写不敏感)。
74
+ - 辅助元素永远不要公开导出。
75
+ - 被模块内多个不同文件中的程序元素共享的辅助元素,应该放在一个单独的文件中,例如 `./src/tube/internal.ts`。
76
+ - 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时,才应将其视为子模块。
77
+ - 子模块包含多个文件时,应该为其单独创建子文件夹,并为其创建单独的 Barrel 文件;父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
78
+ - 子模块不需要有自己的 `README.md`。
79
+ - 子模块可以有自己的 `internal.ts` 文件,多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中,单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
80
+ - 对模块依赖关系的要求(通常是不循环依赖或不反向依赖)与对 DRY 的要求可能产生冲突。此时,若复用的代码数量不大,可以适当牺牲 DRY,复制粘贴并保留必要的注释说明;若复用的代码数量较大,则可以将其抽象到新的文件或子模块中,如 `common.ts`,并在需要的地方导入使用。
81
+ - 与 tube 相关的实现应优先围绕生命周期、数据进入语义、错误传播、历史回放与集成接点组织,避免把协议细节、连接控制或宿主流底层策略直接混入模块公共边界。
82
+
83
+ ### 导出策略要求
84
+
85
+ - 保持内部辅助项和内部符号为私有,不要让外部接入依赖临时性的内部结构。
86
+ - 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
87
+ - Barrel 文件应命名为 `index.ts`,放在模块目录根部,并且所有公共 API 都应从该文件导出。
88
+ - 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的通道语义,而不是某段宿主实现细节的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
89
+
90
+ ### 测试要求
91
+
92
+ - 若程序元素是函数,则只为该函数编写一个测试,如果该函数需要测试多个用例,应放在同一个测试中。
93
+ - 若程序元素是类,则至少要为该类的每一个方法编写一个测试,如果该方法需要测试多个用例,应放在同一个测试中。
94
+ - 若程序元素是类,除了为该类的每一个方法编写至少一个测试之外,还可以为该类编写任意多个测试,以覆盖该类的不同使用场景或边界情况。
95
+ - 若编写测试时需要用到辅助元素(Mock 或 Spy 等),可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单,则可以直接放在每一个测试内部,优先保证每个测试的独立性,而不是追求极致 DRY;若辅助元素较为复杂或需要在多个测试中复用,则可以放在测试文件顶部,供该测试文件中的所有测试使用。
96
+ - 测试顺序应与源文件中被测试目标的原始顺序保持一致。
97
+ - 若该模块不需要测试,必须在说明文件中明确说明该模块不需要测试,并说明理由。一般来说,只有在该模块没有可执行的公共函数、只承载类型层表达,或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时,才适合这样处理。
98
+ - 模块的单元测试文件目录是 `./tests/unit/tube`。
99
+ - 对这个模块来说,测试应优先覆盖生命周期切换、自动联动配置、历史回放、错误传播、订阅与退订行为,以及 Tube 与其它 Tube 或宿主 `ReadableStream` 之间的集成协同行为。
@@ -0,0 +1,137 @@
1
+ import { ReadableStream } from "node:stream/web"
2
+
3
+ import { streamConsumeInMacroTask, toError } from "#Source/basic/index.ts"
4
+ import { Tube } from "./tube.ts"
5
+
6
+ /**
7
+ * 将上游 Tube 的生命周期、错误与数据转发到下游 Tube。
8
+ */
9
+ export const connectTube = <D, E = Error>(
10
+ upstream: Tube<D, E>,
11
+ downstream: Tube<D, E>,
12
+ ): (() => void) => {
13
+ const openEventSubscriber = async (): Promise<void> => {
14
+ await downstream.open()
15
+ }
16
+ upstream.subscribeOpenEvent({ subscriber: openEventSubscriber })
17
+ const closeEventSubscriber = async (): Promise<void> => {
18
+ await downstream.close()
19
+ }
20
+ upstream.subscribeCloseEvent({ subscriber: closeEventSubscriber })
21
+ const startEventSubscriber = async (): Promise<void> => {
22
+ await downstream.start()
23
+ }
24
+ upstream.subscribeStartEvent({ subscriber: startEventSubscriber })
25
+ const endEventSubscriber = async (): Promise<void> => {
26
+ await downstream.end()
27
+ }
28
+ upstream.subscribeEndEvent({ subscriber: endEventSubscriber })
29
+ const errorEventSubscriber = async (error: E): Promise<void> => {
30
+ await downstream.pushError(error)
31
+ }
32
+ upstream.subscribeErrorEvent({ subscriber: errorEventSubscriber })
33
+ const dataSubscriber = async (data: D): Promise<void> => {
34
+ await downstream.pushData(data)
35
+ }
36
+ upstream.subscribeData({ subscriber: dataSubscriber })
37
+
38
+ return (): void => {
39
+ upstream.unsubscribeOpenEvent(openEventSubscriber)
40
+ upstream.unsubscribeCloseEvent(closeEventSubscriber)
41
+ upstream.unsubscribeStartEvent(startEventSubscriber)
42
+ upstream.unsubscribeEndEvent(endEventSubscriber)
43
+ upstream.unsubscribeErrorEvent(errorEventSubscriber)
44
+ upstream.unsubscribeData(dataSubscriber)
45
+ }
46
+ }
47
+
48
+ /**
49
+ * 把 Tube 收束为一个 Promise,并在通道关闭时以最后一条数据或最新错误结算。
50
+ */
51
+ export const tubeToPromise = async <D, E = Error>(
52
+ tube: Tube<D, E>,
53
+ ): Promise<D> => {
54
+ const promise = new Promise<D>((resolve, reject) => {
55
+ const handle = (): void => {
56
+ if (tube.isError()) {
57
+ reject(toError(tube.safeGetLatestError()))
58
+ unsubscribe()
59
+ }
60
+ if (tube.hasClosed()) {
61
+ if (tube.isWet()) {
62
+ resolve(tube.safeGetLatestData())
63
+ unsubscribe()
64
+ }
65
+ else {
66
+ reject(new Error("No data available"))
67
+ unsubscribe()
68
+ }
69
+ }
70
+ }
71
+ const unsubscribe = tube.subscribeCloseEvent({
72
+ subscriber: () => {
73
+ handle()
74
+ },
75
+ })
76
+ // invoke once
77
+ handle()
78
+ })
79
+ return await promise
80
+ }
81
+
82
+ /**
83
+ * 把 Tube 暴露为一个 `ReadableStream`。
84
+ */
85
+ export const tubeToReadableStream = <D, E = Error>(
86
+ tube: Tube<D, E>,
87
+ ): ReadableStream<D> => {
88
+ const readableStream = new ReadableStream<D>({
89
+ start(controller): void {
90
+ const closeEventSubscriber = (): void => {
91
+ controller.close()
92
+ unsubscribeAll()
93
+ }
94
+ const unsubscribeCloseEvent = tube.subscribeCloseEvent({ subscriber: closeEventSubscriber })
95
+ const errorEventSubscriber = (error: E): void => {
96
+ controller.error(error)
97
+ }
98
+ const unsubscribeErrorEvent = tube.subscribeErrorEvent({ subscriber: errorEventSubscriber })
99
+ const dataSubscriber = (data: D): void => {
100
+ controller.enqueue(data)
101
+ }
102
+ const unsubscribeDataSubscriber = tube.subscribeData({ subscriber: dataSubscriber })
103
+
104
+ const unsubscribeAll = (): void => {
105
+ unsubscribeCloseEvent()
106
+ unsubscribeErrorEvent()
107
+ unsubscribeDataSubscriber()
108
+ }
109
+ },
110
+ })
111
+ return readableStream
112
+ }
113
+
114
+ /**
115
+ * 把 `ReadableStream` 转换为一个立即开始消费上游数据的 Tube。
116
+ *
117
+ * 调用后会立刻开始消费 `ReadableStream`,并将数据推入返回的 Tube。
118
+ */
119
+ export const readableStreamToTube = <D, E = Error>(
120
+ readableStream: ReadableStream<D>,
121
+ ): Tube<D, E> => {
122
+ const tube = new Tube<D, E>({
123
+ historyCount: Infinity,
124
+ replayHistory: true,
125
+ })
126
+
127
+ // NOTE: 这里确保 chunk 的消费和 Tube 的回调穿插在宏任务队列中执行,
128
+ // 而不是所有 chunk 都获取完毕之后才开始处理 Tube 回调。
129
+ streamConsumeInMacroTask({
130
+ readableStream,
131
+ onValue: async chunk => await tube.pushData(chunk),
132
+ onDone: async () => await tube.close(),
133
+ onError: async error => await tube.pushError(error as E),
134
+ })
135
+
136
+ return tube
137
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./tube.ts"
2
+ export * from "./helper.ts"