@taicode/common-server 4.0.0-beta.2 → 4.0.0-beta.4

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 (276) 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/core/boot/context.js +106 -0
  13. package/output/core/boot/lifecycle.d.ts +94 -0
  14. package/output/core/boot/lifecycle.d.ts.map +1 -0
  15. package/output/{app → core/boot}/lifecycle.js +45 -14
  16. package/output/core/boot/shutdown.d.ts +30 -0
  17. package/output/core/boot/shutdown.d.ts.map +1 -0
  18. package/output/core/boot/shutdown.js +45 -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 +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/core/types.d.ts +338 -0
  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} +12 -7
  124. package/output/redis/index.d.ts.map +1 -0
  125. package/output/{app/redis.js → redis/index.js} +15 -5
  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 +16 -15
  135. package/LICENSE +0 -21
  136. package/output/app/auth.test.d.ts +0 -2
  137. package/output/app/auth.test.d.ts.map +0 -1
  138. package/output/app/auth.test.js +0 -148
  139. package/output/app/boot.d.ts +0 -39
  140. package/output/app/boot.d.ts.map +0 -1
  141. package/output/app/boot.js +0 -140
  142. package/output/app/config.test.d.ts +0 -2
  143. package/output/app/config.test.d.ts.map +0 -1
  144. package/output/app/config.test.js +0 -132
  145. package/output/app/context.d.ts +0 -70
  146. package/output/app/context.d.ts.map +0 -1
  147. package/output/app/context.js +0 -73
  148. package/output/app/core/cookie.d.ts +0 -25
  149. package/output/app/core/cookie.d.ts.map +0 -1
  150. package/output/app/core/identity.d.ts +0 -12
  151. package/output/app/core/identity.d.ts.map +0 -1
  152. package/output/app/core/index.d.ts +0 -44
  153. package/output/app/core/index.d.ts.map +0 -1
  154. package/output/app/core/index.js +0 -57
  155. package/output/app/core/logging.d.ts +0 -13
  156. package/output/app/core/logging.d.ts.map +0 -1
  157. package/output/app/core/openapi.d.ts +0 -17
  158. package/output/app/core/openapi.d.ts.map +0 -1
  159. package/output/app/core/request-id.d.ts.map +0 -1
  160. package/output/app/core/response.d.ts +0 -41
  161. package/output/app/core/response.d.ts.map +0 -1
  162. package/output/app/define-server.negative-cases.d.ts +0 -40
  163. package/output/app/define-server.negative-cases.d.ts.map +0 -1
  164. package/output/app/define-server.negative-cases.js +0 -268
  165. package/output/app/define-server.test.d.ts +0 -2
  166. package/output/app/define-server.test.d.ts.map +0 -1
  167. package/output/app/define-server.test.js +0 -97
  168. package/output/app/entries.d.ts +0 -29
  169. package/output/app/entries.d.ts.map +0 -1
  170. package/output/app/entries.js +0 -64
  171. package/output/app/env.d.ts +0 -59
  172. package/output/app/env.d.ts.map +0 -1
  173. package/output/app/env.js +0 -66
  174. package/output/app/env.test.js +0 -124
  175. package/output/app/error-contract.test.d.ts +0 -2
  176. package/output/app/error-contract.test.d.ts.map +0 -1
  177. package/output/app/error-contract.test.js +0 -227
  178. package/output/app/errors.d.ts +0 -20
  179. package/output/app/errors.d.ts.map +0 -1
  180. package/output/app/errors.js +0 -51
  181. package/output/app/generate-openapi.test.d.ts +0 -2
  182. package/output/app/generate-openapi.test.d.ts.map +0 -1
  183. package/output/app/generate-openapi.test.js +0 -123
  184. package/output/app/lifecycle-logging.test.d.ts +0 -2
  185. package/output/app/lifecycle-logging.test.d.ts.map +0 -1
  186. package/output/app/lifecycle-logging.test.js +0 -155
  187. package/output/app/lifecycle.d.ts +0 -70
  188. package/output/app/lifecycle.d.ts.map +0 -1
  189. package/output/app/lifecycle.test.d.ts +0 -2
  190. package/output/app/lifecycle.test.d.ts.map +0 -1
  191. package/output/app/lifecycle.test.js +0 -152
  192. package/output/app/middleware/auth.d.ts +0 -71
  193. package/output/app/middleware/auth.d.ts.map +0 -1
  194. package/output/app/middleware/auth.js +0 -190
  195. package/output/app/middleware/context.d.ts +0 -13
  196. package/output/app/middleware/context.d.ts.map +0 -1
  197. package/output/app/middleware/context.js +0 -15
  198. package/output/app/middleware/cors.d.ts +0 -14
  199. package/output/app/middleware/cors.d.ts.map +0 -1
  200. package/output/app/middleware/cors.js +0 -30
  201. package/output/app/middleware/index.d.ts +0 -17
  202. package/output/app/middleware/index.d.ts.map +0 -1
  203. package/output/app/middleware/index.js +0 -16
  204. package/output/app/middleware/rate-limit.d.ts +0 -24
  205. package/output/app/middleware/rate-limit.d.ts.map +0 -1
  206. package/output/app/middleware/rate-limit.js +0 -46
  207. package/output/app/middleware-order.test.d.ts +0 -2
  208. package/output/app/middleware-order.test.d.ts.map +0 -1
  209. package/output/app/middleware-order.test.js +0 -148
  210. package/output/app/permission-operations.d.ts +0 -48
  211. package/output/app/permission-operations.d.ts.map +0 -1
  212. package/output/app/permission-operations.js +0 -77
  213. package/output/app/permission-operations.test.d.ts +0 -2
  214. package/output/app/permission-operations.test.d.ts.map +0 -1
  215. package/output/app/permission-operations.test.js +0 -72
  216. package/output/app/permission-registration.test.d.ts +0 -2
  217. package/output/app/permission-registration.test.d.ts.map +0 -1
  218. package/output/app/permission-registration.test.js +0 -138
  219. package/output/app/redis.d.ts.map +0 -1
  220. package/output/app/registered-operations.test.d.ts +0 -2
  221. package/output/app/registered-operations.test.d.ts.map +0 -1
  222. package/output/app/registered-operations.test.js +0 -57
  223. package/output/app/request-id.test.d.ts +0 -2
  224. package/output/app/request-id.test.d.ts.map +0 -1
  225. package/output/app/request-id.test.js +0 -130
  226. package/output/app/resources.test.d.ts +0 -2
  227. package/output/app/resources.test.d.ts.map +0 -1
  228. package/output/app/resources.test.js +0 -199
  229. package/output/app/shutdown.d.ts +0 -22
  230. package/output/app/shutdown.d.ts.map +0 -1
  231. package/output/app/shutdown.js +0 -30
  232. package/output/app/shutdown.test.d.ts +0 -2
  233. package/output/app/shutdown.test.d.ts.map +0 -1
  234. package/output/app/shutdown.test.js +0 -103
  235. package/output/app/signal.d.ts +0 -12
  236. package/output/app/signal.d.ts.map +0 -1
  237. package/output/app/smoke.test.d.ts +0 -2
  238. package/output/app/smoke.test.d.ts.map +0 -1
  239. package/output/app/smoke.test.js +0 -210
  240. package/output/app/test-support.d.ts +0 -21
  241. package/output/app/test-support.d.ts.map +0 -1
  242. package/output/app/test-support.js +0 -24
  243. package/output/app/type-augment.d.ts.map +0 -1
  244. package/output/app/types.d.ts +0 -228
  245. package/output/app/types.d.ts.map +0 -1
  246. package/output/context/index.d.ts +0 -6
  247. package/output/context/index.d.ts.map +0 -1
  248. package/output/context/index.js +0 -14
  249. package/output/rate-limiter/index.d.ts +0 -5
  250. package/output/rate-limiter/index.d.ts.map +0 -1
  251. package/output/rate-limiter/index.js +0 -4
  252. package/output/rate-limiter/memory-store.d.ts.map +0 -1
  253. package/output/rate-limiter/rate-limiter.d.ts.map +0 -1
  254. package/output/rate-limiter/redis-store.d.ts.map +0 -1
  255. package/output/rate-limiter/redis-store.test.d.ts.map +0 -1
  256. package/output/rate-limiter/types.d.ts.map +0 -1
  257. package/output/redis-cache/index.d.ts +0 -4
  258. package/output/redis-cache/index.d.ts.map +0 -1
  259. package/output/redis-cache/index.js +0 -1
  260. package/output/redis-cache/redis-cache.d.ts.map +0 -1
  261. package/output/redis-cache/redis-cache.test.d.ts +0 -2
  262. package/output/redis-cache/redis-cache.test.d.ts.map +0 -1
  263. package/output/schema/index.d.ts.map +0 -1
  264. /package/output/{app → core}/env.test.d.ts +0 -0
  265. /package/output/{rate-limiter → core/modules/auth-provider}/types.js +0 -0
  266. /package/output/{context → core/modules/context}/index.test.d.ts +0 -0
  267. /package/output/{context → core/modules/context}/index.test.js +0 -0
  268. /package/output/{app → core}/type-augment.js +0 -0
  269. /package/output/{app → core}/types.js +0 -0
  270. /package/output/{rate-limiter/rate-limiter.js → rate-limit/limiter.js} +0 -0
  271. /package/output/{rate-limiter → rate-limit}/memory-store.d.ts +0 -0
  272. /package/output/{rate-limiter → rate-limit}/memory-store.js +0 -0
  273. /package/output/{rate-limiter → rate-limit}/redis-store.d.ts +0 -0
  274. /package/output/{rate-limiter → rate-limit}/redis-store.js +0 -0
  275. /package/output/{rate-limiter → rate-limit}/redis-store.test.d.ts +0 -0
  276. /package/output/{redis-cache/redis-cache.js → redis/cache.js} +0 -0
