@taicode/common-server 4.0.0-beta.1 → 4.0.0-beta.10

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 (292) hide show
  1. package/output/app/index.d.ts +18 -15
  2. package/output/app/index.d.ts.map +1 -1
  3. package/output/app/index.js +330 -18
  4. package/output/auth/index.d.ts +73 -0
  5. package/output/auth/index.d.ts.map +1 -0
  6. package/output/auth/index.js +196 -0
  7. package/output/core/assemble.d.ts +64 -0
  8. package/output/core/assemble.d.ts.map +1 -0
  9. package/output/core/assemble.js +88 -0
  10. package/output/core/boot/context.d.ts +109 -0
  11. package/output/core/boot/context.d.ts.map +1 -0
  12. package/output/core/boot/context.js +145 -0
  13. package/output/core/boot/lifecycle.d.ts +206 -0
  14. package/output/core/boot/lifecycle.d.ts.map +1 -0
  15. package/output/core/boot/lifecycle.js +255 -0
  16. package/output/core/boot/shutdown.d.ts +34 -0
  17. package/output/core/boot/shutdown.d.ts.map +1 -0
  18. package/output/core/boot/shutdown.js +50 -0
  19. package/output/core/boot/signal.d.ts +13 -0
  20. package/output/core/boot/signal.d.ts.map +1 -0
  21. package/output/{app → core/boot}/signal.js +3 -2
  22. package/output/core/boot/steps.d.ts +138 -0
  23. package/output/core/boot/steps.d.ts.map +1 -0
  24. package/output/core/boot/steps.js +259 -0
  25. package/output/core/entries.d.ts +151 -0
  26. package/output/core/entries.d.ts.map +1 -0
  27. package/output/core/entries.js +242 -0
  28. package/output/core/entries.test.d.ts +2 -0
  29. package/output/core/entries.test.d.ts.map +1 -0
  30. package/output/core/entries.test.js +458 -0
  31. package/output/core/env.d.ts +83 -0
  32. package/output/core/env.d.ts.map +1 -0
  33. package/output/core/env.js +124 -0
  34. package/output/{app → core}/env.test.d.ts.map +1 -1
  35. package/output/core/env.test.js +105 -0
  36. package/output/core/modules/auth-provider/index.d.ts +10 -0
  37. package/output/core/modules/auth-provider/index.d.ts.map +1 -0
  38. package/output/core/modules/auth-provider/index.js +9 -0
  39. package/output/core/modules/auth-provider/types.d.ts +71 -0
  40. package/output/core/modules/auth-provider/types.d.ts.map +1 -0
  41. package/output/core/modules/context/index.d.ts +18 -0
  42. package/output/core/modules/context/index.d.ts.map +1 -0
  43. package/output/core/modules/context/index.js +23 -0
  44. package/output/core/modules/context/index.test.d.ts.map +1 -0
  45. package/output/core/modules/cookie/index.d.ts +21 -0
  46. package/output/core/modules/cookie/index.d.ts.map +1 -0
  47. package/output/{app/core/cookie.js → core/modules/cookie/index.js} +1 -6
  48. package/output/core/modules/identity/index.d.ts +4 -0
  49. package/output/core/modules/identity/index.d.ts.map +1 -0
  50. package/output/{app/core/identity.js → core/modules/identity/index.js} +1 -1
  51. package/output/core/modules/logging/index.d.ts +5 -0
  52. package/output/core/modules/logging/index.d.ts.map +1 -0
  53. package/output/{app/core/logging.js → core/modules/logging/index.js} +2 -2
  54. package/output/core/modules/openapi/index.d.ts +7 -0
  55. package/output/core/modules/openapi/index.d.ts.map +1 -0
  56. package/output/{app/core/openapi.js → core/modules/openapi/index.js} +11 -12
  57. package/output/core/modules/permission-operations/index.d.ts +26 -0
  58. package/output/core/modules/permission-operations/index.d.ts.map +1 -0
  59. package/output/core/modules/permission-operations/index.js +36 -0
  60. package/output/core/modules/permission-operations/index.test.d.ts +2 -0
  61. package/output/core/modules/permission-operations/index.test.d.ts.map +1 -0
  62. package/output/core/modules/permission-operations/index.test.js +45 -0
  63. package/output/{app/core/request-id.d.ts → core/modules/request-id/index.d.ts} +1 -4
  64. package/output/core/modules/request-id/index.d.ts.map +1 -0
  65. package/output/{app/core/request-id.js → core/modules/request-id/index.js} +2 -2
  66. package/output/core/modules/response/index.d.ts +50 -0
  67. package/output/core/modules/response/index.d.ts.map +1 -0
  68. package/output/{app/core/response.js → core/modules/response/index.js} +31 -18
  69. package/output/{schema/index.d.ts → core/modules/schema/builders.d.ts} +8 -1
  70. package/output/core/modules/schema/builders.d.ts.map +1 -0
  71. package/output/{schema/index.js → core/modules/schema/builders.js} +7 -4
  72. package/output/core/modules/schema/index.d.ts +34 -0
  73. package/output/core/modules/schema/index.d.ts.map +1 -0
  74. package/output/core/modules/schema/index.js +62 -0
  75. package/output/core/modules/schema/index.test.d.ts +2 -0
  76. package/output/core/modules/schema/index.test.d.ts.map +1 -0
  77. package/output/core/modules/schema/index.test.js +55 -0
  78. package/output/core/modules/system-routes/index.d.ts +15 -0
  79. package/output/core/modules/system-routes/index.d.ts.map +1 -0
  80. package/output/core/modules/system-routes/index.js +205 -0
  81. package/output/core/resource.d.ts +56 -0
  82. package/output/core/resource.d.ts.map +1 -0
  83. package/output/core/resource.js +56 -0
  84. package/output/core/test-support/database.d.ts +89 -0
  85. package/output/core/test-support/database.d.ts.map +1 -0
  86. package/output/core/test-support/database.js +131 -0
  87. package/output/core/test-support/database.test.d.ts +2 -0
  88. package/output/core/test-support/database.test.d.ts.map +1 -0
  89. package/output/core/test-support/database.test.js +111 -0
  90. package/output/core/test-support/environment.d.ts +38 -0
  91. package/output/core/test-support/environment.d.ts.map +1 -0
  92. package/output/core/test-support/environment.js +45 -0
  93. package/output/core/test-support/service-context.d.ts +64 -0
  94. package/output/core/test-support/service-context.d.ts.map +1 -0
  95. package/output/core/test-support/service-context.js +63 -0
  96. package/output/core/test-support/service-context.test.d.ts +2 -0
  97. package/output/core/test-support/service-context.test.d.ts.map +1 -0
  98. package/output/core/test-support/service-context.test.js +55 -0
  99. package/output/core/test-support/test-server.d.ts +154 -0
  100. package/output/core/test-support/test-server.d.ts.map +1 -0
  101. package/output/core/test-support/test-server.js +114 -0
  102. package/output/core/test-support/test-server.test.d.ts +2 -0
  103. package/output/core/test-support/test-server.test.d.ts.map +1 -0
  104. package/output/core/test-support/test-server.test.js +296 -0
  105. package/output/core/test-support/testdata.d.ts +255 -0
  106. package/output/core/test-support/testdata.d.ts.map +1 -0
  107. package/output/core/test-support/testdata.js +271 -0
  108. package/output/core/test-support/testdata.test.d.ts +2 -0
  109. package/output/core/test-support/testdata.test.d.ts.map +1 -0
  110. package/output/core/test-support/testdata.test.js +334 -0
  111. package/output/{app → core}/type-augment.d.ts +2 -2
  112. package/output/core/type-augment.d.ts.map +1 -0
  113. package/output/core/types.d.ts +582 -0
  114. package/output/core/types.d.ts.map +1 -0
  115. package/output/cors/index.d.ts +3 -0
  116. package/output/cors/index.d.ts.map +1 -0
  117. package/output/cors/index.js +28 -0
  118. package/output/index.d.ts +10 -4
  119. package/output/index.d.ts.map +1 -1
  120. package/output/index.js +25 -5
  121. package/output/rate-limit/index.d.ts +23 -0
  122. package/output/rate-limit/index.d.ts.map +1 -0
  123. package/output/rate-limit/index.js +43 -0
  124. package/output/{rate-limiter/rate-limiter.d.ts → rate-limit/limiter.d.ts} +1 -1
  125. package/output/rate-limit/limiter.d.ts.map +1 -0
  126. package/output/{rate-limiter → rate-limit}/memory-store.d.ts +6 -0
  127. package/output/rate-limit/memory-store.d.ts.map +1 -0
  128. package/output/{rate-limiter → rate-limit}/memory-store.js +8 -0
  129. package/output/{rate-limiter → rate-limit}/redis-store.d.ts +18 -0
  130. package/output/rate-limit/redis-store.d.ts.map +1 -0
  131. package/output/{rate-limiter → rate-limit}/redis-store.js +35 -0
  132. package/output/rate-limit/redis-store.test.d.ts.map +1 -0
  133. package/output/{rate-limiter → rate-limit}/redis-store.test.js +2 -2
  134. package/output/rate-limit/types.d.ts +45 -0
  135. package/output/rate-limit/types.d.ts.map +1 -0
  136. package/output/rate-limit/types.js +1 -0
  137. package/output/{redis-cache/redis-cache.d.ts → redis/cache.d.ts} +1 -1
  138. package/output/redis/cache.d.ts.map +1 -0
  139. package/output/redis/cache.test.d.ts +2 -0
  140. package/output/redis/cache.test.d.ts.map +1 -0
  141. package/output/{redis-cache/redis-cache.test.js → redis/cache.test.js} +1 -1
  142. package/output/{app/redis.d.ts → redis/index.d.ts} +12 -7
  143. package/output/redis/index.d.ts.map +1 -0
  144. package/output/{app/redis.js → redis/index.js} +15 -5
  145. package/output/redis/index.test.d.ts +2 -0
  146. package/output/{context → redis}/index.test.d.ts.map +1 -1
  147. package/output/redis/index.test.js +55 -0
  148. package/output/redis-queue/index.js +1 -1
  149. package/output/redis-queue/test-helpers.d.ts +1 -1
  150. package/output/redis-queue/test-helpers.js +1 -1
  151. package/output/redis-queue/types.d.ts +3 -12
  152. package/output/redis-queue/types.d.ts.map +1 -1
  153. package/package.json +5 -5
  154. package/output/app/auth.test.d.ts +0 -2
  155. package/output/app/auth.test.d.ts.map +0 -1
  156. package/output/app/auth.test.js +0 -148
  157. package/output/app/boot.d.ts +0 -48
  158. package/output/app/boot.d.ts.map +0 -1
  159. package/output/app/boot.js +0 -123
  160. package/output/app/config.test.d.ts +0 -2
  161. package/output/app/config.test.d.ts.map +0 -1
  162. package/output/app/config.test.js +0 -132
  163. package/output/app/context.d.ts +0 -68
  164. package/output/app/context.d.ts.map +0 -1
  165. package/output/app/context.js +0 -73
  166. package/output/app/core/cookie.d.ts +0 -25
  167. package/output/app/core/cookie.d.ts.map +0 -1
  168. package/output/app/core/identity.d.ts +0 -12
  169. package/output/app/core/identity.d.ts.map +0 -1
  170. package/output/app/core/index.d.ts +0 -44
  171. package/output/app/core/index.d.ts.map +0 -1
  172. package/output/app/core/index.js +0 -57
  173. package/output/app/core/logging.d.ts +0 -13
  174. package/output/app/core/logging.d.ts.map +0 -1
  175. package/output/app/core/openapi.d.ts +0 -17
  176. package/output/app/core/openapi.d.ts.map +0 -1
  177. package/output/app/core/request-id.d.ts.map +0 -1
  178. package/output/app/core/response.d.ts +0 -41
  179. package/output/app/core/response.d.ts.map +0 -1
  180. package/output/app/define-server.negative-cases.d.ts +0 -32
  181. package/output/app/define-server.negative-cases.d.ts.map +0 -1
  182. package/output/app/define-server.negative-cases.js +0 -209
  183. package/output/app/define-server.test.d.ts +0 -2
  184. package/output/app/define-server.test.d.ts.map +0 -1
  185. package/output/app/define-server.test.js +0 -97
  186. package/output/app/entries.d.ts +0 -29
  187. package/output/app/entries.d.ts.map +0 -1
  188. package/output/app/entries.js +0 -64
  189. package/output/app/env.d.ts +0 -59
  190. package/output/app/env.d.ts.map +0 -1
  191. package/output/app/env.js +0 -66
  192. package/output/app/env.test.js +0 -124
  193. package/output/app/error-contract.test.d.ts +0 -2
  194. package/output/app/error-contract.test.d.ts.map +0 -1
  195. package/output/app/error-contract.test.js +0 -125
  196. package/output/app/errors.d.ts +0 -20
  197. package/output/app/errors.d.ts.map +0 -1
  198. package/output/app/errors.js +0 -51
  199. package/output/app/generate-openapi.test.d.ts +0 -2
  200. package/output/app/generate-openapi.test.d.ts.map +0 -1
  201. package/output/app/generate-openapi.test.js +0 -123
  202. package/output/app/lifecycle-logging.test.d.ts +0 -2
  203. package/output/app/lifecycle-logging.test.d.ts.map +0 -1
  204. package/output/app/lifecycle-logging.test.js +0 -155
  205. package/output/app/lifecycle.d.ts +0 -70
  206. package/output/app/lifecycle.d.ts.map +0 -1
  207. package/output/app/lifecycle.js +0 -99
  208. package/output/app/lifecycle.test.d.ts +0 -2
  209. package/output/app/lifecycle.test.d.ts.map +0 -1
  210. package/output/app/lifecycle.test.js +0 -152
  211. package/output/app/middleware/auth.d.ts +0 -71
  212. package/output/app/middleware/auth.d.ts.map +0 -1
  213. package/output/app/middleware/auth.js +0 -190
  214. package/output/app/middleware/context.d.ts +0 -13
  215. package/output/app/middleware/context.d.ts.map +0 -1
  216. package/output/app/middleware/context.js +0 -15
  217. package/output/app/middleware/cors.d.ts +0 -14
  218. package/output/app/middleware/cors.d.ts.map +0 -1
  219. package/output/app/middleware/cors.js +0 -30
  220. package/output/app/middleware/index.d.ts +0 -17
  221. package/output/app/middleware/index.d.ts.map +0 -1
  222. package/output/app/middleware/index.js +0 -16
  223. package/output/app/middleware/rate-limit.d.ts +0 -24
  224. package/output/app/middleware/rate-limit.d.ts.map +0 -1
  225. package/output/app/middleware/rate-limit.js +0 -46
  226. package/output/app/middleware-order.test.d.ts +0 -2
  227. package/output/app/middleware-order.test.d.ts.map +0 -1
  228. package/output/app/middleware-order.test.js +0 -148
  229. package/output/app/permission-operations.d.ts +0 -48
  230. package/output/app/permission-operations.d.ts.map +0 -1
  231. package/output/app/permission-operations.js +0 -77
  232. package/output/app/permission-operations.test.d.ts +0 -2
  233. package/output/app/permission-operations.test.d.ts.map +0 -1
  234. package/output/app/permission-operations.test.js +0 -72
  235. package/output/app/permission-registration.test.d.ts +0 -2
  236. package/output/app/permission-registration.test.d.ts.map +0 -1
  237. package/output/app/permission-registration.test.js +0 -138
  238. package/output/app/redis.d.ts.map +0 -1
  239. package/output/app/registered-operations.test.d.ts +0 -2
  240. package/output/app/registered-operations.test.d.ts.map +0 -1
  241. package/output/app/registered-operations.test.js +0 -57
  242. package/output/app/request-id.test.d.ts +0 -2
  243. package/output/app/request-id.test.d.ts.map +0 -1
  244. package/output/app/request-id.test.js +0 -130
  245. package/output/app/resources.test.d.ts +0 -2
  246. package/output/app/resources.test.d.ts.map +0 -1
  247. package/output/app/resources.test.js +0 -197
  248. package/output/app/shutdown.d.ts +0 -22
  249. package/output/app/shutdown.d.ts.map +0 -1
  250. package/output/app/shutdown.js +0 -30
  251. package/output/app/shutdown.test.d.ts +0 -2
  252. package/output/app/shutdown.test.d.ts.map +0 -1
  253. package/output/app/shutdown.test.js +0 -103
  254. package/output/app/signal.d.ts +0 -12
  255. package/output/app/signal.d.ts.map +0 -1
  256. package/output/app/smoke.test.d.ts +0 -2
  257. package/output/app/smoke.test.d.ts.map +0 -1
  258. package/output/app/smoke.test.js +0 -210
  259. package/output/app/test-support.d.ts +0 -21
  260. package/output/app/test-support.d.ts.map +0 -1
  261. package/output/app/test-support.js +0 -24
  262. package/output/app/type-augment.d.ts.map +0 -1
  263. package/output/app/types.d.ts +0 -211
  264. package/output/app/types.d.ts.map +0 -1
  265. package/output/context/index.d.ts +0 -6
  266. package/output/context/index.d.ts.map +0 -1
  267. package/output/context/index.js +0 -14
  268. package/output/rate-limiter/index.d.ts +0 -5
  269. package/output/rate-limiter/index.d.ts.map +0 -1
  270. package/output/rate-limiter/index.js +0 -4
  271. package/output/rate-limiter/memory-store.d.ts.map +0 -1
  272. package/output/rate-limiter/rate-limiter.d.ts.map +0 -1
  273. package/output/rate-limiter/redis-store.d.ts.map +0 -1
  274. package/output/rate-limiter/redis-store.test.d.ts.map +0 -1
  275. package/output/rate-limiter/types.d.ts +0 -27
  276. package/output/rate-limiter/types.d.ts.map +0 -1
  277. package/output/redis-cache/index.d.ts +0 -4
  278. package/output/redis-cache/index.d.ts.map +0 -1
  279. package/output/redis-cache/index.js +0 -1
  280. package/output/redis-cache/redis-cache.d.ts.map +0 -1
  281. package/output/redis-cache/redis-cache.test.d.ts +0 -2
  282. package/output/redis-cache/redis-cache.test.d.ts.map +0 -1
  283. package/output/schema/index.d.ts.map +0 -1
  284. /package/output/{app → core}/env.test.d.ts +0 -0
  285. /package/output/{rate-limiter → core/modules/auth-provider}/types.js +0 -0
  286. /package/output/{context → core/modules/context}/index.test.d.ts +0 -0
  287. /package/output/{context → core/modules/context}/index.test.js +0 -0
  288. /package/output/{app → core}/type-augment.js +0 -0
  289. /package/output/{app → core}/types.js +0 -0
  290. /package/output/{rate-limiter/rate-limiter.js → rate-limit/limiter.js} +0 -0
  291. /package/output/{rate-limiter → rate-limit}/redis-store.test.d.ts +0 -0
  292. /package/output/{redis-cache/redis-cache.js → redis/cache.js} +0 -0
