@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,757 @@
1
+ /**
2
+ * 模块职责:构造插件上下文(`ctx`)——"一切皆可为插件"的落地实现
3
+ * 依赖方向:依赖 plugin/{hooks,events,services}、util/*、类型包;**不依赖任何子系统实现**
4
+ * 生命周期:每个插件实例一个 ctx,随插件卸载整体失效
5
+ * 注意事项:三条硬规则 ——
6
+ *
7
+ * **零全局变量。** 全部能力须经 ctx 上的具名方法获取,故「某插件用了哪些能力」
8
+ * 可枚举,卸载时可确定性回收。
9
+ *
10
+ * **每个注册型方法都把 disposer 登记进 DisposalRegistry。** 插件作者无从遗漏清理 ——
11
+ * 否则改十次代码就有十份定时器同时在跑。
12
+ *
13
+ * **路径不做算术。** `ctx.resource()` 与模板根由内核依据插件根计算,`safeJoin` 拦
14
+ * `../` 越界;手工拼相对路径会在目录层级一变时给出空白页面。
15
+ */
16
+ import { join } from "node:path"
17
+ import type {
18
+ AdapterProvider,
19
+ AppView,
20
+ Awaitable,
21
+ CommandBuilder,
22
+ CommandHandler,
23
+ CommandOptions,
24
+ CommandPattern,
25
+ ConfigHandle,
26
+ ContactCache,
27
+ ContactCacheOptions,
28
+ CoreEventMap,
29
+ Disposer,
30
+ DurationLike,
31
+ HttpClient,
32
+ HttpMethod,
33
+ KvDriver,
34
+ KvNamespace,
35
+ Logger,
36
+ MessageScene,
37
+ Middleware,
38
+ MiddlewareOptions,
39
+ PluginContext,
40
+ RenderOptions,
41
+ RenderablePage,
42
+ RenderedImage,
43
+ RendererProvider,
44
+ RouteHandler,
45
+ RouteOptions,
46
+ SqlHandle,
47
+ TaskFn,
48
+ TaskOptions,
49
+ WebSocketHandler,
50
+ WebSocketOptions,
51
+ BotApi,
52
+ CooldownScope,
53
+ ImageSegment
54
+ } from "@yunzai-ng/types"
55
+ import { DisposalRegistry } from "../util/dispose.js"
56
+ import { parseDuration } from "../util/duration.js"
57
+ import { safeJoin } from "../util/fs.js"
58
+ import { LruCache } from "../util/lru.js"
59
+ import type { CoreEventBus } from "./events.js"
60
+ import type { CommandRegistration, KernelHooks } from "./hooks.js"
61
+ import type { ServiceRegistry } from "./services.js"
62
+
63
+ /** 插件内的模板目录名 */
64
+ const TEMPLATE_DIR = "templates"
65
+
66
+ /** 插件内的静态资源目录名 */
67
+ const RESOURCE_DIR = "resources"
68
+
69
+ /** 渲染输出格式对应的 MIME */
70
+ const IMAGE_MIME: Record<string, string> = {
71
+ jpeg: "image/jpeg",
72
+ png: "image/png",
73
+ webp: "image/webp"
74
+ }
75
+
76
+ /** 插件注册计数(供 WebUI 的插件列表展示) */
77
+ export interface PluginCounters {
78
+ /** 已注册命令数 */
79
+ commands: number
80
+ /** 已注册中间件数 */
81
+ middlewares: number
82
+ /** 已注册定时任务数 */
83
+ tasks: number
84
+ /** 已注册路由数 */
85
+ routes: number
86
+ /** 已提供服务数 */
87
+ services: number
88
+ }
89
+
90
+ /** 构造上下文所需的依赖 */
91
+ export interface PluginContextDeps {
92
+ /** 插件名 */
93
+ name: string
94
+ /** 插件版本 */
95
+ version: string
96
+ /** 插件安装目录(绝对路径) */
97
+ root: string
98
+ /** 插件专属数据目录(绝对路径,调用方已确保存在) */
99
+ dataDir: string
100
+ /** 插件专属日志器 */
101
+ logger: Logger
102
+ /** 插件专属 KV 命名空间 */
103
+ kv: KvNamespace
104
+ /** 插件配置句柄 */
105
+ config: ConfigHandle<unknown>
106
+ /** 应用只读视图 */
107
+ app: AppView
108
+ /** HTTP 客户端 */
109
+ http: HttpClient
110
+ /** 服务注册表 */
111
+ services: ServiceRegistry
112
+ /** 内核事件总线 */
113
+ events: CoreEventBus
114
+ /** 子系统接缝 */
115
+ hooks: KernelHooks
116
+ /** 本插件的回收登记簿 */
117
+ registry: DisposalRegistry
118
+ /** 卸载信号 */
119
+ signal: AbortSignal
120
+ }
121
+
122
+ /** 构造结果 */
123
+ export interface PluginContextHandle {
124
+ /** 交给插件的上下文 */
125
+ ctx: PluginContext<unknown>
126
+ /** 实时计数,宿主用它填 `PluginState` */
127
+ counters: PluginCounters
128
+ }
129
+
130
+ /**
131
+ * 链式命令构造器
132
+ *
133
+ * 每一环都在改同一份 `CommandRegistration`,路由器持有该对象引用,
134
+ * 因此 `.desc()` / `.master()` 这类不影响索引键的改动无需通知路由器;
135
+ * 只有 `.alias()` 改了索引键才调 `reindex`。
136
+ */
137
+ class ChainedCommand implements CommandBuilder {
138
+ /** 登记内容 */
139
+ readonly #reg: CommandRegistration
140
+ /** 命令路由 */
141
+ readonly #sink: KernelHooks["commands"]
142
+ /** 注销句柄(已登记进 registry) */
143
+ readonly #undo: Disposer
144
+ /** 日志器,用于提示可疑用法 */
145
+ readonly #logger: Logger
146
+
147
+ /**
148
+ * @param reg 登记内容
149
+ * @param sink 命令路由
150
+ * @param undo 注销句柄
151
+ * @param logger 日志器
152
+ */
153
+ constructor(reg: CommandRegistration, sink: KernelHooks["commands"], undo: Disposer, logger: Logger) {
154
+ this.#reg = reg
155
+ this.#sink = sink
156
+ this.#undo = undo
157
+ this.#logger = logger
158
+ }
159
+
160
+ /**
161
+ * 绑定处理函数
162
+ * @param fn 处理函数
163
+ * @returns 自身
164
+ */
165
+ action(fn: CommandHandler): CommandBuilder {
166
+ if (this.#reg.handler) {
167
+ // 覆盖 action 几乎总是复制粘贴的产物:静默覆盖会让人以为前一个还在生效
168
+ this.#logger.warn(`命令 ${describePatterns(this.#reg.patterns)} 的 action 被重复设置,后者生效`)
169
+ }
170
+ this.#reg.handler = fn
171
+ return this
172
+ }
173
+
174
+ /**
175
+ * 追加别名
176
+ * @param patterns 别名模式
177
+ * @returns 自身
178
+ */
179
+ alias(...patterns: CommandPattern[]): CommandBuilder {
180
+ if (patterns.length === 0) return this
181
+ this.#reg.patterns.push(...patterns)
182
+ this.#sink.reindex(this.#reg)
183
+ return this
184
+ }
185
+
186
+ /**
187
+ * 设置说明
188
+ * @param text 说明文案
189
+ * @returns 自身
190
+ */
191
+ desc(text: string): CommandBuilder {
192
+ this.#reg.options.desc = text
193
+ return this
194
+ }
195
+
196
+ /**
197
+ * 限定场景
198
+ * @param scenes 允许的场景
199
+ * @returns 自身
200
+ */
201
+ scene(...scenes: MessageScene[]): CommandBuilder {
202
+ this.#reg.options.scene = scenes
203
+ return this
204
+ }
205
+
206
+ /**
207
+ * 限定仅主人可用
208
+ * @returns 自身
209
+ */
210
+ master(): CommandBuilder {
211
+ this.#reg.options.master = true
212
+ return this
213
+ }
214
+
215
+ /**
216
+ * 限定仅群管可用
217
+ * @returns 自身
218
+ */
219
+ admin(): CommandBuilder {
220
+ this.#reg.options.admin = true
221
+ return this
222
+ }
223
+
224
+ /**
225
+ * 设置冷却
226
+ * @param spec 冷却时长
227
+ * @param scope 作用域
228
+ * @returns 自身
229
+ */
230
+ cooldown(spec: DurationLike, scope?: CooldownScope): CommandBuilder {
231
+ this.#reg.options.cooldown = spec
232
+ if (scope) this.#reg.options.cooldownScope = scope
233
+ return this
234
+ }
235
+
236
+ /**
237
+ * 设置优先级
238
+ * @param value 数值,小者优先
239
+ * @returns 自身
240
+ */
241
+ priority(value: number): CommandBuilder {
242
+ this.#reg.options.priority = value
243
+ return this
244
+ }
245
+
246
+ /** 注销该命令 */
247
+ dispose(): void {
248
+ this.#undo()
249
+ }
250
+ }
251
+
252
+ /**
253
+ * 把模式数组渲染成可读文本
254
+ * @param patterns 模式数组
255
+ * @returns 可读字符串
256
+ */
257
+ export function describePatterns(patterns: readonly CommandPattern[]): string {
258
+ return patterns.map(p => (typeof p === "string" ? p : p.source)).join(" | ")
259
+ }
260
+
261
+ /** 归一化后的一次渲染调用 */
262
+ export interface RenderCall {
263
+ /** 模板名:字符串通路为相对路径,TSX 通路为页面名(仅用于日志与临时文件名) */
264
+ template: string
265
+ /** 模板数据;TSX 通路为空对象 —— 数据已在组件求值时消化完毕 */
266
+ data: Record<string, unknown>
267
+ /** 已渲染好的 HTML;仅 TSX 通路有值 */
268
+ html?: string
269
+ /** 渲染选项 */
270
+ opts: RenderOptions
271
+ }
272
+
273
+ /**
274
+ * 把两条渲染通路的实参归一为一次调用描述
275
+ *
276
+ * `render()` 有两种形态:`render(page, opts)` 与 `render(template, data, opts)`。
277
+ * 以第一参是否为字符串判别 —— `RenderablePage` 是对象,二者不可能混淆。
278
+ * 判别只做这一处,两个调用点(`ctx.render` 与 `e.render`)共用同一份语义。
279
+ * @param first 页面或模板相对路径
280
+ * @param second 模板数据(字符串通路)或渲染选项(TSX 通路)
281
+ * @param third 渲染选项(字符串通路)
282
+ * @returns 归一化后的调用描述
283
+ */
284
+ export function splitRenderArgs(
285
+ first: RenderablePage | string,
286
+ second?: Record<string, unknown> | RenderOptions,
287
+ third?: RenderOptions
288
+ ): RenderCall {
289
+ if (typeof first === "string") {
290
+ return { template: first, data: (second as Record<string, unknown> | undefined) ?? {}, opts: third ?? {} }
291
+ }
292
+ return { template: first.name, data: {}, html: first.html, opts: (second as RenderOptions | undefined) ?? {} }
293
+ }
294
+
295
+ /**
296
+ * 插件上下文实现
297
+ *
298
+ * 每个成员都对应 `PluginContext` 的一项能力;除 `resource()` 这类纯计算外,
299
+ * 所有注册都经过 `#own()` 登记回收。
300
+ */
301
+ class Context implements PluginContext<unknown> {
302
+ /** 依赖 */
303
+ readonly #deps: PluginContextDeps
304
+ /** 计数 */
305
+ readonly #counters: PluginCounters
306
+ /** 已打开的 SQL 库,按库名缓存(含进行中的打开操作) */
307
+ readonly #sqlCache = new Map<string, Promise<SqlHandle>>()
308
+ /** URL 作用域,如 `/plugin/mhy-game` */
309
+ readonly #scope: string
310
+
311
+ /** 插件名 */
312
+ readonly name: string
313
+ /** 插件版本 */
314
+ readonly version: string
315
+ /** 插件安装目录 */
316
+ readonly root: string
317
+ /** 插件数据目录 */
318
+ readonly dataDir: string
319
+ /** 插件日志器 */
320
+ readonly logger: Logger
321
+ /** 插件 KV 命名空间 */
322
+ readonly kv: KvNamespace
323
+ /** 插件配置句柄 */
324
+ readonly config: ConfigHandle<unknown>
325
+ /** 应用只读视图 */
326
+ readonly app: AppView
327
+ /** HTTP 客户端 */
328
+ readonly http: HttpClient
329
+ /** 卸载信号 */
330
+ readonly signal: AbortSignal
331
+
332
+ /**
333
+ * @param deps 依赖
334
+ * @param counters 计数对象(与宿主共享同一引用)
335
+ */
336
+ constructor(deps: PluginContextDeps, counters: PluginCounters) {
337
+ this.#deps = deps
338
+ this.#counters = counters
339
+ this.#scope = `/plugin/${deps.name}`
340
+
341
+ this.name = deps.name
342
+ this.version = deps.version
343
+ this.root = deps.root
344
+ this.dataDir = deps.dataDir
345
+ this.logger = deps.logger
346
+ this.kv = deps.kv
347
+ this.config = deps.config
348
+ this.app = deps.app
349
+ this.http = deps.http
350
+ this.signal = deps.signal
351
+ }
352
+
353
+ /**
354
+ * 声明一条命令
355
+ * @param pattern 触发模式
356
+ * @param opts 命令选项
357
+ * @returns 链式构造器
358
+ */
359
+ command(pattern: CommandPattern, opts: CommandOptions = {}): CommandBuilder {
360
+ const patterns: CommandPattern[] = [pattern]
361
+ if (opts.alias) patterns.push(...(Array.isArray(opts.alias) ? opts.alias : [opts.alias]))
362
+
363
+ // options 拷一份:插件可能复用同一个 options 字面量声明多条命令,
364
+ // 而链式方法会就地改它
365
+ const reg: CommandRegistration = {
366
+ plugin: this.name,
367
+ patterns,
368
+ options: { ...opts },
369
+ handler: undefined
370
+ }
371
+
372
+ const undo = this.#deps.hooks.commands.register(reg)
373
+ this.#counters.commands++
374
+ const disposer = this.#own(() => {
375
+ this.#counters.commands--
376
+ undo()
377
+ }, `command:${describePatterns(patterns)}`)
378
+
379
+ return new ChainedCommand(reg, this.#deps.hooks.commands, disposer, this.logger)
380
+ }
381
+
382
+ /**
383
+ * 注册中间件
384
+ * @param fn 中间件函数
385
+ * @param opts 注册选项
386
+ * @returns 注销句柄
387
+ */
388
+ middleware(fn: Middleware, opts: MiddlewareOptions = {}): Disposer {
389
+ const undo = this.#deps.hooks.middlewares.register({ plugin: this.name, fn, options: opts })
390
+ this.#counters.middlewares++
391
+ return this.#own(() => {
392
+ this.#counters.middlewares--
393
+ undo()
394
+ }, "middleware")
395
+ }
396
+
397
+ /**
398
+ * 监听内核事件
399
+ * @param event 事件名
400
+ * @param handler 处理函数
401
+ * @returns 注销句柄
402
+ */
403
+ on<K extends keyof CoreEventMap>(event: K, handler: (...args: CoreEventMap[K]) => Awaitable<void>): Disposer {
404
+ return this.#own(this.#deps.events.on(event, handler, this.name), `on:${String(event)}`)
405
+ }
406
+
407
+ /**
408
+ * 注册 cron 定时任务
409
+ * @param expression cron 表达式
410
+ * @param fn 任务体
411
+ * @param opts 任务选项
412
+ * @returns 注销句柄
413
+ */
414
+ cron(expression: string, fn: TaskFn, opts: TaskOptions = {}): Disposer {
415
+ return this.#task("cron", expression, fn, opts)
416
+ }
417
+
418
+ /**
419
+ * 注册固定间隔任务
420
+ * @param interval 间隔
421
+ * @param fn 任务体
422
+ * @param opts 任务选项
423
+ * @returns 注销句柄
424
+ * @throws 间隔小于 1 秒时(几乎总是写错了单位)
425
+ */
426
+ every(interval: DurationLike, fn: TaskFn, opts: TaskOptions = {}): Disposer {
427
+ const ms = parseDuration(interval, 0)
428
+ if (ms < 1000) {
429
+ throw new Error(`插件 ${this.name} 的间隔任务周期 ${ms}ms 过短:最小 1s,请确认单位是否写错`)
430
+ }
431
+ return this.#task("interval", ms, fn, opts)
432
+ }
433
+
434
+ /**
435
+ * 对外提供服务
436
+ * @param key 服务键
437
+ * @param value 服务实例
438
+ * @returns 注销句柄
439
+ * @throws 键名非法或已被占用时
440
+ */
441
+ provide<T>(key: string, value: T): Disposer {
442
+ const undo = this.#deps.services.provide(key, value, this.name)
443
+ this.#counters.services++
444
+ return this.#own(() => {
445
+ this.#counters.services--
446
+ undo()
447
+ }, `provide:${key}`)
448
+ }
449
+
450
+ /**
451
+ * 取用其他插件提供的服务
452
+ * @param key 服务键
453
+ * @returns 服务实例;未提供时 undefined
454
+ */
455
+ inject<T>(key: string): T | undefined {
456
+ return this.#deps.services.get<T>(key)
457
+ }
458
+
459
+ /**
460
+ * 取用服务,缺失即抛错
461
+ * @param key 服务键
462
+ * @returns 服务实例
463
+ * @throws 服务未注册时
464
+ */
465
+ require<T>(key: string): T {
466
+ return this.#deps.services.require<T>(key, this.name)
467
+ }
468
+
469
+ /**
470
+ * 等待某服务就绪
471
+ *
472
+ * 插件被卸载时立刻以 undefined 结束等待:否则一个等不到的服务会把
473
+ * 已卸载插件的闭包一直挂在内存里。
474
+ * @param key 服务键
475
+ * @param timeout 等待超时,缺省 30s
476
+ * @returns 服务实例;超时或插件已卸载时 undefined
477
+ */
478
+ async waitFor<T>(key: string, timeout?: DurationLike): Promise<T | undefined> {
479
+ if (this.signal.aborted) return undefined
480
+ const ready = this.#deps.services.get<T>(key)
481
+ if (ready !== undefined) return ready
482
+
483
+ return new Promise<T | undefined>(resolve => {
484
+ let settled = false
485
+ /** 只兑现一次 */
486
+ const finish = (value: T | undefined): void => {
487
+ if (settled) return
488
+ settled = true
489
+ this.signal.removeEventListener("abort", onAbort)
490
+ resolve(value)
491
+ }
492
+ /** 卸载时放弃等待 */
493
+ function onAbort(): void {
494
+ finish(undefined)
495
+ }
496
+ this.signal.addEventListener("abort", onAbort, { once: true })
497
+ void this.#deps.services.waitFor<T>(key, timeout).then(finish, () => finish(undefined))
498
+ })
499
+ }
500
+
501
+ /**
502
+ * 注册 HTTP 路由
503
+ * @param method HTTP 方法
504
+ * @param path 路径
505
+ * @param handler 处理函数
506
+ * @param opts 选项
507
+ * @returns 注销句柄
508
+ * @throws 服务器未启用时
509
+ */
510
+ route(method: HttpMethod, path: string, handler: RouteHandler, opts?: RouteOptions): Disposer {
511
+ const undo = this.#deps.hooks.server.route(this.#scope, method, path, handler, opts)
512
+ this.#counters.routes++
513
+ return this.#own(() => {
514
+ this.#counters.routes--
515
+ undo()
516
+ }, `route:${method} ${path}`)
517
+ }
518
+
519
+ /**
520
+ * 注册 WebSocket 路径
521
+ * @param path 路径
522
+ * @param handler 连接处理函数
523
+ * @param opts 选项
524
+ * @returns 注销句柄
525
+ * @throws 服务器未启用时
526
+ */
527
+ websocket(path: string, handler: WebSocketHandler, opts?: WebSocketOptions): Disposer {
528
+ const undo = this.#deps.hooks.server.websocket(this.#scope, path, handler, opts)
529
+ this.#counters.routes++
530
+ return this.#own(() => {
531
+ this.#counters.routes--
532
+ undo()
533
+ }, `ws:${path}`)
534
+ }
535
+
536
+ /**
537
+ * 挂载静态目录
538
+ * @param urlPath URL 前缀
539
+ * @param dir 本地目录绝对路径
540
+ * @returns 注销句柄
541
+ * @throws 服务器未启用时
542
+ */
543
+ static(urlPath: string, dir: string): Disposer {
544
+ return this.#own(this.#deps.hooks.server.static(this.#scope, urlPath, dir), `static:${urlPath}`)
545
+ }
546
+
547
+ /**
548
+ * 接管站点根路径,替换内置面板
549
+ *
550
+ * 不经过 `this.#scope`:接管面板的意义就在于占据 `/`,挂在
551
+ * `/plugin/<插件名>` 之下的页面无法成为默认入口。归属标识写成
552
+ * `plugin:<插件名>`,`claimant("/")` 因此能回答"面板由谁提供"。
553
+ * 根路径已被占用时抛错,不静默覆盖 —— 见 types 里的说明。
554
+ * @param dir 单页应用产物目录(绝对路径)
555
+ * @returns 注销句柄
556
+ * @throws 服务器未启用、或根路径已被占用时
557
+ */
558
+ panel(dir: string): Disposer {
559
+ const undo = this.#deps.hooks.server.panel(`plugin:${this.name}`, dir)
560
+ this.#deps.logger.info(`已接管面板根路径:${dir}`)
561
+ return this.#own(undo, "panel:/")
562
+ }
563
+
564
+ /**
565
+ * 注册适配器
566
+ * @param provider 适配器实现
567
+ * @returns 注销句柄
568
+ */
569
+ registerAdapter(provider: AdapterProvider): Disposer {
570
+ return this.#own(this.#deps.hooks.adapters.register(provider, this.name), `adapter:${provider.id}`)
571
+ }
572
+
573
+ /**
574
+ * 注册渲染器
575
+ * @param provider 渲染器实现
576
+ * @returns 注销句柄
577
+ */
578
+ registerRenderer(provider: RendererProvider): Disposer {
579
+ return this.#own(this.#deps.hooks.render.register(provider, this.name), `renderer:${provider.id}`)
580
+ }
581
+
582
+ /**
583
+ * 注册 KV 驱动
584
+ * @param driver 驱动实现
585
+ * @returns 注销句柄
586
+ */
587
+ registerKvDriver(driver: KvDriver): Disposer {
588
+ return this.#own(this.#deps.hooks.kvDrivers.register(driver, this.name), `kv-driver:${driver.id}`)
589
+ }
590
+
591
+ /**
592
+ * 渲染 TSX 页面为图片段
593
+ * @param page 由 `defineTemplate()` 产出的页面
594
+ * @param opts 渲染选项
595
+ * @returns 图片段;分页时为数组
596
+ * @throws 无可用渲染器、渲染失败或渲染器返回空结果时
597
+ */
598
+ async render(page: RenderablePage, opts?: RenderOptions): Promise<RenderedImage>
599
+
600
+ /**
601
+ * 渲染字符串模板为图片段
602
+ *
603
+ * 模板根与资源根由内核按插件目录算好(`<root>/templates`、`<root>/resources`),
604
+ * 插件只写 `"player/daily-note"` 这样的相对名。
605
+ * @param template 模板相对路径
606
+ * @param data 模板数据
607
+ * @param opts 渲染选项
608
+ * @returns 图片段;分页时为数组
609
+ * @throws 无可用渲染器、渲染失败或渲染器返回空结果时
610
+ */
611
+ async render(template: string, data?: Record<string, unknown>, opts?: RenderOptions): Promise<RenderedImage>
612
+
613
+ /**
614
+ * 渲染实现
615
+ * @param first 页面或模板相对路径
616
+ * @param second 模板数据(字符串通路)或渲染选项(TSX 通路)
617
+ * @param third 渲染选项(字符串通路)
618
+ * @returns 图片段;分页时为数组
619
+ * @throws 无可用渲染器、渲染失败或渲染器返回空结果时
620
+ */
621
+ async render(
622
+ first: RenderablePage | string,
623
+ second?: Record<string, unknown> | RenderOptions,
624
+ third?: RenderOptions
625
+ ): Promise<RenderedImage> {
626
+ const call = splitRenderArgs(first, second, third)
627
+
628
+ const result = await this.#deps.hooks.render.render({
629
+ ...call.opts,
630
+ ...(call.html !== undefined ? { html: call.html } : {}),
631
+ template: call.template,
632
+ data: call.data,
633
+ templateRoot: join(this.root, TEMPLATE_DIR),
634
+ resourceRoot: join(this.root, RESOURCE_DIR),
635
+ origin: this.name
636
+ })
637
+
638
+ if (result.images.length === 0) {
639
+ throw new Error(`渲染器 ${result.renderer} 处理模板 ${call.template} 后没有产出图片`)
640
+ }
641
+
642
+ const type = call.opts.type ?? "jpeg"
643
+ const mime = IMAGE_MIME[type] ?? "image/jpeg"
644
+ const segments: ImageSegment[] = result.images.map((bytes, index) => ({
645
+ type: "image",
646
+ // buffer 直传:渲染器刚出的图就在内存里,转 base64 只是白占一倍内存
647
+ file: { kind: "buffer", data: bytes, name: `${this.name}-${index}.${type}`, mime }
648
+ }))
649
+
650
+ return segments.length === 1 ? segments[0]! : segments
651
+ }
652
+
653
+ /**
654
+ * 打开本插件专属的 SQLite 库
655
+ *
656
+ * 同名库只打开一次:插件在多处 `await ctx.sql()` 拿到的是同一个连接,
657
+ * 否则 WAL 下多连接互相等锁,症状是"偶发变慢",极难定位。
658
+ * @param name 库名,缺省 `"main"`
659
+ * @returns SQL 句柄
660
+ * @throws better-sqlite3 不可用时
661
+ */
662
+ async sql(name = "main"): Promise<SqlHandle> {
663
+ const cached = this.#sqlCache.get(name)
664
+ if (cached) return cached
665
+
666
+ const opening = this.#deps.hooks.sql.open(this.name, name)
667
+ this.#sqlCache.set(name, opening)
668
+ // 打开失败不留下坏缓存,否则重试永远拿到同一个已拒绝的 promise
669
+ opening.catch(() => this.#sqlCache.delete(name))
670
+
671
+ if (this.#sqlCache.size === 1) {
672
+ this.#own(() => void this.#deps.hooks.sql.close(this.name), "sql:close")
673
+ }
674
+ return opening
675
+ }
676
+
677
+ /**
678
+ * 创建 LRU + TTL 缓存
679
+ * @param opts 缓存参数
680
+ * @returns 缓存实例,随插件卸载自动清空
681
+ */
682
+ cache<V>(opts: ContactCacheOptions): ContactCache<V> {
683
+ const cache = new LruCache<V>(opts)
684
+ this.#own(() => cache.clear(), "cache")
685
+ return cache
686
+ }
687
+
688
+ /**
689
+ * 拼出插件内资源的绝对路径
690
+ *
691
+ * 走 `safeJoin`:插件传入 `"../../../etc/passwd"` 会被拒绝。参数很可能来自
692
+ * 用户消息(如"看某个模板"),不挡住就是任意文件读。
693
+ * @param parts 相对插件根的路径片段
694
+ * @returns 绝对路径
695
+ * @throws 结果越出插件根目录时
696
+ */
697
+ resource(...parts: string[]): string {
698
+ return safeJoin(this.root, join(...parts))
699
+ }
700
+
701
+ /**
702
+ * 注册清理回调
703
+ * @param fn 清理函数
704
+ */
705
+ onDispose(fn: Disposer): void {
706
+ this.#deps.registry.add(fn, "onDispose")
707
+ }
708
+
709
+ /**
710
+ * 选一个可用账号
711
+ * @param bot 账号记录 id 或平台 selfId
712
+ * @returns Bot;无可用账号时 undefined
713
+ */
714
+ pickBot(bot?: string): BotApi | undefined {
715
+ return this.#deps.hooks.bots.pick(bot)
716
+ }
717
+
718
+ /**
719
+ * 登记定时任务
720
+ * @param kind 任务类型
721
+ * @param schedule cron 表达式或间隔毫秒
722
+ * @param fn 任务体
723
+ * @param opts 任务选项
724
+ * @returns 注销句柄
725
+ */
726
+ #task(kind: "cron" | "interval", schedule: string | number, fn: TaskFn, opts: TaskOptions): Disposer {
727
+ const undo = this.#deps.hooks.tasks.register({ plugin: this.name, kind, schedule, fn, options: opts })
728
+ this.#counters.tasks++
729
+ return this.#own(() => {
730
+ this.#counters.tasks--
731
+ undo()
732
+ }, `task:${opts.name ?? schedule}`)
733
+ }
734
+
735
+ /**
736
+ * 把一个 disposer 登记进本插件的回收簿
737
+ *
738
+ * 插件卸载后仍然发生的注册(异步 setup 慢一步)会被立即回收 ——
739
+ * 这是 `DisposalRegistry.add` 的既有行为,此处依赖它。
740
+ * @param fn 回收函数
741
+ * @param label 标签
742
+ * @returns 提前回收的句柄
743
+ */
744
+ #own(fn: Disposer, label: string): Disposer {
745
+ return this.#deps.registry.add(fn, label)
746
+ }
747
+ }
748
+
749
+ /**
750
+ * 构造插件上下文
751
+ * @param deps 依赖
752
+ * @returns 上下文与计数对象
753
+ */
754
+ export function createPluginContext(deps: PluginContextDeps): PluginContextHandle {
755
+ const counters: PluginCounters = { commands: 0, middlewares: 0, tasks: 0, routes: 0, services: 0 }
756
+ return { ctx: new Context(deps, counters), counters }
757
+ }