@@ -1,124 +0,0 @@
1
- import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
- /**
6
- * env 加载契约(设计 §3.1 ①)。
7
- *
8
- * 固定约定:`.env.dev` < `.env` < `.env.local`(外部注入最高),`ENV=test` 追加 `.env.test.local`;
9
- * 固定读取 `ENV` / `API_PORT` / `LOG_LEVEL`,端口**只读 `API_PORT`,绝不回退 `PORT`**。
10
- */
11
- const ENV_KEYS = [
12
- 'LAYER',
13
- 'OVERRIDE',
14
- 'ONLY_DEV',
15
- 'ONLY_ENV',
16
- 'ONLY_LOCAL',
17
- 'EXTERNAL_ONLY',
18
- 'ONLY_TEST',
19
- 'EXPORTED',
20
- 'QUOTED',
21
- 'MULTI',
22
- 'HASHED',
23
- 'ENV',
24
- 'API_PORT',
25
- 'PORT',
26
- 'LOG_LEVEL',
27
- ];
28
- function clearEnvKeys() {
29
- for (const key of ENV_KEYS) {
30
- delete process.env[key];
31
- }
32
- }
33
- /** 重新求值 `env.js`(`EXTERNAL_ENV` 快照在模块首次求值时抓取) */
34
- async function importEnvModule() {
35
- vi.resetModules();
36
- return import('./env.js');
37
- }
38
- describe('env 加载(设计 §3.1 ①)', () => {
39
- let previousCwd;
40
- let directory;
41
- beforeEach(() => {
42
- previousCwd = process.cwd();
43
- directory = mkdtempSync(join(tmpdir(), 'common-app-env-'));
44
- writeFileSync(join(directory, '.env.dev'), 'LAYER=dev\nONLY_DEV=dev\nOVERRIDE=dev\n');
45
- writeFileSync(join(directory, '.env'), 'LAYER=env\nONLY_ENV=env\nOVERRIDE=env\n');
46
- writeFileSync(join(directory, '.env.local'), 'LAYER=local\nONLY_LOCAL=local\nOVERRIDE=local\n');
47
- process.chdir(directory);
48
- clearEnvKeys();
49
- });
50
- afterEach(() => {
51
- process.chdir(previousCwd);
52
- rmSync(directory, { recursive: true, force: true });
53
- clearEnvKeys();
54
- vi.resetModules();
55
- });
56
- it('分层优先级:外部注入 > .env.local > .env > .env.dev', async () => {
57
- process.env.LAYER = 'external';
58
- process.env.EXTERNAL_ONLY = 'external';
59
- const { loadEnvFiles } = await importEnvModule();
60
- loadEnvFiles();
61
- expect(process.env.LAYER).toBe('external');
62
- expect(process.env.EXTERNAL_ONLY).toBe('external');
63
- expect(process.env.OVERRIDE).toBe('local');
64
- expect(process.env.ONLY_DEV).toBe('dev');
65
- expect(process.env.ONLY_ENV).toBe('env');
66
- expect(process.env.ONLY_LOCAL).toBe('local');
67
- });
68
- it('追加层(如 .env.test.local)优先级最高', async () => {
69
- writeFileSync(join(directory, '.env.test.local'), 'OVERRIDE=test-local\nONLY_TEST=1\n');
70
- const { loadEnvFiles } = await importEnvModule();
71
- loadEnvFiles(['.env.test.local']);
72
- expect(process.env.OVERRIDE).toBe('test-local');
73
- expect(process.env.ONLY_TEST).toBe('1');
74
- });
75
- it('缺失的 env 文件不报错,已有层照常生效', async () => {
76
- rmSync(join(directory, '.env.local'));
77
- const { loadEnvFiles } = await importEnvModule();
78
- expect(() => loadEnvFiles()).not.toThrow();
79
- expect(process.env.ONLY_DEV).toBe('dev');
80
- expect(process.env.OVERRIDE).toBe('env');
81
- });
82
- it('dotenv 语义:export 前缀 / 引号 / 转义 / 多行 / 行尾注释(与四家现状逐字一致)', async () => {
83
- writeFileSync(join(directory, '.env.local'), ['export EXPORTED=1', 'QUOTED="a b"', 'MULTI="line1\\nline2"', 'HASHED=value # 行尾注释'].join('\n'));
84
- const { loadEnvFiles } = await importEnvModule();
85
- loadEnvFiles();
86
- expect(process.env.EXPORTED).toBe('1');
87
- expect(process.env.QUOTED).toBe('a b');
88
- expect(process.env.MULTI).toBe('line1\nline2');
89
- expect(process.env.HASHED).toBe('value');
90
- });
91
- it('loadRuntimeEnvFiles:ENV=test 时追加 .env.test.local', async () => {
92
- writeFileSync(join(directory, '.env.dev'), 'LAYER=dev\nOVERRIDE=dev\nENV=test\nAPI_PORT=4321\n');
93
- writeFileSync(join(directory, '.env.test.local'), 'LOG_LEVEL=debug\nONLY_TEST=1\n');
94
- const { loadRuntimeEnvFiles } = await importEnvModule();
95
- const env = loadRuntimeEnvFiles();
96
- expect(env.ENV).toBe('test');
97
- expect(env.API_PORT).toBe('4321');
98
- expect(env.LOG_LEVEL).toBe('debug');
99
- expect(process.env.ONLY_TEST).toBe('1');
100
- });
101
- it('loadRuntimeEnvFiles:ENV 非 test 时不加载 .env.test.local', async () => {
102
- writeFileSync(join(directory, '.env.local'), 'ENV=development\n');
103
- writeFileSync(join(directory, '.env.test.local'), 'LOG_LEVEL=debug\nONLY_TEST=1\n');
104
- const { loadRuntimeEnvFiles } = await importEnvModule();
105
- const env = loadRuntimeEnvFiles();
106
- expect(env.ENV).toBe('development');
107
- expect(env.LOG_LEVEL).toBe('info');
108
- expect(process.env.ONLY_TEST).toBeUndefined();
109
- });
110
- it('固定读取:只认 API_PORT(绝不回退 PORT),LOG_LEVEL 默认 info,ENV 缺省空串', async () => {
111
- process.env.PORT = '9999';
112
- const { resolveConfigEnv } = await importEnvModule();
113
- const env = resolveConfigEnv();
114
- expect(env.API_PORT).toBe('');
115
- expect(env.LOG_LEVEL).toBe('info');
116
- expect(env.ENV).toBe('');
117
- });
118
- it('固有常量:分层顺序 / 测试层 / 默认日志级别', async () => {
119
- const { ENV_FILE_LAYERS, TEST_ENV_FILE_LAYER, DEFAULT_LOG_LEVEL } = await importEnvModule();
120
- expect(ENV_FILE_LAYERS).toEqual(['.env.dev', '.env', '.env.local']);
121
- expect(TEST_ENV_FILE_LAYER).toBe('.env.test.local');
122
- expect(DEFAULT_LOG_LEVEL).toBe('info');
123
- });
124
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=error-contract.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"error-contract.test.d.ts","sourceRoot":"","sources":["../../source/app/error-contract.test.ts"],"names":[],"mappings":""}
@@ -1,227 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { z } from 'zod';
3
- import { defineServer, startServer } from './entries.js';
4
- import { SystemError, UserError } from './errors.js';
5
- /**
6
- * 错误契约——**强制、不可配置**。
7
- *
8
- * 响应信封 `{ status, message, data }`(错误时 `data: null`,`status` 放错误码字符串);
9
- * 映射固定:`UserError`→400(`error.status` 字面量)/ `SystemError`→500 / zod→400 / 未知→500 /
10
- * 未匹配路由→404。错误只给稳定**码**,文案不归框架。
11
- */
12
- /** zod validator 由 core 的 `schema` 统一装配,运行时按 zod 校验(类型上仍是 FastifySchema) */
13
- const validatedQuerySchema = {
14
- querystring: z.object({ count: z.coerce.number().int().min(1) }),
15
- };
16
- function createErrorContractServer() {
17
- return defineServer({
18
- app: { service: 'error-contract', title: 'Error Contract', version: '1.0.0' },
19
- resources: () => ({}),
20
- config: () => ({}),
21
- services: () => ({}),
22
- middlewares: () => [],
23
- routers: (ctx) => {
24
- ctx.app.get('/ok', () => ({ hello: 'world' }));
25
- ctx.app.get('/envelope', () => ({ status: 'CUSTOM', message: 'm', data: 1 }));
26
- ctx.app.get('/user-error', () => {
27
- throw new UserError('INVALID_TOKEN', undefined, 'token 无效');
28
- });
29
- ctx.app.get('/system-error', () => {
30
- throw new SystemError('DATABASE_UNAVAILABLE', undefined, '数据库不可用');
31
- });
32
- ctx.app.get('/unknown-error', () => {
33
- throw new Error('boom');
34
- });
35
- ctx.app.get('/validated', { schema: validatedQuerySchema }, (request) => ({
36
- count: request.query.count,
37
- }));
38
- },
39
- });
40
- }
41
- describe('错误契约', () => {
42
- it('成功信封:{ status: SUCCESS, message: ok, data }', async () => {
43
- const app = await startServer(createErrorContractServer(), { listen: false });
44
- const response = await app.inject({ method: 'GET', url: '/ok' });
45
- expect(response.statusCode).toBe(200);
46
- expect(response.json()).toEqual({ status: 'SUCCESS', message: 'ok', data: { hello: 'world' } });
47
- await app.close();
48
- });
49
- it('已是信封的载荷不被二次包裹', async () => {
50
- const app = await startServer(createErrorContractServer(), { listen: false });
51
- const response = await app.inject({ method: 'GET', url: '/envelope' });
52
- expect(response.statusCode).toBe(200);
53
- expect(response.json()).toEqual({ status: 'CUSTOM', message: 'm', data: 1 });
54
- await app.close();
55
- });
56
- it('UserError → 400(取 `error.status` 字面量),status 放错误码、data 为 null', async () => {
57
- const app = await startServer(createErrorContractServer(), { listen: false });
58
- const response = await app.inject({ method: 'GET', url: '/user-error' });
59
- expect(response.statusCode).toBe(400);
60
- expect(response.json()).toEqual({ status: 'INVALID_TOKEN', message: 'token 无效', data: null });
61
- await app.close();
62
- });
63
- it('SystemError → 500(取 `error.status` 字面量)', async () => {
64
- const app = await startServer(createErrorContractServer(), { listen: false });
65
- const response = await app.inject({ method: 'GET', url: '/system-error' });
66
- expect(response.statusCode).toBe(500);
67
- expect(response.json()).toEqual({ status: 'DATABASE_UNAVAILABLE', message: '数据库不可用', data: null });
68
- await app.close();
69
- });
70
- it('未知错误 → 500 + UNKNOWN_ERROR', async () => {
71
- const app = await startServer(createErrorContractServer(), { listen: false });
72
- const response = await app.inject({ method: 'GET', url: '/unknown-error' });
73
- expect(response.statusCode).toBe(500);
74
- expect(response.json()).toEqual({ status: 'UNKNOWN_ERROR', message: 'boom', data: null });
75
- await app.close();
76
- });
77
- it('zod 校验失败 → 400(INVALID_INPUT);通过校验 → 200', async () => {
78
- const app = await startServer(createErrorContractServer(), { listen: false });
79
- const invalid = await app.inject({ method: 'GET', url: '/validated?count=0' });
80
- expect(invalid.statusCode).toBe(400);
81
- const invalidBody = invalid.json();
82
- expect(invalidBody.status).toBe('INVALID_INPUT');
83
- expect(invalidBody.data).toBeNull();
84
- expect(invalidBody.message).toContain('count');
85
- const valid = await app.inject({ method: 'GET', url: '/validated?count=3' });
86
- expect(valid.statusCode).toBe(200);
87
- expect(valid.json()).toEqual({ status: 'SUCCESS', message: 'ok', data: { count: 3 } });
88
- await app.close();
89
- });
90
- it('未匹配路由 → 404 + NOT_IMPLEMENTED', async () => {
91
- const app = await startServer(createErrorContractServer(), { listen: false });
92
- const response = await app.inject({ method: 'GET', url: '/missing' });
93
- expect(response.statusCode).toBe(404);
94
- const body = response.json();
95
- expect(body.status).toBe('NOT_IMPLEMENTED');
96
- expect(body.data).toBeNull();
97
- expect(body.message).toContain('GET /missing');
98
- await app.close();
99
- });
100
- it('资源注册的 `ctx.registerClassifyError` 分类器生效(→500,错误码 = 分类器返回的 SystemError.type)', async () => {
101
- const server = defineServer({
102
- app: { service: 'error-contract-database', title: 'Error Contract Database', version: '1.0.0' },
103
- resources: (ctx) => {
104
- ctx.registerClassifyError((error) => error instanceof Error && error.message === 'pg down'
105
- ? new SystemError('DATABASE_ERROR', { operation: 'query' }, 'pg down')
106
- : null);
107
- return {};
108
- },
109
- config: () => ({}),
110
- services: () => ({}),
111
- middlewares: () => [],
112
- routers: (ctx) => {
113
- ctx.app.get('/database-down', () => {
114
- throw new Error('pg down');
115
- });
116
- },
117
- });
118
- const app = await startServer(server, { listen: false });
119
- const response = await app.inject({ method: 'GET', url: '/database-down' });
120
- expect(response.statusCode).toBe(500);
121
- expect(response.json()).toEqual({ status: 'DATABASE_ERROR', message: 'pg down', data: null });
122
- await app.close();
123
- });
124
- it('跨资源注册:每个资源各注册自己的分类器,各自认领域内错误(顺序短路),都不认领则兜底', async () => {
125
- const calls = [];
126
- const server = defineServer({
127
- app: { service: 'error-contract-cross-resource', title: 'Error Contract Cross Resource', version: '1.0.0' },
128
- resources: (ctx) => {
129
- // prisma 资源:只认领自己域内的 Prisma 错误(认领不了返回 null)
130
- ctx.registerClassifyError((error) => {
131
- calls.push('prisma');
132
- return error instanceof Error && error.name === 'PrismaError'
133
- ? new SystemError('DATABASE_ERROR', { operation: 'query' }, '数据库错误')
134
- : null;
135
- });
136
- // cache 资源:只认领自己域内的缓存错误
137
- ctx.registerClassifyError((error) => {
138
- calls.push('cache');
139
- return error instanceof Error && error.name === 'CacheError'
140
- ? new SystemError('CACHE_ERROR', undefined, '缓存错误')
141
- : null;
142
- });
143
- return { prisma: {}, cache: {} };
144
- },
145
- config: () => ({}),
146
- services: () => ({}),
147
- middlewares: () => [],
148
- routers: (ctx) => {
149
- ctx.app.get('/prisma-error', () => {
150
- throw Object.assign(new Error('db down'), { name: 'PrismaError' });
151
- });
152
- ctx.app.get('/cache-error', () => {
153
- throw Object.assign(new Error('cache down'), { name: 'CacheError' });
154
- });
155
- ctx.app.get('/unclaimed-error', () => {
156
- throw new Error('someone else error');
157
- });
158
- },
159
- });
160
- const app = await startServer(server, { listen: false });
161
- // ① prisma 域:prisma 分类器认领,错误码正确;首个命中即短路(不再问 cache)
162
- calls.length = 0;
163
- const prisma = await app.inject({ method: 'GET', url: '/prisma-error' });
164
- expect(prisma.statusCode).toBe(500);
165
- expect(prisma.json()).toEqual({ status: 'DATABASE_ERROR', message: '数据库错误', data: null });
166
- expect(calls).toEqual(['prisma']);
167
- // ② cache 域:prisma 返回 null → 交给 cache 认领,错误码正确
168
- calls.length = 0;
169
- const cache = await app.inject({ method: 'GET', url: '/cache-error' });
170
- expect(cache.statusCode).toBe(500);
171
- expect(cache.json()).toEqual({ status: 'CACHE_ERROR', message: '缓存错误', data: null });
172
- expect(calls).toEqual(['prisma', 'cache']);
173
- // ③ 都不认领 → 落 `UNKNOWN_ERROR` 兜底
174
- calls.length = 0;
175
- const unclaimed = await app.inject({ method: 'GET', url: '/unclaimed-error' });
176
- expect(unclaimed.statusCode).toBe(500);
177
- expect(unclaimed.json()).toEqual({ status: 'UNKNOWN_ERROR', message: 'someone else error', data: null });
178
- expect(calls).toEqual(['prisma', 'cache']);
179
- await app.close();
180
- });
181
- it('一个资源都不注册分类器 → 资源错误分支不生效,落 `UNKNOWN_ERROR` 兜底', async () => {
182
- const server = defineServer({
183
- app: { service: 'error-contract-none', title: 'Error Contract None', version: '1.0.0' },
184
- resources: () => ({}),
185
- config: () => ({}),
186
- services: () => ({}),
187
- middlewares: () => [],
188
- routers: (ctx) => {
189
- ctx.app.get('/boom', () => {
190
- throw new Error('boom');
191
- });
192
- },
193
- });
194
- const app = await startServer(server, { listen: false });
195
- const response = await app.inject({ method: 'GET', url: '/boom' });
196
- expect(response.statusCode).toBe(500);
197
- expect(response.json()).toEqual({ status: 'UNKNOWN_ERROR', message: 'boom', data: null });
198
- await app.close();
199
- });
200
- it('注册的分类器不影响既有分支:`UserError` 仍走 400(分类器只在资源错误分支求值)', async () => {
201
- let classified = 0;
202
- const server = defineServer({
203
- app: { service: 'error-contract-user', title: 'Error Contract User', version: '1.0.0' },
204
- resources: (ctx) => {
205
- ctx.registerClassifyError((error) => {
206
- classified += 1;
207
- return error instanceof Error ? new SystemError('SHOULD_NOT_HAPPEN', undefined, 'x') : null;
208
- });
209
- return {};
210
- },
211
- config: () => ({}),
212
- services: () => ({}),
213
- middlewares: () => [],
214
- routers: (ctx) => {
215
- ctx.app.get('/user-error-2', () => {
216
- throw new UserError('INVALID_TOKEN', undefined, 'token 无效');
217
- });
218
- },
219
- });
220
- const app = await startServer(server, { listen: false });
221
- const response = await app.inject({ method: 'GET', url: '/user-error-2' });
222
- expect(response.statusCode).toBe(400);
223
- expect(response.json()).toEqual({ status: 'INVALID_TOKEN', message: 'token 无效', data: null });
224
- expect(classified).toBe(0); // `UserError` 在分支①命中,分类器根本没被求值
225
- await app.close();
226
- });
227
- });
@@ -1,20 +0,0 @@
1
- import type { ErrorContext } from '@taicode/common-base';
2
- export declare class UserError extends Error {
3
- readonly type: string;
4
- readonly context?: ErrorContext | undefined;
5
- /** 类型守卫(`Symbol.for` 品牌,不用 `instanceof`) */
6
- static is(value: unknown): value is UserError;
7
- /** 固定 HTTP 状态(字面量类型) */
8
- readonly status: 400;
9
- constructor(type: string, context?: ErrorContext | undefined, message?: string);
10
- }
11
- export declare class SystemError extends Error {
12
- readonly type: string;
13
- readonly context?: ErrorContext | undefined;
14
- /** 类型守卫(`Symbol.for` 品牌,不用 `instanceof`) */
15
- static is(value: unknown): value is SystemError;
16
- /** 固定 HTTP 状态(字面量类型) */
17
- readonly status: 500;
18
- constructor(type: string, context?: ErrorContext | undefined, message?: string);
19
- }
20
- //# sourceMappingURL=errors.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../source/app/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAsBxD,qBAAa,SAAU,SAAQ,KAAK;aASC,IAAI,EAAE,MAAM;aAAkB,OAAO,CAAC,EAAE,YAAY;IARvF,4CAA4C;IAC5C,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS;IAI7C,wBAAwB;IACxB,QAAQ,CAAC,MAAM,EAAG,GAAG,CAAS;gBAEK,IAAI,EAAE,MAAM,EAAkB,OAAO,CAAC,EAAE,YAAY,YAAA,EAAE,OAAO,SAAK;CAKtG;AAED,qBAAa,WAAY,SAAQ,KAAK;aASD,IAAI,EAAE,MAAM;aAAkB,OAAO,CAAC,EAAE,YAAY;IARvF,4CAA4C;IAC5C,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW;IAI/C,wBAAwB;IACxB,QAAQ,CAAC,MAAM,EAAG,GAAG,CAAS;gBAEK,IAAI,EAAE,MAAM,EAAkB,OAAO,CAAC,EAAE,YAAY,YAAA,EAAE,OAAO,SAAK;CAKtG"}
@@ -1,51 +0,0 @@
1
- /**
2
- * 错误契约(N2 §2.12)。
3
- *
4
- * 框架直接提供并导出 `UserError` / `SystemError`:
5
- * - `status` 是**字面量**(`400` / `500`)—— 映射表写成
6
- * `Record<UserError['status'] | SystemError['status'], number>` 时若改错直接编译失败;
7
- * - `is()` 是**类型守卫**,走 `Symbol.for` 品牌(**不用 `instanceof`**:跨包 / 编译后 / 原型链丢失
8
- * 都仍然可靠);品牌与 `@taicode/common-base` 同名,故基座抛出的同类错误同样被识别;
9
- * - 业务只抛这两个错误,状态码映射由框架固定(`UserError`→400 / `SystemError`→500)。
10
- *
11
- * 构造签名保持 `(code, context?, message?)`。
12
- */
13
- const USER_ERROR_BRAND = Symbol.for('@taicode/common/UserError');
14
- const SYSTEM_ERROR_BRAND = Symbol.for('@taicode/common/SystemError');
15
- function hasBrand(value, brand) {
16
- return typeof value === 'object' && value !== null && value[brand] === true;
17
- }
18
- export class UserError extends Error {
19
- type;
20
- context;
21
- /** 类型守卫(`Symbol.for` 品牌,不用 `instanceof`) */
22
- static is(value) {
23
- return hasBrand(value, USER_ERROR_BRAND);
24
- }
25
- /** 固定 HTTP 状态(字面量类型) */
26
- status = 400;
27
- constructor(type, context, message = '') {
28
- super(message, context);
29
- this.type = type;
30
- this.context = context;
31
- this.name = 'UserError';
32
- Object.defineProperty(this, USER_ERROR_BRAND, { value: true });
33
- }
34
- }
35
- export class SystemError extends Error {
36
- type;
37
- context;
38
- /** 类型守卫(`Symbol.for` 品牌,不用 `instanceof`) */
39
- static is(value) {
40
- return hasBrand(value, SYSTEM_ERROR_BRAND);
41
- }
42
- /** 固定 HTTP 状态(字面量类型) */
43
- status = 500;
44
- constructor(type, context, message = '') {
45
- super(message, context);
46
- this.type = type;
47
- this.context = context;
48
- this.name = 'SystemError';
49
- Object.defineProperty(this, SYSTEM_ERROR_BRAND, { value: true });
50
- }
51
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=generate-openapi.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate-openapi.test.d.ts","sourceRoot":"","sources":["../../source/app/generate-openapi.test.ts"],"names":[],"mappings":""}
@@ -1,123 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { z } from 'zod';
3
- import { defineServer, generateOpenApi, startServer } from './entries.js';
4
- import { createRedis } from './redis.js';
5
- /**
6
- * `generateOpenApi()` 红线。
7
- *
8
- * 全程**不得要求配置有效**:不跑 `config` 槽位、不要求 `DATABASE_URL` / `REDIS_URL` 有效、不建连;
9
- * `resources`(只构造不建连)/ `services` / 中间件注册 / 路由注册照常执行。
10
- * 跳过集:`config` / `initLifecycleUnits` / `listen`。
11
- */
12
- const routeSchema = {
13
- querystring: z.object({ name: z.string().optional() }),
14
- };
15
- describe('generateOpenApi 红线', () => {
16
- it('不跑 config、不要求 DATABASE_URL / REDIS_URL,能出 spec', async () => {
17
- delete process.env.DATABASE_URL;
18
- delete process.env.REDIS_URL;
19
- let configCalls = 0;
20
- const server = defineServer({
21
- app: { service: 'openapi-test', title: 'OpenAPI Test', version: '1.2.3' },
22
- resources: () => ({ database: { connected: false } }),
23
- config: () => {
24
- configCalls += 1;
25
- return {};
26
- },
27
- services: () => ({}),
28
- middlewares: () => [],
29
- routers: (ctx) => {
30
- ctx.app.get('/widgets', { schema: routeSchema }, () => []);
31
- },
32
- });
33
- const spec = (await generateOpenApi(server));
34
- expect(configCalls).toBe(0);
35
- expect(spec.openapi).toBe('3.0.3');
36
- expect(spec.info).toMatchObject({ title: 'OpenAPI Test', version: '1.2.3' });
37
- // 身份块只有 service / title / version:`info.description` 不再由框架填
38
- expect(spec.info?.description).toBeUndefined();
39
- expect(spec.servers).toEqual([{ url: '/' }]);
40
- expect(Object.keys(spec.paths ?? {})).toContain('/widgets');
41
- expect(Object.keys(spec.components?.securitySchemes ?? {})).toContain('bearerAuth');
42
- });
43
- it('跳过集:不跑 config / initLifecycleUnits(但跑 resources / services / 中间件注册 / routers)', async () => {
44
- const calls = [];
45
- const server = defineServer({
46
- app: { service: 'openapi-skip-set', title: 'OpenAPI Skip Set', version: '1.0.0' },
47
- resources: () => {
48
- calls.push('resources');
49
- return {};
50
- },
51
- config: () => {
52
- calls.push('config');
53
- return {};
54
- },
55
- services: () => {
56
- calls.push('services');
57
- return {
58
- a: {
59
- init: () => {
60
- calls.push('init:a');
61
- },
62
- },
63
- };
64
- },
65
- middlewares: () => [
66
- {
67
- name: 'probe',
68
- register: () => {
69
- calls.push('register:probe');
70
- },
71
- },
72
- ],
73
- routers: () => {
74
- calls.push('routers');
75
- },
76
- });
77
- await generateOpenApi(server);
78
- expect(calls).toEqual(['resources', 'services', 'register:probe', 'routers']);
79
- });
80
- it('REDIS_URL 存在时仍不建连(惰性包装)', async () => {
81
- process.env.REDIS_URL = 'redis://127.0.0.1:1/0';
82
- try {
83
- const server = defineServer({
84
- app: { service: 'openapi-redis-lazy', title: 'Redis Lazy', version: '1.0.0' },
85
- // resources 里照常构造 Redis 句柄(惰性包装:不建连、不 ping)
86
- resources: (ctx) => ({ redis: createRedis(ctx.env, ctx.log) }),
87
- config: () => ({}),
88
- services: () => ({}),
89
- middlewares: () => [],
90
- routers: () => undefined,
91
- });
92
- const spec = (await generateOpenApi(server));
93
- expect(spec.openapi).toBe('3.0.3');
94
- }
95
- finally {
96
- delete process.env.REDIS_URL;
97
- }
98
- });
99
- });
100
- describe('startServer 与 generateOpenApi 共用同一份定义', () => {
101
- it('startServer(server, { listen: false }) 返回原生实例、可 inject,且会跑 config', async () => {
102
- let configCalls = 0;
103
- const server = defineServer({
104
- app: { service: 'openapi-shared-definition', title: 'Shared Definition', version: '1.0.0' },
105
- resources: () => ({}),
106
- config: () => {
107
- configCalls += 1;
108
- return {};
109
- },
110
- services: () => ({}),
111
- middlewares: () => [],
112
- routers: (ctx) => {
113
- ctx.app.get('/ping', () => ({ pong: true }));
114
- },
115
- });
116
- const app = await startServer(server, { listen: false });
117
- expect(typeof app.inject).toBe('function');
118
- const response = await app.inject({ method: 'GET', url: '/ping' });
119
- expect(response.statusCode).toBe(200);
120
- expect(configCalls).toBe(1);
121
- await app.close();
122
- });
123
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=lifecycle-logging.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lifecycle-logging.test.d.ts","sourceRoot":"","sources":["../../source/app/lifecycle-logging.test.ts"],"names":[],"mappings":""}