@@ -1,197 +0,0 @@
1
- import { describe, expect, expectTypeOf, it } from 'vitest';
2
- import { defineServer, generateOpenApi, startServer } from './entries.js';
3
- import { createTestConfigEnv } from './test-support.js';
4
- /** 资源包实现(显式标注返回类型:否则资源实现的回调参数会吃隐式 `any`) */
5
- function createResources() {
6
- return {
7
- database: { query: (sql) => `rows:${sql}` },
8
- cache: { read: (key) => `${key}:cached` },
9
- telemetry: false,
10
- queue: undefined,
11
- };
12
- }
13
- // ── 类型自证:`false` 键在 `Live<TestResources>` 中被剔除 ─────────
14
- const assertLiveKeysRemovesFalse = () => {
15
- expectTypeOf().toEqualTypeOf();
16
- };
17
- void assertLiveKeysRemovesFalse;
18
- describe('resources 槽位(N1 §2 / N2 §2.3)', () => {
19
- it('① 入参只有 env + log,且资源嵌套在 ctx.resources 下(config / services 都能用)', async () => {
20
- let captured;
21
- const observed = {};
22
- const server = defineServer({
23
- app: { service: 'resources-basic', title: 'Resources Basic', version: '1.0.0' },
24
- resources: (ctx) => {
25
- captured = ctx;
26
- return createResources();
27
- },
28
- config: (ctx) => {
29
- // 类型自证:精确到资源包里的声明类型(不是 unknown / any)
30
- expectTypeOf(ctx.resources.database.query).toEqualTypeOf();
31
- observed.inConfig = ctx.resources.database.query('select 1');
32
- return { domain: 'example.com' };
33
- },
34
- services: (ctx) => {
35
- expectTypeOf(ctx.resources.cache.read).toEqualTypeOf();
36
- observed.inServices = ctx.resources.cache.read('domain');
37
- return {};
38
- },
39
- middlewares: () => [],
40
- routers: (ctx) => {
41
- ctx.app.get('/resource', () => ({ ok: true }));
42
- },
43
- });
44
- const app = await startServer(server, { listen: false });
45
- try {
46
- expect(Object.keys(captured ?? {}).sort()).toEqual(['env', 'log']);
47
- expect(captured?.env.ENV).toBeTypeOf('string');
48
- expect(typeof captured?.log.info).toBe('function');
49
- expect(observed.inConfig).toBe('rows:select 1');
50
- expect(observed.inServices).toBe('domain:cached');
51
- const response = await app.inject({ method: 'GET', url: '/resource' });
52
- expect(response.statusCode).toBe(200);
53
- }
54
- finally {
55
- await app.close();
56
- }
57
- });
58
- it('② `false` 关闭资源:装配正常,键在类型与运行时都不存在', async () => {
59
- let ctxInServices;
60
- const server = defineServer({
61
- app: { service: 'resources-disabled', title: 'Resources Disabled', version: '1.0.0' },
62
- resources: createResources,
63
- config: () => ({}),
64
- services: (ctx) => {
65
- // 编译期:`telemetry` 被 `Live<>` 剔除 → 访问必须编译失败
66
- // @ts-expect-error `telemetry: false` 已从资源视图中剔除
67
- void ctx.resources.telemetry;
68
- ctxInServices = ctx;
69
- return {};
70
- },
71
- middlewares: () => [],
72
- routers: (ctx) => {
73
- ctx.app.get('/alive', () => ({ ok: true }));
74
- },
75
- });
76
- const app = await startServer(server, { listen: false });
77
- try {
78
- // 运行时也不存在该键(与类型一致)
79
- const resources = ctxInServices.resources;
80
- expect('telemetry' in resources).toBe(false);
81
- const response = await app.inject({ method: 'GET', url: '/alive' });
82
- expect(response.statusCode).toBe(200);
83
- }
84
- finally {
85
- await app.close();
86
- }
87
- });
88
- it('③ 缺省可选资源:类型为 `T | undefined`,运行时为 undefined(键仍在)', async () => {
89
- const observed = {};
90
- const server = defineServer({
91
- app: { service: 'resources-optional', title: 'Resources Optional', version: '1.0.0' },
92
- resources: createResources,
93
- config: (ctx) => {
94
- expectTypeOf(ctx.resources.queue).toEqualTypeOf();
95
- observed.inConfig = ctx.resources.queue;
96
- return {};
97
- },
98
- services: (ctx) => {
99
- observed.inServices = ctx.resources.queue;
100
- observed.keys = Object.keys(ctx.resources);
101
- return {};
102
- },
103
- middlewares: () => [],
104
- routers: () => undefined,
105
- });
106
- const app = await startServer(server, { listen: false });
107
- try {
108
- expect(observed.inConfig).toBeUndefined();
109
- expect(observed.inServices).toBeUndefined();
110
- expect(observed.keys).toContain('queue');
111
- }
112
- finally {
113
- await app.close();
114
- }
115
- });
116
- it('④ resources 允许异步:返回 Promise<资源包> 时同样是精确类型', async () => {
117
- let domain;
118
- const server = defineServer({
119
- app: { service: 'resources-async', title: 'Resources Async', version: '1.0.0' },
120
- resources: async () => ({
121
- database: { query: (sql) => `async:${sql}` },
122
- }),
123
- config: (ctx) => {
124
- expectTypeOf(ctx.resources.database.query).toEqualTypeOf();
125
- return { domain: ctx.resources.database.query('domain') };
126
- },
127
- services: (ctx) => {
128
- domain = ctx.config.domain;
129
- return {};
130
- },
131
- middlewares: () => [],
132
- routers: (ctx) => {
133
- ctx.app.get('/ping', () => ({ ok: true }));
134
- },
135
- });
136
- const app = await startServer(server, { listen: false });
137
- try {
138
- expect(domain).toBe('async:domain');
139
- }
140
- finally {
141
- await app.close();
142
- }
143
- });
144
- it('⑤ 框架不管资源生命周期:资源上的 dispose 不被调用(释放要放 services)', async () => {
145
- const calls = [];
146
- const server = defineServer({
147
- app: { service: 'resources-lifecycle', title: 'Resources Lifecycle', version: '1.0.0' },
148
- resources: () => ({
149
- database: {
150
- dispose: () => {
151
- calls.push('dispose:resource');
152
- },
153
- },
154
- }),
155
- config: () => ({}),
156
- services: () => ({
157
- databaseClient: {
158
- dispose: () => {
159
- calls.push('dispose:service');
160
- },
161
- },
162
- }),
163
- middlewares: () => [],
164
- routers: () => undefined,
165
- });
166
- const app = await startServer(server, { listen: false });
167
- await app.close();
168
- expect(calls).toEqual(['dispose:service']);
169
- });
170
- it('⑥ 出 spec 路径同样跑 resources(只构造,不建连)', async () => {
171
- let calls = 0;
172
- const server = defineServer({
173
- app: { service: 'resources-openapi', title: 'Resources OpenApi', version: '1.0.0' },
174
- resources: () => {
175
- calls += 1;
176
- return {};
177
- },
178
- config: () => ({}),
179
- services: () => ({}),
180
- middlewares: () => [],
181
- routers: () => undefined,
182
- });
183
- await generateOpenApi(server);
184
- expect(calls).toBe(1);
185
- });
186
- });
187
- /** `ResourcesView` 自证:`Awaited<>` 归一(同步 / 异步两写法等价) */
188
- const assertResourcesViewNormalizes = () => {
189
- expectTypeOf().toEqualTypeOf();
190
- expectTypeOf().toEqualTypeOf();
191
- };
192
- void assertResourcesViewNormalizes;
193
- /** 测试用 env 自证:固定读取约定里的五项 */
194
- const assertTestConfigEnv = () => {
195
- expectTypeOf(createTestConfigEnv().ENV).toEqualTypeOf();
196
- };
197
- void assertTestConfigEnv;
@@ -1,22 +0,0 @@
1
- /**
2
- * 固定关闭步(N1 §5)——**顺序写死在数组里**。
3
- *
4
- * 新口径下框架不再持有 Redis / 数据库 / 外链客户端(这些都在 `resources` 里,由各仓自行构造,
5
- * 需要释放时放 `services` 单元);配置失效通道已删除,故没有 `unsubscribeConfig` 步。
6
- *
7
- * 固定两步:`stopProducers`(`ctx.disposer` 注册的清理)→ `disposeServices`(services 逆序)。
8
- *
9
- * 每步失败记日志、不阻断后续,最后聚合上报。
10
- */
11
- export declare const SHUTDOWN_ORDER: readonly ["stopProducers", "disposeServices"];
12
- export type ShutdownStepName = (typeof SHUTDOWN_ORDER)[number];
13
- export type ShutdownHandles = Record<ShutdownStepName, () => void | Promise<void>>;
14
- export interface ShutdownLogger {
15
- error: (obj: unknown, msg?: string) => void;
16
- }
17
- /**
18
- * 顺序执行关闭步骤;单步抛错记录后**继续**后续步骤,最后聚合上报
19
- * (避免「一步报错导致后面资源没关」)。
20
- */
21
- export declare function runShutdown(handles: ShutdownHandles, log: ShutdownLogger): Promise<void>;
22
- //# sourceMappingURL=shutdown.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../source/app/shutdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,+CAAgD,CAAA;AAE3E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAE9D,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,gBAAgB,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;AAElF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAC5C;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAe9F"}
@@ -1,30 +0,0 @@
1
- /**
2
- * 固定关闭步(N1 §5)——**顺序写死在数组里**。
3
- *
4
- * 新口径下框架不再持有 Redis / 数据库 / 外链客户端(这些都在 `resources` 里,由各仓自行构造,
5
- * 需要释放时放 `services` 单元);配置失效通道已删除,故没有 `unsubscribeConfig` 步。
6
- *
7
- * 固定两步:`stopProducers`(`ctx.disposer` 注册的清理)→ `disposeServices`(services 逆序)。
8
- *
9
- * 每步失败记日志、不阻断后续,最后聚合上报。
10
- */
11
- export const SHUTDOWN_ORDER = ['stopProducers', 'disposeServices'];
12
- /**
13
- * 顺序执行关闭步骤;单步抛错记录后**继续**后续步骤,最后聚合上报
14
- * (避免「一步报错导致后面资源没关」)。
15
- */
16
- export async function runShutdown(handles, log) {
17
- const errors = [];
18
- for (const step of SHUTDOWN_ORDER) {
19
- try {
20
- await handles[step]();
21
- }
22
- catch (error) {
23
- errors.push(error);
24
- log.error({ error, step }, '[shutdown] step failed');
25
- }
26
- }
27
- if (errors.length > 0) {
28
- log.error({ errors, count: errors.length }, '[shutdown] completed with errors');
29
- }
30
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=shutdown.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"shutdown.test.d.ts","sourceRoot":"","sources":["../../source/app/shutdown.test.ts"],"names":[],"mappings":""}
@@ -1,103 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { defineServer, startServer } from './entries.js';
3
- import { runShutdown, SHUTDOWN_ORDER } from './shutdown.js';
4
- function createShutdownLog() {
5
- const entries = [];
6
- return { entries, log: { error: (obj, msg) => void entries.push({ obj, msg }) } };
7
- }
8
- function createRecordingHandles(record, throwOn) {
9
- const step = (name) => () => {
10
- record.push(name);
11
- if (name === throwOn) {
12
- throw new Error(`boom:${name}`);
13
- }
14
- };
15
- return {
16
- stopProducers: step('stopProducers'),
17
- disposeServices: step('disposeServices'),
18
- };
19
- }
20
- describe('SHUTDOWN_ORDER', () => {
21
- it('固定两步且顺序不可变', () => {
22
- expect(SHUTDOWN_ORDER).toEqual(['stopProducers', 'disposeServices']);
23
- });
24
- });
25
- describe('runShutdown', () => {
26
- it('按固定序执行全部步骤', async () => {
27
- const record = [];
28
- const { log } = createShutdownLog();
29
- await runShutdown(createRecordingHandles(record), log);
30
- expect(record).toEqual([...SHUTDOWN_ORDER]);
31
- });
32
- it('单步抛错不阻断后续,并聚合上报', async () => {
33
- const record = [];
34
- const { entries, log } = createShutdownLog();
35
- await runShutdown(createRecordingHandles(record, 'stopProducers'), log);
36
- expect(record).toEqual([...SHUTDOWN_ORDER]);
37
- const aggregate = entries.find((entry) => entry.msg === '[shutdown] completed with errors');
38
- expect(aggregate).toBeDefined();
39
- const payload = aggregate?.obj;
40
- expect(payload.count).toBe(1);
41
- expect(payload.errors).toHaveLength(1);
42
- });
43
- });
44
- describe('关闭流程集成', () => {
45
- it('默认两步按序执行:disposer → services 逆序', async () => {
46
- const calls = [];
47
- const server = defineServer({
48
- app: { service: 'shutdown-integration', title: 'Shutdown Integration', version: '1.0.0' },
49
- resources: () => ({ database: { dispose: () => calls.push('disposeResource') } }),
50
- config: () => ({}),
51
- services: (ctx) => {
52
- ctx.disposer.addDisposer(() => {
53
- calls.push('stopProducers');
54
- });
55
- return {
56
- a: {
57
- dispose: () => {
58
- calls.push('disposeServiceA');
59
- },
60
- },
61
- b: {
62
- dispose: () => {
63
- calls.push('disposeServiceB');
64
- },
65
- },
66
- };
67
- },
68
- middlewares: () => [],
69
- routers: () => undefined,
70
- });
71
- const app = await startServer(server, { listen: false });
72
- await app.close();
73
- // 资源不在生命周期内(框架只管 services)
74
- expect(calls).toEqual(['stopProducers', 'disposeServiceB', 'disposeServiceA']);
75
- });
76
- it('StartServerOptions.shutdown 整体覆盖默认关闭流程', async () => {
77
- const calls = [];
78
- const server = defineServer({
79
- app: { service: 'shutdown-override', title: 'Shutdown Override', version: '1.0.0' },
80
- resources: () => ({}),
81
- config: () => ({}),
82
- services: () => ({
83
- a: {
84
- dispose: () => {
85
- calls.push('default-dispose');
86
- },
87
- },
88
- }),
89
- middlewares: () => [],
90
- routers: () => undefined,
91
- });
92
- const app = await startServer(server, {
93
- listen: false,
94
- shutdown: async (ctx) => {
95
- expect(ctx.app).toBeDefined();
96
- expect(ctx.services).toBeDefined();
97
- calls.push('custom');
98
- },
99
- });
100
- await app.close();
101
- expect(calls).toEqual(['custom']);
102
- });
103
- });
@@ -1,12 +0,0 @@
1
- import type { LoggerLike } from './types.js';
2
- import type { FastifyInstance } from 'fastify';
3
- /**
4
- * 内置信号处理(设计 §7.1 `activate` 尾部 / §7.2)。
5
- *
6
- * `SIGTERM` / `SIGINT` / `uncaughtException` / `unhandledRejection` ⇒ `app.close()`(触发 `onClose`
7
- * 的关闭 6 步)⇒ `process.exit(0/1)`。**仅 `listen: true` 时装信号处理**。
8
- *
9
- * @returns detach 函数(移除本次注册的监听器)
10
- */
11
- export declare function attachSignalHandlers(app: FastifyInstance, log: LoggerLike): () => void;
12
- //# sourceMappingURL=signal.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"signal.d.ts","sourceRoot":"","sources":["../../source/app/signal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAE9C;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,IAAI,CA2CtF"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=smoke.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"smoke.test.d.ts","sourceRoot":"","sources":["../../source/app/smoke.test.ts"],"names":[],"mappings":""}
@@ -1,210 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { z } from 'zod';
3
- import { defineServer, generateOpenApi, startServer } from './entries.js';
4
- import { SystemError, UserError } from './errors.js';
5
- import { defaultMiddlewares } from './middleware/index.js';
6
- import { createCache, createRedis } from './redis.js';
7
- const summarySchema = {
8
- querystring: z.object({ currency: z.string().optional() }),
9
- response: {
10
- 200: z.object({
11
- status: z.string(),
12
- message: z.string(),
13
- data: z.object({ siteName: z.string(), currency: z.string() }),
14
- }),
15
- },
16
- };
17
- describe('端到端冒烟:defineServer → startServer → inject → generateOpenApi', () => {
18
- it('真实装配路径跑通最小服务器(无 Postgres / 无 Redis / 无网络)', async () => {
19
- const previousDatabaseUrl = process.env.DATABASE_URL;
20
- const previousRedisUrl = process.env.REDIS_URL;
21
- delete process.env.DATABASE_URL;
22
- delete process.env.REDIS_URL;
23
- const lifecycleEvents = [];
24
- const counters = { readConfiguration: 0, disposeDatabase: 0 };
25
- const database = {
26
- readSystemConfiguration: async () => {
27
- counters.readConfiguration += 1;
28
- return { siteName: 'taicode-smoke', currency: 'CNY' };
29
- },
30
- };
31
- try {
32
- const server = defineServer({
33
- app: { service: 'smoke', title: 'Smoke Service', version: '1.4.0' },
34
- resources: (ctx) => {
35
- const redis = createRedis(ctx.env, ctx.log);
36
- return {
37
- // 构造但不建连;出 spec 路径也会构造
38
- database,
39
- redis,
40
- cache: createCache({ env: ctx.env, prefix: 'smoke', redis, log: ctx.log }),
41
- };
42
- },
43
- config: async ({ resources }) => {
44
- const configuration = await resources.database.readSystemConfiguration();
45
- return { siteName: configuration.siteName, currency: configuration.currency };
46
- },
47
- // 声明序即启动序(services 之间不得相互依赖)
48
- services: (ctx) => {
49
- return {
50
- // routers 拿不到 resources / config,只能穿过 service
51
- // 配置一律在**调用期**现读:构造期不得读配置(出 spec 路径不跑 config 槽位)
52
- configuration: {
53
- readSiteName: () => ctx.config.siteName,
54
- readCurrency: () => ctx.config.currency,
55
- },
56
- ledger: {
57
- init: () => {
58
- lifecycleEvents.push('init:ledger');
59
- },
60
- dispose: () => {
61
- lifecycleEvents.push('dispose:ledger');
62
- },
63
- },
64
- repository: {
65
- init: () => {
66
- lifecycleEvents.push('init:repository');
67
- },
68
- dispose: () => {
69
- lifecycleEvents.push('dispose:repository');
70
- },
71
- },
72
- mailer: {
73
- init: () => {
74
- lifecycleEvents.push('init:mailer');
75
- },
76
- dispose: () => {
77
- lifecycleEvents.push('dispose:mailer');
78
- },
79
- },
80
- databaseLifecycle: {
81
- dispose: () => {
82
- counters.disposeDatabase += 1;
83
- },
84
- },
85
- };
86
- },
87
- // 默认预设 [cors, context, auth]
88
- middlewares: (ctx) => defaultMiddlewares(ctx),
89
- routers: (ctx) => {
90
- ctx.app.get('/ledger/summary', { schema: summarySchema }, () => ({
91
- siteName: ctx.services.configuration.readSiteName(),
92
- currency: ctx.services.configuration.readCurrency(),
93
- }));
94
- ctx.app.get('/ledger/invalid-token', () => {
95
- throw new UserError('INVALID_TOKEN', undefined, '令牌无效或已过期');
96
- });
97
- ctx.app.get('/ledger/database-down', () => {
98
- throw new SystemError('DATABASE_UNAVAILABLE', undefined, '数据库暂不可用');
99
- });
100
- },
101
- });
102
- const app = await startServer(server, { listen: false });
103
- // ── 装配期:声明序 init ────────────────────────────────────────────
104
- expect(lifecycleEvents).toEqual(['init:ledger', 'init:repository', 'init:mailer']);
105
- expect(counters.readConfiguration).toBe(1);
106
- try {
107
- // ── 业务路由:成功信封 ────────────────────────────────────────────
108
- const summaryResponse = await app.inject({
109
- method: 'GET',
110
- url: '/ledger/summary?currency=CNY',
111
- headers: { 'x-request-id': 'smoke-summary-1' },
112
- });
113
- expect(summaryResponse.statusCode).toBe(200);
114
- expect(summaryResponse.json()).toEqual({
115
- status: 'SUCCESS',
116
- message: 'ok',
117
- data: { siteName: 'taicode-smoke', currency: 'CNY' },
118
- });
119
- expect(summaryResponse.headers['x-request-id']).toBe('smoke-summary-1');
120
- // ── UserError → 400 ───────────────────────────────────────────────
121
- const userErrorResponse = await app.inject({ method: 'GET', url: '/ledger/invalid-token' });
122
- expect(userErrorResponse.statusCode).toBe(400);
123
- expect(userErrorResponse.json()).toEqual({
124
- status: 'INVALID_TOKEN',
125
- message: '令牌无效或已过期',
126
- data: null,
127
- });
128
- // ── SystemError → 500 ─────────────────────────────────────────────
129
- const systemErrorResponse = await app.inject({ method: 'GET', url: '/ledger/database-down' });
130
- expect(systemErrorResponse.statusCode).toBe(500);
131
- expect(systemErrorResponse.json()).toEqual({
132
- status: 'DATABASE_UNAVAILABLE',
133
- message: '数据库暂不可用',
134
- data: null,
135
- });
136
- // ── 未匹配路由 → 404 ──────────────────────────────────────────────
137
- const notFoundResponse = await app.inject({ method: 'GET', url: '/no-such-route' });
138
- expect(notFoundResponse.statusCode).toBe(404);
139
- expect(notFoundResponse.json()).toMatchObject({ status: 'NOT_IMPLEMENTED', data: null });
140
- expect(notFoundResponse.headers['x-request-id']).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/);
141
- expect(notFoundResponse.json()).not.toHaveProperty('requestId');
142
- }
143
- finally {
144
- await app.close();
145
- }
146
- // ── 关闭:services 逆序 dispose ─────────────────────────────────────
147
- expect(lifecycleEvents).toEqual([
148
- 'init:ledger',
149
- 'init:repository',
150
- 'init:mailer',
151
- 'dispose:mailer',
152
- 'dispose:repository',
153
- 'dispose:ledger',
154
- ]);
155
- expect(counters.disposeDatabase).toBe(1);
156
- // ── 同一份定义 → generateOpenApi:不跑 config、不重跑 init ──────────
157
- const spec = (await generateOpenApi(server));
158
- expect(counters.readConfiguration).toBe(1);
159
- expect(lifecycleEvents).toHaveLength(6);
160
- expect(spec.openapi).toBe('3.0.3');
161
- expect(spec.info).toMatchObject({ title: 'Smoke Service', version: '1.4.0' });
162
- expect(Object.keys(spec.paths ?? {})).toEqual(expect.arrayContaining(['/ledger/summary', '/ledger/invalid-token', '/ledger/database-down']));
163
- }
164
- finally {
165
- if (previousDatabaseUrl === undefined) {
166
- delete process.env.DATABASE_URL;
167
- }
168
- else {
169
- process.env.DATABASE_URL = previousDatabaseUrl;
170
- }
171
- if (previousRedisUrl === undefined) {
172
- delete process.env.REDIS_URL;
173
- }
174
- else {
175
- process.env.REDIS_URL = previousRedisUrl;
176
- }
177
- }
178
- });
179
- it('非预置原生插件可在 routers(ctx) 里经 ctx.app.register() 注册并生效', async () => {
180
- const smokeDecoratorPlugin = async (app) => {
181
- app.decorate('smokeDecorator', () => 'smoke-decorated');
182
- app.get('/plugin/decorated', () => ({
183
- value: app.smokeDecorator(),
184
- }));
185
- };
186
- const server = defineServer({
187
- app: { service: 'smoke-plugin', title: 'Smoke Plugin', version: '1.0.0' },
188
- resources: () => ({}),
189
- config: () => ({}),
190
- services: () => ({}),
191
- middlewares: () => [],
192
- routers: (ctx) => {
193
- void ctx.app.register(smokeDecoratorPlugin);
194
- },
195
- });
196
- const app = await startServer(server, { listen: false });
197
- try {
198
- const response = await app.inject({ method: 'GET', url: '/plugin/decorated' });
199
- expect(response.statusCode).toBe(200);
200
- expect(response.json()).toEqual({
201
- status: 'SUCCESS',
202
- message: 'ok',
203
- data: { value: 'smoke-decorated' },
204
- });
205
- }
206
- finally {
207
- await app.close();
208
- }
209
- });
210
- });
@@ -1,21 +0,0 @@
1
- import type { ConfigEnv } from './env.js';
2
- import type { LoggerLike } from './types.js';
3
- /**
4
- * 测试公共助手(仅测试使用)。
5
- *
6
- * 只放「多个测试文件都要用」的小工具:记录型 logger、测试用 env。
7
- * 断言与业务语义一律写在各自 `*.test.ts` 内,避免助手文件成为第二真相源。
8
- */
9
- export interface LogEntry {
10
- level: 'debug' | 'info' | 'warn' | 'error';
11
- obj: unknown;
12
- msg?: string;
13
- }
14
- /** 记录型 logger:满足 `LoggerLike`,便于断言「被调用了 / 在什么级别」 */
15
- export interface RecordingLogger extends LoggerLike {
16
- readonly entries: LogEntry[];
17
- }
18
- export declare function createRecordingLogger(): RecordingLogger;
19
- /** 测试用 `ConfigEnv`(固定读取约定:`ENV` / `API_PORT` / `LOG_LEVEL` / `DATABASE_URL` / `REDIS_URL`) */
20
- export declare function createTestConfigEnv(overrides?: Partial<ConfigEnv>): ConfigEnv;
21
- //# sourceMappingURL=test-support.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"test-support.d.ts","sourceRoot":"","sources":["../../source/app/test-support.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;GAKG;AAEH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;IAC1C,GAAG,EAAE,OAAO,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,QAAQ,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAA;CAC7B;AAED,wBAAgB,qBAAqB,IAAI,eAAe,CAcvD;AAED,8FAA8F;AAC9F,wBAAgB,mBAAmB,CAAC,SAAS,GAAE,OAAO,CAAC,SAAS,CAAM,GAAG,SAAS,CASjF"}
@@ -1,24 +0,0 @@
1
- export function createRecordingLogger() {
2
- const entries = [];
3
- const record = (level) => (obj, msg) => {
4
- entries.push({ level, obj, msg });
5
- };
6
- return {
7
- entries,
8
- debug: record('debug'),
9
- info: record('info'),
10
- warn: record('warn'),
11
- error: record('error'),
12
- };
13
- }
14
- /** 测试用 `ConfigEnv`(固定读取约定:`ENV` / `API_PORT` / `LOG_LEVEL` / `DATABASE_URL` / `REDIS_URL`) */
15
- export function createTestConfigEnv(overrides = {}) {
16
- return {
17
- ENV: 'test',
18
- API_PORT: '',
19
- LOG_LEVEL: 'info',
20
- DATABASE_URL: '',
21
- REDIS_URL: undefined,
22
- ...overrides,
23
- };
24
- }
@@ -1 +0,0 @@
1
- {"version":3,"file":"type-augment.d.ts","sourceRoot":"","sources":["../../source/app/type-augment.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG,kBAAkB,GAAG,sBAAsB,CAAA;AAE3F,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,cAAc;IACd,MAAM,EAAE,MAAM,CAAA;IACd,wBAAwB;IACxB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,wBAAwB;QACxB,IAAI,CAAC,EAAE,OAAO,CAAA;QACd,wBAAwB;QACxB,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,4CAA4C;QAC5C,GAAG,CAAC,EAAE,cAAc,CAAA;KACrB;IAED,UAAU,oBAAoB;QAC5B,+EAA+E;QAC/E,cAAc,CAAC,EAAE,aAAa,EAAE,CAAA;QAChC,cAAc;QACd,SAAS,CAAC,EAAE,aAAa,CAAA;KAC1B;CACF"}