@yunzai-ng/core 0.1.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 (357) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +51 -0
  3. package/dist/adapter/accounts.d.ts +174 -0
  4. package/dist/adapter/accounts.d.ts.map +1 -0
  5. package/dist/adapter/accounts.js +653 -0
  6. package/dist/adapter/accounts.js.map +1 -0
  7. package/dist/adapter/bots.d.ts +142 -0
  8. package/dist/adapter/bots.d.ts.map +1 -0
  9. package/dist/adapter/bots.js +454 -0
  10. package/dist/adapter/bots.js.map +1 -0
  11. package/dist/adapter/host.d.ts +114 -0
  12. package/dist/adapter/host.d.ts.map +1 -0
  13. package/dist/adapter/host.js +90 -0
  14. package/dist/adapter/host.js.map +1 -0
  15. package/dist/adapter/login.d.ts +174 -0
  16. package/dist/adapter/login.d.ts.map +1 -0
  17. package/dist/adapter/login.js +345 -0
  18. package/dist/adapter/login.js.map +1 -0
  19. package/dist/adapter/registry.d.ts +102 -0
  20. package/dist/adapter/registry.d.ts.map +1 -0
  21. package/dist/adapter/registry.js +150 -0
  22. package/dist/adapter/registry.js.map +1 -0
  23. package/dist/config/core-config.d.ts +151 -0
  24. package/dist/config/core-config.d.ts.map +1 -0
  25. package/dist/config/core-config.js +338 -0
  26. package/dist/config/core-config.js.map +1 -0
  27. package/dist/config/schema.d.ts +449 -0
  28. package/dist/config/schema.d.ts.map +1 -0
  29. package/dist/config/schema.js +871 -0
  30. package/dist/config/schema.js.map +1 -0
  31. package/dist/config/store.d.ts +184 -0
  32. package/dist/config/store.d.ts.map +1 -0
  33. package/dist/config/store.js +428 -0
  34. package/dist/config/store.js.map +1 -0
  35. package/dist/config/yaml.d.ts +23 -0
  36. package/dist/config/yaml.d.ts.map +1 -0
  37. package/dist/config/yaml.js +108 -0
  38. package/dist/config/yaml.js.map +1 -0
  39. package/dist/http/client.d.ts +82 -0
  40. package/dist/http/client.d.ts.map +1 -0
  41. package/dist/http/client.js +658 -0
  42. package/dist/http/client.js.map +1 -0
  43. package/dist/index.d.ts +77 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +94 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/kernel/app.d.ts +244 -0
  48. package/dist/kernel/app.d.ts.map +1 -0
  49. package/dist/kernel/app.js +654 -0
  50. package/dist/kernel/app.js.map +1 -0
  51. package/dist/kernel/kv-sink.d.ts +34 -0
  52. package/dist/kernel/kv-sink.d.ts.map +1 -0
  53. package/dist/kernel/kv-sink.js +25 -0
  54. package/dist/kernel/kv-sink.js.map +1 -0
  55. package/dist/kernel/policy.d.ts +77 -0
  56. package/dist/kernel/policy.d.ts.map +1 -0
  57. package/dist/kernel/policy.js +99 -0
  58. package/dist/kernel/policy.js.map +1 -0
  59. package/dist/kernel/runtime.d.ts +117 -0
  60. package/dist/kernel/runtime.d.ts.map +1 -0
  61. package/dist/kernel/runtime.js +197 -0
  62. package/dist/kernel/runtime.js.map +1 -0
  63. package/dist/kernel/sql-sink.d.ts +36 -0
  64. package/dist/kernel/sql-sink.d.ts.map +1 -0
  65. package/dist/kernel/sql-sink.js +114 -0
  66. package/dist/kernel/sql-sink.js.map +1 -0
  67. package/dist/logger/format.d.ts +67 -0
  68. package/dist/logger/format.d.ts.map +1 -0
  69. package/dist/logger/format.js +218 -0
  70. package/dist/logger/format.js.map +1 -0
  71. package/dist/logger/index.d.ts +97 -0
  72. package/dist/logger/index.d.ts.map +1 -0
  73. package/dist/logger/index.js +363 -0
  74. package/dist/logger/index.js.map +1 -0
  75. package/dist/logger/rotate.d.ts +47 -0
  76. package/dist/logger/rotate.d.ts.map +1 -0
  77. package/dist/logger/rotate.js +242 -0
  78. package/dist/logger/rotate.js.map +1 -0
  79. package/dist/message/segment.d.ts +255 -0
  80. package/dist/message/segment.d.ts.map +1 -0
  81. package/dist/message/segment.js +510 -0
  82. package/dist/message/segment.js.map +1 -0
  83. package/dist/message/split.d.ts +34 -0
  84. package/dist/message/split.d.ts.map +1 -0
  85. package/dist/message/split.js +79 -0
  86. package/dist/message/split.js.map +1 -0
  87. package/dist/message/target.d.ts +29 -0
  88. package/dist/message/target.d.ts.map +1 -0
  89. package/dist/message/target.js +34 -0
  90. package/dist/message/target.js.map +1 -0
  91. package/dist/pipeline/cooldown.d.ts +84 -0
  92. package/dist/pipeline/cooldown.d.ts.map +1 -0
  93. package/dist/pipeline/cooldown.js +94 -0
  94. package/dist/pipeline/cooldown.js.map +1 -0
  95. package/dist/pipeline/dispatch.d.ts +98 -0
  96. package/dist/pipeline/dispatch.d.ts.map +1 -0
  97. package/dist/pipeline/dispatch.js +323 -0
  98. package/dist/pipeline/dispatch.js.map +1 -0
  99. package/dist/pipeline/event.d.ts +130 -0
  100. package/dist/pipeline/event.d.ts.map +1 -0
  101. package/dist/pipeline/event.js +538 -0
  102. package/dist/pipeline/event.js.map +1 -0
  103. package/dist/pipeline/middleware.d.ts +63 -0
  104. package/dist/pipeline/middleware.d.ts.map +1 -0
  105. package/dist/pipeline/middleware.js +174 -0
  106. package/dist/pipeline/middleware.js.map +1 -0
  107. package/dist/pipeline/prompt.d.ts +80 -0
  108. package/dist/pipeline/prompt.d.ts.map +1 -0
  109. package/dist/pipeline/prompt.js +155 -0
  110. package/dist/pipeline/prompt.js.map +1 -0
  111. package/dist/pipeline/router.d.ts +98 -0
  112. package/dist/pipeline/router.d.ts.map +1 -0
  113. package/dist/pipeline/router.js +489 -0
  114. package/dist/pipeline/router.js.map +1 -0
  115. package/dist/platform/detect.d.ts +24 -0
  116. package/dist/platform/detect.d.ts.map +1 -0
  117. package/dist/platform/detect.js +178 -0
  118. package/dist/platform/detect.js.map +1 -0
  119. package/dist/platform/paths.d.ts +38 -0
  120. package/dist/platform/paths.d.ts.map +1 -0
  121. package/dist/platform/paths.js +129 -0
  122. package/dist/platform/paths.js.map +1 -0
  123. package/dist/platform/system.d.ts +71 -0
  124. package/dist/platform/system.d.ts.map +1 -0
  125. package/dist/platform/system.js +242 -0
  126. package/dist/platform/system.js.map +1 -0
  127. package/dist/plugin/context.d.ts +92 -0
  128. package/dist/plugin/context.d.ts.map +1 -0
  129. package/dist/plugin/context.js +573 -0
  130. package/dist/plugin/context.js.map +1 -0
  131. package/dist/plugin/define.d.ts +92 -0
  132. package/dist/plugin/define.d.ts.map +1 -0
  133. package/dist/plugin/define.js +82 -0
  134. package/dist/plugin/define.js.map +1 -0
  135. package/dist/plugin/discover.d.ts +106 -0
  136. package/dist/plugin/discover.d.ts.map +1 -0
  137. package/dist/plugin/discover.js +254 -0
  138. package/dist/plugin/discover.js.map +1 -0
  139. package/dist/plugin/events.d.ts +109 -0
  140. package/dist/plugin/events.d.ts.map +1 -0
  141. package/dist/plugin/events.js +209 -0
  142. package/dist/plugin/events.js.map +1 -0
  143. package/dist/plugin/hooks.d.ts +266 -0
  144. package/dist/plugin/hooks.d.ts.map +1 -0
  145. package/dist/plugin/hooks.js +84 -0
  146. package/dist/plugin/hooks.js.map +1 -0
  147. package/dist/plugin/host.d.ts +160 -0
  148. package/dist/plugin/host.d.ts.map +1 -0
  149. package/dist/plugin/host.js +546 -0
  150. package/dist/plugin/host.js.map +1 -0
  151. package/dist/plugin/market.d.ts +264 -0
  152. package/dist/plugin/market.d.ts.map +1 -0
  153. package/dist/plugin/market.js +700 -0
  154. package/dist/plugin/market.js.map +1 -0
  155. package/dist/plugin/services.d.ts +116 -0
  156. package/dist/plugin/services.d.ts.map +1 -0
  157. package/dist/plugin/services.js +213 -0
  158. package/dist/plugin/services.js.map +1 -0
  159. package/dist/plugin/tar.d.ts +65 -0
  160. package/dist/plugin/tar.d.ts.map +1 -0
  161. package/dist/plugin/tar.js +277 -0
  162. package/dist/plugin/tar.js.map +1 -0
  163. package/dist/render/registry.d.ts +127 -0
  164. package/dist/render/registry.d.ts.map +1 -0
  165. package/dist/render/registry.js +306 -0
  166. package/dist/render/registry.js.map +1 -0
  167. package/dist/scheduler/index.d.ts +67 -0
  168. package/dist/scheduler/index.d.ts.map +1 -0
  169. package/dist/scheduler/index.js +279 -0
  170. package/dist/scheduler/index.js.map +1 -0
  171. package/dist/server/api.d.ts +179 -0
  172. package/dist/server/api.d.ts.map +1 -0
  173. package/dist/server/api.js +602 -0
  174. package/dist/server/api.js.map +1 -0
  175. package/dist/server/auth.d.ts +83 -0
  176. package/dist/server/auth.d.ts.map +1 -0
  177. package/dist/server/auth.js +178 -0
  178. package/dist/server/auth.js.map +1 -0
  179. package/dist/server/browse.d.ts +60 -0
  180. package/dist/server/browse.d.ts.map +1 -0
  181. package/dist/server/browse.js +187 -0
  182. package/dist/server/browse.js.map +1 -0
  183. package/dist/server/files.d.ts +27 -0
  184. package/dist/server/files.d.ts.map +1 -0
  185. package/dist/server/files.js +76 -0
  186. package/dist/server/files.js.map +1 -0
  187. package/dist/server/index.d.ts +189 -0
  188. package/dist/server/index.d.ts.map +1 -0
  189. package/dist/server/index.js +1123 -0
  190. package/dist/server/index.js.map +1 -0
  191. package/dist/server/table.d.ts +105 -0
  192. package/dist/server/table.d.ts.map +1 -0
  193. package/dist/server/table.js +289 -0
  194. package/dist/server/table.js.map +1 -0
  195. package/dist/store/index.d.ts +50 -0
  196. package/dist/store/index.d.ts.map +1 -0
  197. package/dist/store/index.js +120 -0
  198. package/dist/store/index.js.map +1 -0
  199. package/dist/store/json.d.ts +73 -0
  200. package/dist/store/json.d.ts.map +1 -0
  201. package/dist/store/json.js +174 -0
  202. package/dist/store/json.js.map +1 -0
  203. package/dist/store/kv.d.ts +126 -0
  204. package/dist/store/kv.d.ts.map +1 -0
  205. package/dist/store/kv.js +232 -0
  206. package/dist/store/kv.js.map +1 -0
  207. package/dist/store/level.d.ts +103 -0
  208. package/dist/store/level.d.ts.map +1 -0
  209. package/dist/store/level.js +119 -0
  210. package/dist/store/level.js.map +1 -0
  211. package/dist/store/memory.d.ts +61 -0
  212. package/dist/store/memory.d.ts.map +1 -0
  213. package/dist/store/memory.js +94 -0
  214. package/dist/store/memory.js.map +1 -0
  215. package/dist/store/sql.d.ts +142 -0
  216. package/dist/store/sql.d.ts.map +1 -0
  217. package/dist/store/sql.js +246 -0
  218. package/dist/store/sql.js.map +1 -0
  219. package/dist/testing/fake.d.ts +109 -0
  220. package/dist/testing/fake.d.ts.map +1 -0
  221. package/dist/testing/fake.js +196 -0
  222. package/dist/testing/fake.js.map +1 -0
  223. package/dist/testing/index.d.ts +16 -0
  224. package/dist/testing/index.d.ts.map +1 -0
  225. package/dist/testing/index.js +16 -0
  226. package/dist/testing/index.js.map +1 -0
  227. package/dist/testing/mock-adapter.d.ts +205 -0
  228. package/dist/testing/mock-adapter.d.ts.map +1 -0
  229. package/dist/testing/mock-adapter.js +309 -0
  230. package/dist/testing/mock-adapter.js.map +1 -0
  231. package/dist/util/deep.d.ts +108 -0
  232. package/dist/util/deep.d.ts.map +1 -0
  233. package/dist/util/deep.js +275 -0
  234. package/dist/util/deep.js.map +1 -0
  235. package/dist/util/defer.d.ts +107 -0
  236. package/dist/util/defer.d.ts.map +1 -0
  237. package/dist/util/defer.js +163 -0
  238. package/dist/util/defer.js.map +1 -0
  239. package/dist/util/dispose.d.ts +88 -0
  240. package/dist/util/dispose.d.ts.map +1 -0
  241. package/dist/util/dispose.js +143 -0
  242. package/dist/util/dispose.js.map +1 -0
  243. package/dist/util/duration.d.ts +28 -0
  244. package/dist/util/duration.d.ts.map +1 -0
  245. package/dist/util/duration.js +77 -0
  246. package/dist/util/duration.js.map +1 -0
  247. package/dist/util/fs.d.ts +110 -0
  248. package/dist/util/fs.d.ts.map +1 -0
  249. package/dist/util/fs.js +269 -0
  250. package/dist/util/fs.js.map +1 -0
  251. package/dist/util/id.d.ts +49 -0
  252. package/dist/util/id.d.ts.map +1 -0
  253. package/dist/util/id.js +87 -0
  254. package/dist/util/id.js.map +1 -0
  255. package/dist/util/lru.d.ts +74 -0
  256. package/dist/util/lru.d.ts.map +1 -0
  257. package/dist/util/lru.js +156 -0
  258. package/dist/util/lru.js.map +1 -0
  259. package/dist/util/queue.d.ts +64 -0
  260. package/dist/util/queue.d.ts.map +1 -0
  261. package/dist/util/queue.js +147 -0
  262. package/dist/util/queue.js.map +1 -0
  263. package/dist/util/text.d.ts +86 -0
  264. package/dist/util/text.d.ts.map +1 -0
  265. package/dist/util/text.js +182 -0
  266. package/dist/util/text.js.map +1 -0
  267. package/package.json +44 -0
  268. package/src/adapter/accounts.ts +758 -0
  269. package/src/adapter/bots.ts +582 -0
  270. package/src/adapter/host.ts +224 -0
  271. package/src/adapter/login.ts +481 -0
  272. package/src/adapter/registry.ts +202 -0
  273. package/src/config/core-config.test.ts +60 -0
  274. package/src/config/core-config.ts +401 -0
  275. package/src/config/schema.test.ts +179 -0
  276. package/src/config/schema.ts +1036 -0
  277. package/src/config/store.test.ts +245 -0
  278. package/src/config/store.ts +506 -0
  279. package/src/config/yaml.ts +119 -0
  280. package/src/http/client.test.ts +568 -0
  281. package/src/http/client.ts +777 -0
  282. package/src/index.ts +106 -0
  283. package/src/kernel/app.test.ts +267 -0
  284. package/src/kernel/app.ts +843 -0
  285. package/src/kernel/kv-sink.ts +58 -0
  286. package/src/kernel/policy.ts +120 -0
  287. package/src/kernel/runtime.test.ts +542 -0
  288. package/src/kernel/runtime.ts +307 -0
  289. package/src/kernel/sql-sink.ts +154 -0
  290. package/src/logger/format.ts +259 -0
  291. package/src/logger/index.ts +427 -0
  292. package/src/logger/rotate.ts +265 -0
  293. package/src/message/segment.test.ts +335 -0
  294. package/src/message/segment.ts +569 -0
  295. package/src/message/split.ts +97 -0
  296. package/src/message/target.ts +48 -0
  297. package/src/pipeline/cooldown.ts +136 -0
  298. package/src/pipeline/dispatch.ts +419 -0
  299. package/src/pipeline/event.ts +735 -0
  300. package/src/pipeline/middleware.test.ts +426 -0
  301. package/src/pipeline/middleware.ts +199 -0
  302. package/src/pipeline/prompt.ts +212 -0
  303. package/src/pipeline/router.test.ts +432 -0
  304. package/src/pipeline/router.ts +575 -0
  305. package/src/platform/detect.ts +178 -0
  306. package/src/platform/paths.ts +147 -0
  307. package/src/platform/system.test.ts +102 -0
  308. package/src/platform/system.ts +289 -0
  309. package/src/plugin/context.test.ts +459 -0
  310. package/src/plugin/context.ts +757 -0
  311. package/src/plugin/define.test.ts +120 -0
  312. package/src/plugin/define.ts +142 -0
  313. package/src/plugin/discover.test.ts +253 -0
  314. package/src/plugin/discover.ts +353 -0
  315. package/src/plugin/events.test.ts +162 -0
  316. package/src/plugin/events.ts +266 -0
  317. package/src/plugin/hooks.ts +373 -0
  318. package/src/plugin/host.test.ts +696 -0
  319. package/src/plugin/host.ts +738 -0
  320. package/src/plugin/market.test.ts +781 -0
  321. package/src/plugin/market.ts +870 -0
  322. package/src/plugin/services.test.ts +135 -0
  323. package/src/plugin/services.ts +261 -0
  324. package/src/plugin/tar.test.ts +158 -0
  325. package/src/plugin/tar.ts +309 -0
  326. package/src/render/registry.test.ts +127 -0
  327. package/src/render/registry.ts +439 -0
  328. package/src/scheduler/index.ts +357 -0
  329. package/src/server/api.test.ts +802 -0
  330. package/src/server/api.ts +869 -0
  331. package/src/server/auth.ts +201 -0
  332. package/src/server/browse.test.ts +193 -0
  333. package/src/server/browse.ts +240 -0
  334. package/src/server/files.ts +97 -0
  335. package/src/server/index.test.ts +566 -0
  336. package/src/server/index.ts +1386 -0
  337. package/src/server/table.ts +351 -0
  338. package/src/store/index.ts +156 -0
  339. package/src/store/json.ts +198 -0
  340. package/src/store/kv.test.ts +307 -0
  341. package/src/store/kv.ts +256 -0
  342. package/src/store/level.ts +167 -0
  343. package/src/store/memory.ts +109 -0
  344. package/src/store/sql.test.ts +204 -0
  345. package/src/store/sql.ts +324 -0
  346. package/src/testing/fake.ts +297 -0
  347. package/src/testing/index.ts +15 -0
  348. package/src/testing/mock-adapter.ts +588 -0
  349. package/src/util/deep.ts +272 -0
  350. package/src/util/defer.ts +211 -0
  351. package/src/util/dispose.ts +169 -0
  352. package/src/util/duration.ts +82 -0
  353. package/src/util/fs.ts +279 -0
  354. package/src/util/id.ts +94 -0
  355. package/src/util/lru.ts +185 -0
  356. package/src/util/queue.ts +167 -0
  357. package/src/util/text.ts +189 -0
