@taicode/common-server 4.0.0-beta.3 → 4.0.0-beta.5

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 (270) hide show
  1. package/output/app/index.d.ts +13 -15
  2. package/output/app/index.d.ts.map +1 -1
  3. package/output/app/index.js +256 -18
  4. package/output/auth/index.d.ts +72 -0
  5. package/output/auth/index.d.ts.map +1 -0
  6. package/output/auth/index.js +176 -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 +89 -0
  11. package/output/core/boot/context.d.ts.map +1 -0
  12. package/output/{app → core/boot}/context.js +44 -17
  13. package/output/{app → core/boot}/lifecycle.d.ts +13 -15
  14. package/output/core/boot/lifecycle.d.ts.map +1 -0
  15. package/output/{app → core/boot}/lifecycle.js +15 -12
  16. package/output/{app → core/boot}/shutdown.d.ts +9 -6
  17. package/output/core/boot/shutdown.d.ts.map +1 -0
  18. package/output/{app → core/boot}/shutdown.js +7 -4
  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 +107 -0
  23. package/output/core/boot/steps.d.ts.map +1 -0
  24. package/output/core/boot/steps.js +200 -0
  25. package/output/core/entries.d.ts +123 -0
  26. package/output/core/entries.d.ts.map +1 -0
  27. package/output/core/entries.js +193 -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 +217 -0
  31. package/output/core/env.d.ts +62 -0
  32. package/output/core/env.d.ts.map +1 -0
  33. package/output/core/env.js +96 -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 +22 -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} +22 -11
  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 +171 -0
  81. package/output/core/test-support/database.d.ts +96 -0
  82. package/output/core/test-support/database.d.ts.map +1 -0
  83. package/output/core/test-support/database.js +144 -0
  84. package/output/core/test-support/database.test.d.ts +2 -0
  85. package/output/core/test-support/database.test.d.ts.map +1 -0
  86. package/output/core/test-support/database.test.js +110 -0
  87. package/output/core/test-support/environment.d.ts +33 -0
  88. package/output/core/test-support/environment.d.ts.map +1 -0
  89. package/output/core/test-support/environment.js +42 -0
  90. package/output/core/test-support/test-server.d.ts +94 -0
  91. package/output/core/test-support/test-server.d.ts.map +1 -0
  92. package/output/core/test-support/test-server.js +60 -0
  93. package/output/core/test-support/test-server.test.d.ts +2 -0
  94. package/output/core/test-support/test-server.test.d.ts.map +1 -0
  95. package/output/core/test-support/test-server.test.js +162 -0
  96. package/output/{app → core}/type-augment.d.ts +2 -2
  97. package/output/core/type-augment.d.ts.map +1 -0
  98. package/output/{app → core}/types.d.ts +153 -66
  99. package/output/core/types.d.ts.map +1 -0
  100. package/output/cors/index.d.ts +3 -0
  101. package/output/cors/index.d.ts.map +1 -0
  102. package/output/cors/index.js +27 -0
  103. package/output/index.d.ts +8 -4
  104. package/output/index.d.ts.map +1 -1
  105. package/output/index.js +24 -5
  106. package/output/rate-limit/index.d.ts +23 -0
  107. package/output/rate-limit/index.d.ts.map +1 -0
  108. package/output/rate-limit/index.js +42 -0
  109. package/output/{rate-limiter/rate-limiter.d.ts → rate-limit/limiter.d.ts} +1 -1
  110. package/output/rate-limit/limiter.d.ts.map +1 -0
  111. package/output/rate-limit/memory-store.d.ts.map +1 -0
  112. package/output/rate-limit/redis-store.d.ts.map +1 -0
  113. package/output/rate-limit/redis-store.test.d.ts.map +1 -0
  114. package/output/{rate-limiter → rate-limit}/redis-store.test.js +2 -2
  115. package/output/{rate-limiter → rate-limit}/types.d.ts +0 -1
  116. package/output/rate-limit/types.d.ts.map +1 -0
  117. package/output/rate-limit/types.js +1 -0
  118. package/output/{redis-cache/redis-cache.d.ts → redis/cache.d.ts} +1 -1
  119. package/output/redis/cache.d.ts.map +1 -0
  120. package/output/redis/cache.test.d.ts +2 -0
  121. package/output/redis/cache.test.d.ts.map +1 -0
  122. package/output/{redis-cache/redis-cache.test.js → redis/cache.test.js} +1 -1
  123. package/output/{app/redis.d.ts → redis/index.d.ts} +7 -6
  124. package/output/redis/index.d.ts.map +1 -0
  125. package/output/{app/redis.js → redis/index.js} +10 -4
  126. package/output/redis/index.test.d.ts +2 -0
  127. package/output/{context → redis}/index.test.d.ts.map +1 -1
  128. package/output/redis/index.test.js +55 -0
  129. package/output/redis-queue/index.js +1 -1
  130. package/output/redis-queue/test-helpers.d.ts +1 -1
  131. package/output/redis-queue/test-helpers.js +1 -1
  132. package/output/redis-queue/types.d.ts +3 -12
  133. package/output/redis-queue/types.d.ts.map +1 -1
  134. package/package.json +5 -5
  135. package/output/app/auth.test.d.ts +0 -2
  136. package/output/app/auth.test.d.ts.map +0 -1
  137. package/output/app/auth.test.js +0 -148
  138. package/output/app/boot.d.ts +0 -44
  139. package/output/app/boot.d.ts.map +0 -1
  140. package/output/app/boot.js +0 -158
  141. package/output/app/config.test.d.ts +0 -2
  142. package/output/app/config.test.d.ts.map +0 -1
  143. package/output/app/config.test.js +0 -132
  144. package/output/app/context.d.ts +0 -74
  145. package/output/app/context.d.ts.map +0 -1
  146. package/output/app/core/cookie.d.ts +0 -25
  147. package/output/app/core/cookie.d.ts.map +0 -1
  148. package/output/app/core/identity.d.ts +0 -12
  149. package/output/app/core/identity.d.ts.map +0 -1
  150. package/output/app/core/index.d.ts +0 -44
  151. package/output/app/core/index.d.ts.map +0 -1
  152. package/output/app/core/index.js +0 -57
  153. package/output/app/core/logging.d.ts +0 -13
  154. package/output/app/core/logging.d.ts.map +0 -1
  155. package/output/app/core/openapi.d.ts +0 -17
  156. package/output/app/core/openapi.d.ts.map +0 -1
  157. package/output/app/core/request-id.d.ts.map +0 -1
  158. package/output/app/core/response.d.ts +0 -41
  159. package/output/app/core/response.d.ts.map +0 -1
  160. package/output/app/define-server.negative-cases.d.ts +0 -40
  161. package/output/app/define-server.negative-cases.d.ts.map +0 -1
  162. package/output/app/define-server.negative-cases.js +0 -268
  163. package/output/app/define-server.test.d.ts +0 -2
  164. package/output/app/define-server.test.d.ts.map +0 -1
  165. package/output/app/define-server.test.js +0 -97
  166. package/output/app/entries.d.ts +0 -31
  167. package/output/app/entries.d.ts.map +0 -1
  168. package/output/app/entries.js +0 -66
  169. package/output/app/env.d.ts +0 -59
  170. package/output/app/env.d.ts.map +0 -1
  171. package/output/app/env.js +0 -66
  172. package/output/app/env.test.js +0 -124
  173. package/output/app/error-contract.test.d.ts +0 -2
  174. package/output/app/error-contract.test.d.ts.map +0 -1
  175. package/output/app/error-contract.test.js +0 -227
  176. package/output/app/errors.d.ts +0 -20
  177. package/output/app/errors.d.ts.map +0 -1
  178. package/output/app/errors.js +0 -51
  179. package/output/app/generate-openapi.test.d.ts +0 -2
  180. package/output/app/generate-openapi.test.d.ts.map +0 -1
  181. package/output/app/generate-openapi.test.js +0 -123
  182. package/output/app/lifecycle-logging.test.d.ts +0 -2
  183. package/output/app/lifecycle-logging.test.d.ts.map +0 -1
  184. package/output/app/lifecycle-logging.test.js +0 -175
  185. package/output/app/lifecycle.d.ts.map +0 -1
  186. package/output/app/lifecycle.test.d.ts +0 -2
  187. package/output/app/lifecycle.test.d.ts.map +0 -1
  188. package/output/app/lifecycle.test.js +0 -153
  189. package/output/app/middleware/auth.d.ts +0 -71
  190. package/output/app/middleware/auth.d.ts.map +0 -1
  191. package/output/app/middleware/auth.js +0 -190
  192. package/output/app/middleware/context.d.ts +0 -13
  193. package/output/app/middleware/context.d.ts.map +0 -1
  194. package/output/app/middleware/context.js +0 -15
  195. package/output/app/middleware/cors.d.ts +0 -14
  196. package/output/app/middleware/cors.d.ts.map +0 -1
  197. package/output/app/middleware/cors.js +0 -30
  198. package/output/app/middleware/index.d.ts +0 -17
  199. package/output/app/middleware/index.d.ts.map +0 -1
  200. package/output/app/middleware/index.js +0 -16
  201. package/output/app/middleware/rate-limit.d.ts +0 -24
  202. package/output/app/middleware/rate-limit.d.ts.map +0 -1
  203. package/output/app/middleware/rate-limit.js +0 -46
  204. package/output/app/middleware-order.test.d.ts +0 -2
  205. package/output/app/middleware-order.test.d.ts.map +0 -1
  206. package/output/app/middleware-order.test.js +0 -149
  207. package/output/app/permission-operations.d.ts +0 -48
  208. package/output/app/permission-operations.d.ts.map +0 -1
  209. package/output/app/permission-operations.js +0 -77
  210. package/output/app/permission-operations.test.d.ts +0 -2
  211. package/output/app/permission-operations.test.d.ts.map +0 -1
  212. package/output/app/permission-operations.test.js +0 -72
  213. package/output/app/permission-registration.test.d.ts +0 -2
  214. package/output/app/permission-registration.test.d.ts.map +0 -1
  215. package/output/app/permission-registration.test.js +0 -138
  216. package/output/app/redis.d.ts.map +0 -1
  217. package/output/app/registered-operations.test.d.ts +0 -2
  218. package/output/app/registered-operations.test.d.ts.map +0 -1
  219. package/output/app/registered-operations.test.js +0 -57
  220. package/output/app/request-id.test.d.ts +0 -2
  221. package/output/app/request-id.test.d.ts.map +0 -1
  222. package/output/app/request-id.test.js +0 -130
  223. package/output/app/resources.test.d.ts +0 -2
  224. package/output/app/resources.test.d.ts.map +0 -1
  225. package/output/app/resources.test.js +0 -222
  226. package/output/app/shutdown.d.ts.map +0 -1
  227. package/output/app/shutdown.test.d.ts +0 -2
  228. package/output/app/shutdown.test.d.ts.map +0 -1
  229. package/output/app/shutdown.test.js +0 -210
  230. package/output/app/signal.d.ts +0 -12
  231. package/output/app/signal.d.ts.map +0 -1
  232. package/output/app/smoke.test.d.ts +0 -2
  233. package/output/app/smoke.test.d.ts.map +0 -1
  234. package/output/app/smoke.test.js +0 -210
  235. package/output/app/test-support.d.ts +0 -21
  236. package/output/app/test-support.d.ts.map +0 -1
  237. package/output/app/test-support.js +0 -24
  238. package/output/app/type-augment.d.ts.map +0 -1
  239. package/output/app/types.d.ts.map +0 -1
  240. package/output/context/index.d.ts +0 -6
  241. package/output/context/index.d.ts.map +0 -1
  242. package/output/context/index.js +0 -14
  243. package/output/rate-limiter/index.d.ts +0 -5
  244. package/output/rate-limiter/index.d.ts.map +0 -1
  245. package/output/rate-limiter/index.js +0 -4
  246. package/output/rate-limiter/memory-store.d.ts.map +0 -1
  247. package/output/rate-limiter/rate-limiter.d.ts.map +0 -1
  248. package/output/rate-limiter/redis-store.d.ts.map +0 -1
  249. package/output/rate-limiter/redis-store.test.d.ts.map +0 -1
  250. package/output/rate-limiter/types.d.ts.map +0 -1
  251. package/output/redis-cache/index.d.ts +0 -4
  252. package/output/redis-cache/index.d.ts.map +0 -1
  253. package/output/redis-cache/index.js +0 -1
  254. package/output/redis-cache/redis-cache.d.ts.map +0 -1
  255. package/output/redis-cache/redis-cache.test.d.ts +0 -2
  256. package/output/redis-cache/redis-cache.test.d.ts.map +0 -1
  257. package/output/schema/index.d.ts.map +0 -1
  258. /package/output/{app → core}/env.test.d.ts +0 -0
  259. /package/output/{rate-limiter → core/modules/auth-provider}/types.js +0 -0
  260. /package/output/{context → core/modules/context}/index.test.d.ts +0 -0
  261. /package/output/{context → core/modules/context}/index.test.js +0 -0
  262. /package/output/{app → core}/type-augment.js +0 -0
  263. /package/output/{app → core}/types.js +0 -0
  264. /package/output/{rate-limiter/rate-limiter.js → rate-limit/limiter.js} +0 -0
  265. /package/output/{rate-limiter → rate-limit}/memory-store.d.ts +0 -0
  266. /package/output/{rate-limiter → rate-limit}/memory-store.js +0 -0
  267. /package/output/{rate-limiter → rate-limit}/redis-store.d.ts +0 -0
  268. /package/output/{rate-limiter → rate-limit}/redis-store.js +0 -0
  269. /package/output/{rate-limiter → rate-limit}/redis-store.test.d.ts +0 -0
  270. /package/output/{redis-cache/redis-cache.js → redis/cache.js} +0 -0
@@ -1,138 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { defineServer, generateOpenApi, startServer } from './entries.js';
3
- import { defaultMiddlewares } from './middleware/index.js';
4
- import { createAuthMiddleware } from './middleware/auth.js';
5
- /** 注入假 fetch:记录请求,并按 `responder` 返回最小 Response 形状 */
6
- function stubFetch(responder) {
7
- const requests = [];
8
- const fetchStub = vi.fn(async (input, init) => {
9
- const url = typeof input === 'string' ? input : input.toString();
10
- requests.push({
11
- url,
12
- method: init?.method,
13
- body: init?.body ? JSON.parse(String(init.body)) : undefined,
14
- });
15
- const { status, payload } = responder(url);
16
- return {
17
- ok: status >= 200 && status < 300,
18
- status,
19
- json: async () => payload,
20
- };
21
- });
22
- vi.stubGlobal('fetch', fetchStub);
23
- return { requests, fetchStub };
24
- }
25
- const listWidgetSchema = {
26
- operationId: 'listWidget',
27
- summary: 'List widgets',
28
- tags: ['Widget'],
29
- };
30
- const createWidgetSchema = {
31
- operationId: 'createWidget',
32
- };
33
- function createServer(configuration, authOptions) {
34
- return defineServer({
35
- app: { service: 'widgets', title: 'Widgets Service', version: '2.3.4' },
36
- resources: () => ({}),
37
- config: () => configuration,
38
- // 默认预设去掉 auth,换上带选项的 auth(设计 §5.2 预设只是内容)
39
- middlewares: (ctx) => [...defaultMiddlewares(ctx).slice(0, -1), createAuthMiddleware(ctx, authOptions)],
40
- services: () => ({}),
41
- routers: (ctx) => {
42
- // 公开路由(不走 verify);本用例只关心 onReady 的自注册
43
- ctx.app.get('/widget/list', { schema: listWidgetSchema, config: { accessControls: ['NoLoginRequired'] } }, () => ({ items: [] }));
44
- ctx.app.post('/widget/create', { schema: createWidgetSchema, config: { accessControls: ['NoLoginRequired'] } }, () => ({ id: 'w-1' }));
45
- },
46
- });
47
- }
48
- afterEach(() => {
49
- vi.unstubAllGlobals();
50
- });
51
- describe('权限操作自注册(设计 §5.2 / §10-11)', () => {
52
- it('onReady 阶段向 /permission/operation/register POST 正确请求体(service / version / operations)', async () => {
53
- const { requests, fetchStub } = stubFetch(() => ({ status: 200, payload: { status: 'SUCCESS', data: {} } }));
54
- const app = await startServer(createServer({ account: { enabled: true, serviceUrl: 'http://account.local' } }), { listen: false });
55
- try {
56
- expect(fetchStub).toHaveBeenCalledTimes(1);
57
- expect(requests).toHaveLength(1);
58
- expect(requests[0].url).toBe('http://account.local/permission/operation/register');
59
- expect(requests[0].method).toBe('POST');
60
- expect(requests[0].body).toEqual({
61
- service: 'widgets',
62
- version: '2.3.4',
63
- operations: [
64
- { operation: 'listWidget', summary: 'List widgets', description: '', tags: ['Widget'] },
65
- { operation: 'createWidget', summary: '', description: '', tags: [] },
66
- ],
67
- });
68
- }
69
- finally {
70
- await app.close();
71
- }
72
- });
73
- it('account 无 serviceUrl 时不注册(自然排除 account 自身)', async () => {
74
- const { fetchStub } = stubFetch(() => ({ status: 200, payload: { status: 'SUCCESS' } }));
75
- const app = await startServer(createServer({ account: { enabled: false } }), { listen: false });
76
- try {
77
- expect(fetchStub).not.toHaveBeenCalled();
78
- }
79
- finally {
80
- await app.close();
81
- }
82
- });
83
- it('registerPermissions: false 时不注册', async () => {
84
- const { fetchStub } = stubFetch(() => ({ status: 200, payload: { status: 'SUCCESS' } }));
85
- const app = await startServer(createServer({ account: { serviceUrl: 'http://account.local' } }, { registerPermissions: false }), { listen: false });
86
- try {
87
- expect(fetchStub).not.toHaveBeenCalled();
88
- }
89
- finally {
90
- await app.close();
91
- }
92
- });
93
- it('注册失败(HTTP 非 2xx)不致命:startServer 仍 resolve、服务可用', async () => {
94
- const { fetchStub } = stubFetch(() => ({ status: 500, payload: { status: 'UNKNOWN_ERROR', message: 'boom' } }));
95
- const app = await startServer(createServer({ account: { serviceUrl: 'http://account.local' } }), { listen: false });
96
- try {
97
- expect(fetchStub).toHaveBeenCalledTimes(1);
98
- const response = await app.inject({ method: 'GET', url: '/widget/list' });
99
- expect(response.statusCode).toBe(200);
100
- }
101
- finally {
102
- await app.close();
103
- }
104
- });
105
- it('HTTP ok 但信封非 SUCCESS 也不致命(成功判定与 /gateway/verify 同口径)', async () => {
106
- const { fetchStub } = stubFetch(() => ({ status: 200, payload: { status: 'INVALID_INPUT', message: 'bad' } }));
107
- const app = await startServer(createServer({ account: { serviceUrl: 'http://account.local' } }), { listen: false });
108
- try {
109
- expect(fetchStub).toHaveBeenCalledTimes(1);
110
- const response = await app.inject({ method: 'GET', url: '/widget/list' });
111
- expect(response.statusCode).toBe(200);
112
- }
113
- finally {
114
- await app.close();
115
- }
116
- });
117
- it('fetch 抛错(网络不可达)也不致命', async () => {
118
- const fetchStub = vi.fn(async () => {
119
- throw new Error('network unreachable');
120
- });
121
- vi.stubGlobal('fetch', fetchStub);
122
- const app = await startServer(createServer({ account: { serviceUrl: 'http://account.local' } }), { listen: false });
123
- try {
124
- expect(fetchStub).toHaveBeenCalledTimes(1);
125
- const response = await app.inject({ method: 'GET', url: '/widget/list' });
126
- expect(response.statusCode).toBe(200);
127
- }
128
- finally {
129
- await app.close();
130
- }
131
- });
132
- it('出 spec 路径(generateOpenApi 不调 loadConfig)不注册', async () => {
133
- const { fetchStub } = stubFetch(() => ({ status: 200, payload: { status: 'SUCCESS' } }));
134
- const spec = (await generateOpenApi(createServer({ account: { serviceUrl: 'http://account.local' } })));
135
- expect(fetchStub).not.toHaveBeenCalled();
136
- expect(Object.keys(spec.paths ?? {})).toEqual(expect.arrayContaining(['/widget/list', '/widget/create']));
137
- });
138
- });
@@ -1 +0,0 @@
1
- {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../source/app/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,UAAU,EAAE,MAAM,yBAAyB,CAAA;AAEvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAEzD;;;;;;;;;;;;;;;;;;GAkBG;AAEH,2CAA2C;AAC3C,eAAO,MAAM,+BAA+B,MAAM,CAAA;AAElD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,WAAW,GAAG,SAAS,CAmBrF;AAED,uBAAuB;AACvB,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,SAAS,CAAA;IACd,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,iDAAiD;IACjD,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,GAAG,CAAC,EAAE,UAAU,CAAA;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,CAYhE"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=registered-operations.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registered-operations.test.d.ts","sourceRoot":"","sources":["../../source/app/registered-operations.test.ts"],"names":[],"mappings":""}
@@ -1,57 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { defineServer, generateOpenApi, startServer } from './entries.js';
3
- import { createContextMiddleware } from './middleware/context.js';
4
- import { readRegisteredOperations, toPermissionOperations } from './permission-operations.js';
5
- /**
6
- * 路由注册表 + swagger 时序。
7
- *
8
- * 关键点:自定义 `middlewares`(**不含 cors**)时,swagger 插件体也必须在任何路由注册之前执行完
9
- * (`registerOpenApi` 内 `await app.after()`),路由注册表同样非空——两者都不依赖「恰好有 cors
10
- * 顺带冲刷队列」这种隐式巧合。
11
- */
12
- const listWidgetSchema = {
13
- operationId: 'listWidget',
14
- summary: 'List widgets',
15
- tags: ['Widget'],
16
- };
17
- /** 未声明 `operationId` 的路由(回落 url,且不进权限目录) */
18
- const plainSchema = {};
19
- function createServer() {
20
- return defineServer({
21
- app: { service: 'registry', title: 'Registry', version: '1.0.0' },
22
- resources: () => ({}),
23
- config: () => ({}),
24
- services: () => ({}),
25
- // 自定义中间件:只有 context,**没有 cors**(不得靠 cors 冲刷 swagger 队列)
26
- middlewares: () => [createContextMiddleware()],
27
- routers: (ctx) => {
28
- ctx.app.get('/widget/list', { schema: listWidgetSchema }, () => ({ items: [] }));
29
- ctx.app.post('/widget/plain', { schema: plainSchema }, () => ({ ok: true }));
30
- },
31
- });
32
- }
33
- describe('路由注册表', () => {
34
- it('自定义 middlewares(无 cors)时,注册表非空且能拿到 operationId', async () => {
35
- const app = await startServer(createServer(), { listen: false });
36
- try {
37
- const operations = readRegisteredOperations(app);
38
- expect(operations.length).toBeGreaterThan(0);
39
- const listWidget = operations.find((entry) => entry.url === '/widget/list');
40
- expect(listWidget).toMatchObject({ method: 'GET', url: '/widget/list', operationId: 'listWidget' });
41
- // Fastify 为 GET 自动生成的 HEAD 路由不进注册表(与 swagger 同口径)
42
- expect(operations.some((entry) => entry.method === 'HEAD')).toBe(false);
43
- // 未声明 operationId 的路由不进权限目录
44
- expect(toPermissionOperations(operations)).toEqual([
45
- { operation: 'listWidget', summary: 'List widgets', description: '', tags: ['Widget'] },
46
- ]);
47
- }
48
- finally {
49
- await app.close();
50
- }
51
- });
52
- it('自定义 middlewares(无 cors)时,swagger 文档仍包含已注册路由(确定性冲刷)', async () => {
53
- const spec = (await generateOpenApi(createServer()));
54
- const paths = spec.paths ?? {};
55
- expect(Object.keys(paths)).toEqual(expect.arrayContaining(['/widget/list', '/widget/plain']));
56
- });
57
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=request-id.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"request-id.test.d.ts","sourceRoot":"","sources":["../../source/app/request-id.test.ts"],"names":[],"mappings":""}
@@ -1,130 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { createRequestIdOptions, normalizeInboundRequestId, REQUEST_ID_HEADER, REQUEST_ID_LOG_LABEL, } from './core/request-id.js';
3
- import { defineServer, startServer } from './entries.js';
4
- /**
5
- * `requestId` 契约(设计 §5.1 / §8)——**框架内置、锁定**。
6
- *
7
- * 入站 `x-request-id` 存在且非空 → 复用,否则 `randomUUID`;响应 header 统一回吐
8
- * `x-request-id`;**不进响应体**;日志字段键名统一为全拼 `requestId`。
9
- */
10
- const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
11
- function createRequestIdServer() {
12
- return defineServer({
13
- app: { service: 'request-id-test', title: 'Request Id Test', version: '1.0.0' },
14
- resources: () => ({}),
15
- config: () => ({}),
16
- services: () => ({}),
17
- middlewares: () => [],
18
- routers: (ctx) => {
19
- ctx.app.get('/echo', () => ({ ok: true }));
20
- },
21
- });
22
- }
23
- /** 启动一个可读取请求日志 bindings 的 app(`x-request-id` 归一化断言用) */
24
- async function startBindingApp() {
25
- let bindings;
26
- const server = defineServer({
27
- app: { service: 'request-id-normalize', title: 'Request Id Normalize', version: '1.0.0' },
28
- resources: () => ({}),
29
- config: () => ({}),
30
- services: () => ({}),
31
- middlewares: () => [],
32
- routers: (ctx) => {
33
- ctx.app.addHook('onRequest', (request, _reply, done) => {
34
- bindings = request.log.bindings();
35
- done();
36
- });
37
- ctx.app.get('/echo', () => ({ ok: true }));
38
- },
39
- });
40
- const app = await startServer(server, { listen: false });
41
- return { app, getBindings: () => bindings };
42
- }
43
- describe('requestId(设计 §5.1 / §8)', () => {
44
- it('固定 header 名与日志字段名', () => {
45
- expect(REQUEST_ID_HEADER).toBe('x-request-id');
46
- expect(REQUEST_ID_LOG_LABEL).toBe('requestId');
47
- const options = createRequestIdOptions();
48
- expect(options.requestIdHeader).toBe('x-request-id');
49
- expect(options.requestIdLogLabel).toBe('requestId');
50
- });
51
- it('genReqId:入站值非空则复用,否则生成 UUID', () => {
52
- const { genReqId } = createRequestIdOptions();
53
- expect(genReqId({ headers: { 'x-request-id': 'abc' } })).toBe('abc');
54
- expect(genReqId({ headers: { 'x-request-id': ['abc', 'def'] } })).toBe('abc');
55
- expect(genReqId({ headers: {} })).toMatch(UUID_PATTERN);
56
- expect(genReqId({ headers: { 'x-request-id': ' ' } })).toMatch(UUID_PATTERN);
57
- });
58
- it('normalizeInboundRequestId:trim 后为空视为缺失(undefined),非空返回 trim 值', () => {
59
- expect(normalizeInboundRequestId(undefined)).toBeUndefined();
60
- expect(normalizeInboundRequestId('')).toBeUndefined();
61
- expect(normalizeInboundRequestId(' ')).toBeUndefined();
62
- expect(normalizeInboundRequestId('trace-abc')).toBe('trace-abc');
63
- expect(normalizeInboundRequestId(' trace-abc ')).toBe('trace-abc');
64
- expect(normalizeInboundRequestId(['abc', 'def'])).toBe('abc');
65
- });
66
- it('空白入站 header → 视为缺失,重新生成 UUID(request.id / 日志字段 / 响应 header 一致)', async () => {
67
- const { app, getBindings } = await startBindingApp();
68
- const response = await app.inject({ method: 'GET', url: '/echo', headers: { [REQUEST_ID_HEADER]: ' ' } });
69
- expect(response.statusCode).toBe(200);
70
- const requestId = String(response.headers[REQUEST_ID_HEADER]);
71
- expect(requestId).toMatch(UUID_PATTERN);
72
- expect(getBindings()).toEqual({ [REQUEST_ID_LOG_LABEL]: requestId });
73
- await app.close();
74
- });
75
- it('带空格的入站 header → trim 后复用(request.id / 日志字段 / 响应 header 一致)', async () => {
76
- const { app, getBindings } = await startBindingApp();
77
- const response = await app.inject({
78
- method: 'GET',
79
- url: '/echo',
80
- headers: { [REQUEST_ID_HEADER]: ' trace-trim ' },
81
- });
82
- expect(response.statusCode).toBe(200);
83
- expect(response.headers[REQUEST_ID_HEADER]).toBe('trace-trim');
84
- expect(getBindings()).toEqual({ [REQUEST_ID_LOG_LABEL]: 'trace-trim' });
85
- await app.close();
86
- });
87
- it('缺失入站 header → 生成 UUID(request.id / 日志字段 / 响应 header 一致)', async () => {
88
- const { app, getBindings } = await startBindingApp();
89
- const response = await app.inject({ method: 'GET', url: '/echo' });
90
- expect(response.statusCode).toBe(200);
91
- const requestId = String(response.headers[REQUEST_ID_HEADER]);
92
- expect(requestId).toMatch(UUID_PATTERN);
93
- expect(getBindings()).toEqual({ [REQUEST_ID_LOG_LABEL]: requestId });
94
- await app.close();
95
- });
96
- it('复用入站 header 并回吐到响应 header;日志绑定字段为 requestId;不进响应体', async () => {
97
- let bindings;
98
- const server = defineServer({
99
- app: { service: 'request-id-integration', title: 'Request Id Integration', version: '1.0.0' },
100
- resources: () => ({}),
101
- config: () => ({}),
102
- services: () => ({}),
103
- middlewares: () => [],
104
- routers: (ctx) => {
105
- ctx.app.addHook('onRequest', (request, _reply, done) => {
106
- bindings = request.log.bindings();
107
- done();
108
- });
109
- ctx.app.get('/echo', () => ({ ok: true }));
110
- },
111
- });
112
- const app = await startServer(server, { listen: false });
113
- const reused = await app.inject({ method: 'GET', url: '/echo', headers: { 'x-request-id': 'trace-abc' } });
114
- expect(reused.statusCode).toBe(200);
115
- expect(reused.headers[REQUEST_ID_HEADER]).toBe('trace-abc');
116
- expect(bindings).toEqual({ requestId: 'trace-abc' });
117
- expect(reused.body).not.toContain('trace-abc');
118
- const generated = await app.inject({ method: 'GET', url: '/echo' });
119
- expect(String(generated.headers[REQUEST_ID_HEADER])).toMatch(UUID_PATTERN);
120
- expect(bindings).toHaveProperty(REQUEST_ID_LOG_LABEL);
121
- await app.close();
122
- });
123
- it('startServer 冒烟:定义可跑、路由可用', async () => {
124
- const app = await startServer(createRequestIdServer(), { listen: false });
125
- const response = await app.inject({ method: 'GET', url: '/echo' });
126
- expect(response.statusCode).toBe(200);
127
- expect(response.json()).toEqual({ status: 'SUCCESS', message: 'ok', data: { ok: true } });
128
- await app.close();
129
- });
130
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=resources.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"resources.test.d.ts","sourceRoot":"","sources":["../../source/app/resources.test.ts"],"names":[],"mappings":""}
@@ -1,222 +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 + disposer + registerClassifyError,且资源嵌套在 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
- // 槽位 ctx:env + log + disposer(+ 专属的 registerClassifyError)
47
- expect(Object.keys(captured ?? {}).sort()).toEqual(['disposer', 'env', 'log', 'registerClassifyError']);
48
- expect(captured?.env.ENV).toBeTypeOf('string');
49
- expect(typeof captured?.log.info).toBe('function');
50
- expect(typeof captured?.disposer.add).toBe('function');
51
- // resources 槽位专属能力:每个资源注册自己的错误分类器(函数)
52
- expect(typeof captured?.registerClassifyError).toBe('function');
53
- expect(observed.inConfig).toBe('rows:select 1');
54
- expect(observed.inServices).toBe('domain:cached');
55
- const response = await app.inject({ method: 'GET', url: '/resource' });
56
- expect(response.statusCode).toBe(200);
57
- }
58
- finally {
59
- await app.close();
60
- }
61
- });
62
- it('② `false` 关闭资源:装配正常,键在类型与运行时都不存在', async () => {
63
- let ctxInServices;
64
- const server = defineServer({
65
- app: { service: 'resources-disabled', title: 'Resources Disabled', version: '1.0.0' },
66
- resources: createResources,
67
- config: () => ({}),
68
- services: (ctx) => {
69
- // 编译期:`telemetry` 被 `Live<>` 剔除 → 访问必须编译失败
70
- // @ts-expect-error `telemetry: false` 已从资源视图中剔除
71
- void ctx.resources.telemetry;
72
- ctxInServices = ctx;
73
- return {};
74
- },
75
- middlewares: () => [],
76
- routers: (ctx) => {
77
- ctx.app.get('/alive', () => ({ ok: true }));
78
- },
79
- });
80
- const app = await startServer(server, { listen: false });
81
- try {
82
- // 运行时也不存在该键(与类型一致)
83
- const resources = ctxInServices.resources;
84
- expect('telemetry' in resources).toBe(false);
85
- const response = await app.inject({ method: 'GET', url: '/alive' });
86
- expect(response.statusCode).toBe(200);
87
- }
88
- finally {
89
- await app.close();
90
- }
91
- });
92
- it('③ 缺省可选资源:类型为 `T | undefined`,运行时为 undefined(键仍在)', async () => {
93
- const observed = {};
94
- const server = defineServer({
95
- app: { service: 'resources-optional', title: 'Resources Optional', version: '1.0.0' },
96
- resources: createResources,
97
- config: (ctx) => {
98
- expectTypeOf(ctx.resources.queue).toEqualTypeOf();
99
- observed.inConfig = ctx.resources.queue;
100
- return {};
101
- },
102
- services: (ctx) => {
103
- observed.inServices = ctx.resources.queue;
104
- observed.keys = Object.keys(ctx.resources);
105
- return {};
106
- },
107
- middlewares: () => [],
108
- routers: () => undefined,
109
- });
110
- const app = await startServer(server, { listen: false });
111
- try {
112
- expect(observed.inConfig).toBeUndefined();
113
- expect(observed.inServices).toBeUndefined();
114
- expect(observed.keys).toContain('queue');
115
- }
116
- finally {
117
- await app.close();
118
- }
119
- });
120
- it('④ resources 允许异步:返回 Promise<资源包> 时同样是精确类型', async () => {
121
- let domain;
122
- const server = defineServer({
123
- app: { service: 'resources-async', title: 'Resources Async', version: '1.0.0' },
124
- resources: async () => ({
125
- database: { query: (sql) => `async:${sql}` },
126
- }),
127
- config: (ctx) => {
128
- expectTypeOf(ctx.resources.database.query).toEqualTypeOf();
129
- return { domain: ctx.resources.database.query('domain') };
130
- },
131
- services: (ctx) => {
132
- domain = ctx.config.domain;
133
- return {};
134
- },
135
- middlewares: () => [],
136
- routers: (ctx) => {
137
- ctx.app.get('/ping', () => ({ ok: true }));
138
- },
139
- });
140
- const app = await startServer(server, { listen: false });
141
- try {
142
- expect(domain).toBe('async:domain');
143
- }
144
- finally {
145
- await app.close();
146
- }
147
- });
148
- it('⑤ resources 的 disposer 在 services 阶段**之后**执行;阶段内按逆注册序;资源对象自带的 dispose 不被调用', async () => {
149
- const calls = [];
150
- const server = defineServer({
151
- app: { service: 'resources-lifecycle', title: 'Resources Lifecycle', version: '1.0.0' },
152
- resources: (ctx) => {
153
- // 资源对象自带的 `dispose` 不参与生命周期:释放只认 `ctx.disposer.add(...)`
154
- const database = {
155
- dispose: () => {
156
- calls.push('dispose:resource-object');
157
- },
158
- };
159
- ctx.disposer.add(() => {
160
- calls.push('dispose:resource-a');
161
- });
162
- ctx.disposer.add(() => {
163
- calls.push('dispose:resource-b');
164
- });
165
- return { database };
166
- },
167
- config: () => ({}),
168
- services: (ctx) => {
169
- ctx.disposer.add(() => {
170
- calls.push('dispose:services-disposer');
171
- });
172
- return {
173
- databaseClient: {
174
- dispose: () => {
175
- calls.push('dispose:service-unit');
176
- },
177
- },
178
- };
179
- },
180
- middlewares: () => [],
181
- routers: () => undefined,
182
- });
183
- const app = await startServer(server, { listen: false });
184
- await app.close();
185
- expect(calls).toEqual([
186
- 'dispose:services-disposer',
187
- 'dispose:service-unit',
188
- 'dispose:resource-b',
189
- 'dispose:resource-a',
190
- ]);
191
- });
192
- it('⑥ 出 spec 路径同样跑 resources(只构造,不建连),但不 flush 任何 disposer', async () => {
193
- const calls = [];
194
- const server = defineServer({
195
- app: { service: 'resources-openapi', title: 'Resources OpenApi', version: '1.0.0' },
196
- resources: (ctx) => {
197
- calls.push('resources');
198
- ctx.disposer.add(() => {
199
- calls.push('dispose:resources');
200
- });
201
- return {};
202
- },
203
- config: () => ({}),
204
- services: () => ({}),
205
- middlewares: () => [],
206
- routers: () => undefined,
207
- });
208
- await generateOpenApi(server);
209
- expect(calls).toEqual(['resources']);
210
- });
211
- });
212
- /** `ResourcesView` 自证:`Awaited<>` 归一(同步 / 异步两写法等价) */
213
- const assertResourcesViewNormalizes = () => {
214
- expectTypeOf().toEqualTypeOf();
215
- expectTypeOf().toEqualTypeOf();
216
- };
217
- void assertResourcesViewNormalizes;
218
- /** 测试用 env 自证:固定读取约定里的五项 */
219
- const assertTestConfigEnv = () => {
220
- expectTypeOf(createTestConfigEnv().ENV).toEqualTypeOf();
221
- };
222
- void assertTestConfigEnv;
@@ -1 +0,0 @@
1
- {"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../../source/app/shutdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,cAAc,oHAOjB,CAAA;AAEV,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,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":""}