@@ -0,0 +1,426 @@
1
+ /**
2
+ * 模块职责:中间件管线的单元测试 —— 洋葱顺序、优先级、事件大类分桶、拦截语义
3
+ * 依赖方向:测试文件,依赖 pipeline/middleware 与 testing/fake
4
+ * 生命周期:每个用例新建一条管线
5
+ * 注意事项:这里也不建真事件 —— 管线只读 `kind`/`stopped`/`id` 三个字段。
6
+ * 真事件那一侧(中间件改写 message 后路由按改写结果匹配)由
7
+ * kernel/runtime.test.ts 覆盖。
8
+ *
9
+ * 顺序断言一律用一个 `trace: string[]`,进出各记一笔(`a>` / `a<`)。
10
+ * 仅断言"哪些中间件被执行"并不充分:中间件最易出错之处为 `next()` 之后的收尾代码
11
+ * 跑在了内层之前,而那只有进出成对记录才看得出来。
12
+ */
13
+ import { describe, expect, it } from "vitest"
14
+ import type { AnyEvent, EventKind, Middleware, MiddlewareOptions } from "@yunzai-ng/types"
15
+ import type { MiddlewareRegistration } from "../plugin/hooks.js"
16
+ import { fakeLogger, type FakeLogger } from "../testing/fake.js"
17
+ import { MiddlewarePipeline } from "./middleware.js"
18
+
19
+ /** 够管线用的假事件 */
20
+ interface FakeEvent {
21
+ /** 事件大类 */
22
+ kind: EventKind
23
+ /** 事件 id */
24
+ id: string
25
+ /** 是否已被阻断 */
26
+ stopped: boolean
27
+ /** 阻断 */
28
+ stop: () => void
29
+ }
30
+
31
+ /**
32
+ * 造一个假事件
33
+ * @param kind 事件大类,缺省消息
34
+ * @returns 假事件
35
+ */
36
+ function ev(kind: EventKind = "message"): FakeEvent {
37
+ const e: FakeEvent = {
38
+ kind,
39
+ id: `e-${kind}`,
40
+ stopped: false,
41
+ stop: () => {
42
+ e.stopped = true
43
+ }
44
+ }
45
+ return e
46
+ }
47
+
48
+ /**
49
+ * 造一条中间件登记
50
+ * @param plugin 插件名
51
+ * @param fn 中间件函数
52
+ * @param options 注册选项
53
+ * @returns 登记内容
54
+ */
55
+ function reg(plugin: string, fn: Middleware, options: MiddlewareOptions = {}): MiddlewareRegistration {
56
+ return { plugin, fn, options }
57
+ }
58
+
59
+ /**
60
+ * 造一条只在 trace 里留下进出记录的中间件
61
+ * @param tag 标记
62
+ * @param trace 记录数组
63
+ * @returns 中间件登记用的函数
64
+ */
65
+ function tracer(tag: string, trace: string[]): Middleware {
66
+ return async (_e, next) => {
67
+ trace.push(`${tag}>`)
68
+ await next()
69
+ trace.push(`${tag}<`)
70
+ }
71
+ }
72
+
73
+ /** 建一条管线与它的日志器 */
74
+ function make(): { pipe: MiddlewarePipeline; logger: FakeLogger } {
75
+ const logger = fakeLogger()
76
+ return { pipe: new MiddlewarePipeline(logger), logger }
77
+ }
78
+
79
+ /**
80
+ * 执行一遍管线
81
+ *
82
+ * 将 `as unknown as AnyEvent` 收束于此一处,以使用例中的 `e` 保持 FakeEvent 类型、
83
+ * 可直接断言 `e.stopped`。
84
+ * @param pipe 管线
85
+ * @param e 假事件
86
+ * @param core 最内层
87
+ */
88
+ async function run(pipe: MiddlewarePipeline, e: FakeEvent, core: () => Promise<void>): Promise<void> {
89
+ await pipe.run(e as unknown as AnyEvent, core)
90
+ }
91
+
92
+ /**
93
+ * 造一个往 trace 里记一笔的最内层
94
+ * @param trace 记录数组
95
+ * @returns 最内层函数
96
+ */
97
+ function core(trace: string[]): () => Promise<void> {
98
+ return async () => {
99
+ trace.push("core")
100
+ return Promise.resolve()
101
+ }
102
+ }
103
+
104
+ describe("执行顺序", () => {
105
+ it("洋葱模型:next() 之后的收尾在内层执行完毕之后执行", async () => {
106
+ const { pipe } = make()
107
+ const trace: string[] = []
108
+ pipe.register(reg("甲", tracer("甲", trace)))
109
+ pipe.register(reg("乙", tracer("乙", trace)))
110
+
111
+ await run(pipe, ev(), core(trace))
112
+
113
+ expect(trace).toEqual(["甲>", "乙>", "core", "乙<", "甲<"])
114
+ })
115
+
116
+ it("priority 小者在外层,同优先级按注册先后", async () => {
117
+ const { pipe } = make()
118
+ const trace: string[] = []
119
+ pipe.register(reg("先注册", tracer("先注册", trace)))
120
+ pipe.register(reg("最外层", tracer("最外层", trace), { priority: 10 }))
121
+ pipe.register(reg("后注册", tracer("后注册", trace)))
122
+ pipe.register(reg("最内层", tracer("最内层", trace), { priority: 900 }))
123
+
124
+ await run(pipe, ev(), core(trace))
125
+
126
+ expect(trace).toEqual([
127
+ "最外层>",
128
+ "先注册>",
129
+ "后注册>",
130
+ "最内层>",
131
+ "core",
132
+ "最内层<",
133
+ "后注册<",
134
+ "先注册<",
135
+ "最外层<"
136
+ ])
137
+ })
138
+
139
+ it("一条中间件都没有时也会执行最内层", async () => {
140
+ const { pipe } = make()
141
+ const trace: string[] = []
142
+
143
+ await run(pipe, ev(), core(trace))
144
+
145
+ expect(trace).toEqual(["core"])
146
+ })
147
+ })
148
+
149
+ describe("事件大类分桶", () => {
150
+ it("kind 限定生效,单值与数组两种写法都算", async () => {
151
+ const { pipe } = make()
152
+ const trace: string[] = []
153
+ pipe.register(reg("只管消息", tracer("只管消息", trace), { kind: "message" }))
154
+ pipe.register(reg("管通知与请求", tracer("管通知与请求", trace), { kind: ["notice", "request"] }))
155
+ pipe.register(reg("全都管", tracer("全都管", trace)))
156
+
157
+ await run(pipe, ev("message"), core(trace))
158
+ expect(trace).toEqual(["只管消息>", "全都管>", "core", "全都管<", "只管消息<"])
159
+
160
+ trace.length = 0
161
+ await run(pipe, ev("notice"), core(trace))
162
+ expect(trace).toEqual(["管通知与请求>", "全都管>", "core", "全都管<", "管通知与请求<"])
163
+
164
+ trace.length = 0
165
+ await run(pipe, ev("meta"), core(trace))
166
+ expect(trace).toEqual(["全都管>", "core", "全都管<"])
167
+ })
168
+
169
+ it("不认识的 kind 不会让最内层被跳过", async () => {
170
+ const { pipe } = make()
171
+ const trace: string[] = []
172
+ pipe.register(reg("全都管", tracer("全都管", trace)))
173
+ const weird = ev()
174
+ weird.kind = "unknown" as EventKind
175
+
176
+ await run(pipe, weird, core(trace))
177
+
178
+ // 桶中不存在该大类:管线应当直接执行核心逻辑,而非丢弃该事件
179
+ expect(trace).toEqual(["core"])
180
+ })
181
+ })
182
+
183
+ describe("拦截语义", () => {
184
+ it("不调 next() 就拦住了后续与最内层", async () => {
185
+ const { pipe } = make()
186
+ const trace: string[] = []
187
+ pipe.register(reg("拦路", async () => void trace.push("拦路")))
188
+ pipe.register(reg("后面的", tracer("后面的", trace)))
189
+
190
+ await run(pipe, ev(), core(trace))
191
+
192
+ expect(trace).toEqual(["拦路"])
193
+ })
194
+
195
+ it("不调 next() 也没 stop() 时留一条 trace 级提示", async () => {
196
+ const { pipe, logger } = make()
197
+ pipe.register(reg("忘了写 next 的插件", async () => Promise.resolve()))
198
+
199
+ await run(pipe, ev(), async () => Promise.resolve())
200
+
201
+ expect(logger.lines.some(l => l.includes("未调用 next()") && l.includes("忘了写 next 的插件"))).toBe(true)
202
+ })
203
+
204
+ it("显式 stop() 后不再提示未调用 next()", async () => {
205
+ const { pipe, logger } = make()
206
+ const e = ev()
207
+ pipe.register(reg("正当拦截", async ev0 => void ev0.stop()))
208
+
209
+ await run(pipe, e, async () => Promise.resolve())
210
+
211
+ expect(e.stopped).toBe(true)
212
+ expect(logger.lines.some(l => l.includes("未调用 next()"))).toBe(false)
213
+ })
214
+
215
+ it("stop() 之后即使调了 next(),下游与最内层也不执行", async () => {
216
+ const { pipe } = make()
217
+ const trace: string[] = []
218
+ pipe.register(
219
+ reg("先停再放行", async (e, next) => {
220
+ trace.push("先停再放行>")
221
+ e.stop()
222
+ await next()
223
+ trace.push("先停再放行<")
224
+ })
225
+ )
226
+ pipe.register(reg("下游", tracer("下游", trace)))
227
+
228
+ await run(pipe, ev(), core(trace))
229
+
230
+ // next() 本身不抛错,只是什么都不做:拦截路径不该逼插件多写一个 try
231
+ expect(trace).toEqual(["先停再放行>", "先停再放行<"])
232
+ })
233
+
234
+ it("事件进来时就已 stopped 的话一条中间件都不跑", async () => {
235
+ const { pipe } = make()
236
+ const trace: string[] = []
237
+ pipe.register(reg("甲", tracer("甲", trace)))
238
+ const e = ev()
239
+ e.stopped = true
240
+
241
+ await run(pipe, e, core(trace))
242
+
243
+ expect(trace).toEqual([])
244
+ })
245
+
246
+ it("空管线遇到已 stopped 的事件也不执行最内层", async () => {
247
+ const { pipe } = make()
248
+ const trace: string[] = []
249
+ const e = ev()
250
+ e.stopped = true
251
+
252
+ await run(pipe, e, core(trace))
253
+
254
+ expect(trace).toEqual([])
255
+ })
256
+ })
257
+
258
+ describe("错误处理", () => {
259
+ it("重复调用 next() 直接抛错,错误信息里点名插件", async () => {
260
+ const { pipe } = make()
261
+ pipe.register(
262
+ reg("话多的插件", async (_e, next) => {
263
+ await next()
264
+ await next()
265
+ })
266
+ )
267
+
268
+ await expect(run(pipe, ev(), async () => Promise.resolve())).rejects.toThrow(
269
+ /话多的插件.*重复调用了 next\(\)/
270
+ )
271
+ })
272
+
273
+ it("中间件抛的错原样冒泡,交给 dispatch 统一处理", async () => {
274
+ const { pipe } = make()
275
+ pipe.register(reg("执行失败的插件", async () => Promise.reject(new Error("插件内部执行失败"))))
276
+
277
+ await expect(run(pipe, ev(), async () => Promise.resolve())).rejects.toThrow("插件内部执行失败")
278
+ })
279
+
280
+ it("最内层抛的错也原样冒泡,且外层的收尾不会被跑", async () => {
281
+ const { pipe } = make()
282
+ const trace: string[] = []
283
+ pipe.register(reg("甲", tracer("甲", trace)))
284
+
285
+ await expect(
286
+ run(pipe, ev(), async () => {
287
+ throw new Error("命令处理执行失败")
288
+ })
289
+ ).rejects.toThrow("命令处理执行失败")
290
+ expect(trace).toEqual(["甲>"])
291
+ })
292
+ })
293
+
294
+ describe("注册表维护", () => {
295
+ it("执行期间新注册的中间件不影响本次执行,下一次才生效", async () => {
296
+ const { pipe } = make()
297
+ const trace: string[] = []
298
+ pipe.register(
299
+ reg("装了个新的", async (_e, next) => {
300
+ pipe.register(reg("半路加入", tracer("半路加入", trace)))
301
+ await next()
302
+ })
303
+ )
304
+
305
+ await run(pipe, ev(), core(trace))
306
+ expect(trace).toEqual(["core"])
307
+
308
+ trace.length = 0
309
+ await run(pipe, ev(), core(trace))
310
+ expect(trace).toEqual(["半路加入>", "core", "半路加入<"])
311
+ })
312
+
313
+ it("注销后不再执行,size 也跟着减", async () => {
314
+ const { pipe } = make()
315
+ const trace: string[] = []
316
+ const off = pipe.register(reg("甲", tracer("甲", trace)))
317
+ expect(pipe.size).toBe(1)
318
+
319
+ off()
320
+ // 幂等:插件卸载时上下文与兜底清理可能都调一次
321
+ off()
322
+
323
+ expect(pipe.size).toBe(0)
324
+ await run(pipe, ev(), core(trace))
325
+ expect(trace).toEqual(["core"])
326
+ })
327
+
328
+ it("重复注册同一条登记是幂等的", async () => {
329
+ const { pipe } = make()
330
+ const trace: string[] = []
331
+ const r = reg("甲", tracer("甲", trace))
332
+ pipe.register(r)
333
+ pipe.register(r)
334
+
335
+ expect(pipe.size).toBe(1)
336
+ await run(pipe, ev(), core(trace))
337
+ expect(trace).toEqual(["甲>", "core", "甲<"])
338
+ })
339
+
340
+ it("removePlugin 只摘该插件的中间件,含多大类登记", async () => {
341
+ const { pipe } = make()
342
+ const trace: string[] = []
343
+ pipe.register(reg("甲", tracer("甲消息", trace), { kind: "message" }))
344
+ pipe.register(reg("甲", tracer("甲全部", trace)))
345
+ pipe.register(reg("乙", tracer("乙", trace)))
346
+
347
+ expect(pipe.removePlugin("甲")).toBe(2)
348
+ expect(pipe.size).toBe(1)
349
+
350
+ await run(pipe, ev(), core(trace))
351
+ expect(trace).toEqual(["乙>", "core", "乙<"])
352
+ // 通知桶里也不该有残留
353
+ trace.length = 0
354
+ await run(pipe, ev("notice"), core(trace))
355
+ expect(trace).toEqual(["乙>", "core", "乙<"])
356
+ })
357
+
358
+ it("clear 清空全部大类的桶", async () => {
359
+ const { pipe } = make()
360
+ const trace: string[] = []
361
+ pipe.register(reg("甲", tracer("甲", trace), { kind: ["message", "notice"] }))
362
+
363
+ pipe.clear()
364
+
365
+ expect(pipe.size).toBe(0)
366
+ await run(pipe, ev(), core(trace))
367
+ await run(pipe, ev("notice"), core(trace))
368
+ expect(trace).toEqual(["core", "core"])
369
+ })
370
+ })
371
+
372
+ describe("对外清单", () => {
373
+ it("**顺序是执行顺序,不是注册顺序** —— 面板上这张表要回答「一条消息先过谁」", () => {
374
+ const { pipe } = make()
375
+ const trace: string[] = []
376
+ pipe.register(reg("后注册的", tracer("后", trace), { priority: 10 }))
377
+ pipe.register(reg("先注册的", tracer("先", trace), { priority: 200 }))
378
+
379
+ expect(pipe.list().map(item => item.plugin)).toEqual(["后注册的", "先注册的"])
380
+ })
381
+
382
+ it("未声明优先级时报出缺省值,而不是留空", () => {
383
+ const { pipe } = make()
384
+ pipe.register(reg("甲", tracer("甲", [])))
385
+ expect(pipe.list()[0]?.priority).toBe(100)
386
+ })
387
+
388
+ it("**未限定 kind 时列出全部大类** —— 「适用于全部」与「一类都不适用」必须分得开", () => {
389
+ const { pipe } = make()
390
+ pipe.register(reg("甲", tracer("甲", [])))
391
+ expect(pipe.list()[0]?.kinds).toEqual(["message", "notice", "request", "meta"])
392
+ })
393
+
394
+ it("限定了 kind 就只列那几类,单值写法也算", () => {
395
+ const { pipe } = make()
396
+ pipe.register(reg("甲", tracer("甲", []), { kind: "notice" }))
397
+ pipe.register(reg("乙", tracer("乙", []), { kind: ["request", "meta"] }))
398
+ const byPlugin = new Map(pipe.list().map(item => [item.plugin, item.kinds]))
399
+ expect(byPlugin.get("甲")).toEqual(["notice"])
400
+ expect(byPlugin.get("乙")).toEqual(["request", "meta"])
401
+ })
402
+
403
+ it("**不管消息的中间件也要列出来** —— 少列一条会让人以为插件没注册上", () => {
404
+ const { pipe } = make()
405
+ pipe.register(reg("只管通知的", tracer("甲", []), { kind: "notice" }))
406
+ expect(pipe.list().map(item => item.plugin)).toEqual(["只管通知的"])
407
+ expect(pipe.list()).toHaveLength(pipe.size)
408
+ })
409
+
410
+ it("条数与 size 始终一致,注销之后也是", () => {
411
+ const { pipe } = make()
412
+ const off = pipe.register(reg("甲", tracer("甲", [])))
413
+ pipe.register(reg("乙", tracer("乙", []), { kind: "meta" }))
414
+ expect(pipe.list()).toHaveLength(2)
415
+ off()
416
+ expect(pipe.list()).toHaveLength(1)
417
+ expect(pipe.list()[0]?.plugin).toBe("乙")
418
+ })
419
+
420
+ it("不含中间件函数本身 —— 那是个闭包,序列化不出去", () => {
421
+ const { pipe } = make()
422
+ pipe.register(reg("甲", tracer("甲", [])))
423
+ expect(pipe.list()[0]).not.toHaveProperty("fn")
424
+ expect(() => JSON.stringify(pipe.list())).not.toThrow()
425
+ })
426
+ })
@@ -0,0 +1,199 @@
1
+ /**
2
+ * 模块职责:Koa 式中间件管线(实现 `MiddlewareSink`)
3
+ * 依赖方向:依赖类型包与 plugin/hooks 的登记结构
4
+ * 生命周期:随内核创建;每条注册返回 Disposer,插件卸载即摘除
5
+ * 注意事项:中间件是"把业务逻辑挡在内核之外"的关键机制。诸如 `*` → 星铁、
6
+ * `%` → 绝区零这类前缀识别,都是插件里的一个中间件,内核对"游戏"
7
+ * 一词毫不知情 —— 写进内核就再也洗不掉。
8
+ *
9
+ * 按事件大类预分桶(`#byKind`)而不是每条消息 filter 一遍:
10
+ * 注册是低频的,消息是高频的。
11
+ *
12
+ * `next()` 只能调一次。重复调用在 Koa 里也是硬错误 —— 它意味着
13
+ * 下游会被跑两遍,而"回复发了两条"这种症状极难追回到源头。
14
+ */
15
+ import type { AnyEvent, Disposer, EventKind, Logger, MiddlewareInfo } from "@yunzai-ng/types"
16
+ import type { MiddlewareRegistration, MiddlewareSink } from "../plugin/hooks.js"
17
+
18
+ /** 缺省优先级 */
19
+ const DEFAULT_PRIORITY = 100
20
+
21
+ /** 全部事件大类 */
22
+ const ALL_KINDS: readonly EventKind[] = ["message", "notice", "request", "meta"]
23
+
24
+ /**
25
+ * 比较两条中间件的执行顺序
26
+ * @param a 登记 a
27
+ * @param b 登记 b
28
+ * @returns 排序结果
29
+ */
30
+ function byPriority(a: MiddlewareRegistration, b: MiddlewareRegistration): number {
31
+ return (a.options.priority ?? DEFAULT_PRIORITY) - (b.options.priority ?? DEFAULT_PRIORITY)
32
+ }
33
+
34
+ /** 中间件管线 */
35
+ export class MiddlewarePipeline implements MiddlewareSink {
36
+ /** 日志器 */
37
+ readonly #logger: Logger
38
+ /** 全部登记,用于 `size` 与按插件摘除 */
39
+ readonly #all = new Set<MiddlewareRegistration>()
40
+ /** 按事件大类预分桶的有序执行链 */
41
+ readonly #byKind = new Map<EventKind, MiddlewareRegistration[]>()
42
+
43
+ /**
44
+ * @param logger 日志器
45
+ */
46
+ constructor(logger: Logger) {
47
+ this.#logger = logger
48
+ for (const kind of ALL_KINDS) this.#byKind.set(kind, [])
49
+ }
50
+
51
+ /** 已注册的中间件条数 */
52
+ get size(): number {
53
+ return this.#all.size
54
+ }
55
+
56
+ /**
57
+ * 列出全部中间件,顺序即实际的执行顺序
58
+ *
59
+ * **按 `message` 桶的顺序输出,不按注册顺序。** 面板上这张表要回答的是
60
+ * 「一条消息先过谁」,而注册顺序与执行顺序并不一致 —— `register()` 是插入排序,
61
+ * 后注册的高优先级中间件会排到前面去。
62
+ *
63
+ * 只对某几类**非** message 事件生效的中间件不在 message 桶里,故补在末尾:
64
+ * 少列一条比顺序不完美糟得多,那会让人以为某个插件没注册上。
65
+ * @returns 中间件描述数组
66
+ */
67
+ list(): MiddlewareInfo[] {
68
+ const ordered = [...(this.#byKind.get("message") ?? [])]
69
+ for (const reg of this.#all) {
70
+ if (!ordered.includes(reg)) ordered.push(reg)
71
+ }
72
+ return ordered.map(reg => ({
73
+ plugin: reg.plugin,
74
+ priority: reg.options.priority ?? DEFAULT_PRIORITY,
75
+ kinds: [...this.#kindsOf(reg)]
76
+ }))
77
+ }
78
+
79
+ /**
80
+ * 登记中间件
81
+ * @param reg 登记内容
82
+ * @returns 注销句柄
83
+ */
84
+ register(reg: MiddlewareRegistration): Disposer {
85
+ if (this.#all.has(reg)) return () => this.#remove(reg)
86
+ this.#all.add(reg)
87
+
88
+ for (const kind of this.#kindsOf(reg)) {
89
+ const list = this.#byKind.get(kind)
90
+ if (list === undefined) continue
91
+ // 插入排序:同优先级时后注册的排在后面,符合"先装的插件先跑"的直觉
92
+ let at = list.length
93
+ while (at > 0 && byPriority(list[at - 1]!, reg) > 0) at--
94
+ list.splice(at, 0, reg)
95
+ }
96
+
97
+ return () => this.#remove(reg)
98
+ }
99
+
100
+ /**
101
+ * 执行整条管线
102
+ *
103
+ * `core` 是管线最内层 —— 对消息事件而言即命令路由。中间件未调用 `next()`
104
+ * 或调用了 `e.stop()` 均将导致 `core` 不执行,这正是"拦截消息"的实现方式。
105
+ * @param e 事件
106
+ * @param core 最内层处理
107
+ * @throws 任一中间件或 core 抛错时原样冒泡(由 dispatch 统一处理)
108
+ */
109
+ async run(e: AnyEvent, core: () => Promise<void>): Promise<void> {
110
+ const chain = this.#byKind.get(e.kind)
111
+ // 没有中间件时直接执行核心逻辑,一个闭包都不分配
112
+ if (chain === undefined || chain.length === 0) {
113
+ if (!e.stopped) await core()
114
+ return
115
+ }
116
+
117
+ // 取快照:中间件里调 ctx.middleware() 注册新中间件不该影响本次执行,
118
+ // 否则同一条消息的处理链会在跑的过程中变长,行为不可复现
119
+ const list = chain.slice()
120
+
121
+ /**
122
+ * 递归执行第 i 层
123
+ * @param i 层号
124
+ */
125
+ const dispatch = async (i: number): Promise<void> => {
126
+ if (e.stopped) return
127
+ if (i === list.length) {
128
+ await core()
129
+ return
130
+ }
131
+
132
+ const reg = list[i]!
133
+ let called = false
134
+ /** 传给中间件的 next */
135
+ const next = async (): Promise<void> => {
136
+ if (called) {
137
+ throw new Error(`插件 ${reg.plugin} 的中间件重复调用了 next():下游会被执行多次`)
138
+ }
139
+ called = true
140
+ await dispatch(i + 1)
141
+ }
142
+
143
+ await reg.fn(e, next)
144
+
145
+ // 中间件既没调 next 也没显式 stop:这是合法的"拦截",但很容易是忘了写。
146
+ // 只在 trace 级别提示,避免正常拦截也刷日志。
147
+ if (!called && !e.stopped && this.#logger.isLevelEnabled("trace")) {
148
+ this.#logger.trace(`插件 ${reg.plugin} 的中间件未调用 next(),事件 ${e.id} 到此中断`)
149
+ }
150
+ }
151
+
152
+ await dispatch(0)
153
+ }
154
+
155
+ /**
156
+ * 摘除某插件的全部中间件
157
+ * @param plugin 插件名
158
+ * @returns 摘除的条数
159
+ */
160
+ removePlugin(plugin: string): number {
161
+ let n = 0
162
+ for (const reg of [...this.#all]) {
163
+ if (reg.plugin === plugin) {
164
+ this.#remove(reg)
165
+ n++
166
+ }
167
+ }
168
+ return n
169
+ }
170
+
171
+ /** 清空全部登记 */
172
+ clear(): void {
173
+ this.#all.clear()
174
+ for (const list of this.#byKind.values()) list.length = 0
175
+ }
176
+
177
+ /**
178
+ * 取某条登记适用的事件大类
179
+ * @param reg 登记内容
180
+ * @returns 事件大类数组
181
+ */
182
+ #kindsOf(reg: MiddlewareRegistration): readonly EventKind[] {
183
+ const want = reg.options.kind
184
+ if (want === undefined) return ALL_KINDS
185
+ return Array.isArray(want) ? want : [want]
186
+ }
187
+
188
+ /**
189
+ * 注销一条登记
190
+ * @param reg 登记内容
191
+ */
192
+ #remove(reg: MiddlewareRegistration): void {
193
+ if (!this.#all.delete(reg)) return
194
+ for (const list of this.#byKind.values()) {
195
+ const at = list.indexOf(reg)
196
+ if (at >= 0) list.splice(at, 1)
197
+ }
198
+ }
199
+ }