@gravito/cosmos 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +105 -45
  3. package/README.zh-TW.md +102 -22
  4. package/build.ts +35 -0
  5. package/docs/plans/01-performance.md +187 -0
  6. package/docs/plans/02-architecture.md +309 -0
  7. package/docs/plans/03-api-enhancement.md +345 -0
  8. package/docs/plans/04-testing.md +431 -0
  9. package/docs/plans/README.md +47 -0
  10. package/ion/src/index.js +1179 -1138
  11. package/package.json +18 -5
  12. package/src/I18nService.ts +657 -94
  13. package/src/index.ts +51 -6
  14. package/src/loader.ts +45 -12
  15. package/tests/helpers/factory.ts +41 -0
  16. package/tests/performance/translate.bench.ts +27 -0
  17. package/tests/unit/api.test.ts +37 -0
  18. package/tests/unit/detector.test.ts +70 -0
  19. package/tests/unit/edge.test.ts +100 -0
  20. package/tests/unit/fallback.test.ts +66 -0
  21. package/tests/unit/lazy.test.ts +35 -0
  22. package/tests/{loader.test.ts → unit/loader.test.ts} +1 -1
  23. package/tests/{manager.test.ts → unit/manager.test.ts} +1 -2
  24. package/tests/unit/plural.test.ts +58 -0
  25. package/tests/{service.test.ts → unit/service.test.ts} +7 -2
  26. package/tsconfig.json +12 -24
  27. package/dist/core/src/Application.d.ts +0 -185
  28. package/dist/core/src/ConfigManager.d.ts +0 -21
  29. package/dist/core/src/Container.d.ts +0 -38
  30. package/dist/core/src/Event.d.ts +0 -5
  31. package/dist/core/src/EventManager.d.ts +0 -123
  32. package/dist/core/src/GlobalErrorHandlers.d.ts +0 -31
  33. package/dist/core/src/GravitoServer.d.ts +0 -20
  34. package/dist/core/src/HookManager.d.ts +0 -70
  35. package/dist/core/src/Listener.d.ts +0 -4
  36. package/dist/core/src/Logger.d.ts +0 -20
  37. package/dist/core/src/PlanetCore.d.ts +0 -207
  38. package/dist/core/src/Route.d.ts +0 -25
  39. package/dist/core/src/Router.d.ts +0 -232
  40. package/dist/core/src/ServiceProvider.d.ts +0 -150
  41. package/dist/core/src/adapters/PhotonAdapter.d.ts +0 -142
  42. package/dist/core/src/adapters/bun/BunContext.d.ts +0 -36
  43. package/dist/core/src/adapters/bun/BunNativeAdapter.d.ts +0 -21
  44. package/dist/core/src/adapters/bun/BunRequest.d.ts +0 -27
  45. package/dist/core/src/adapters/bun/RadixNode.d.ts +0 -15
  46. package/dist/core/src/adapters/bun/RadixRouter.d.ts +0 -31
  47. package/dist/core/src/adapters/bun/types.d.ts +0 -20
  48. package/dist/core/src/adapters/types.d.ts +0 -186
  49. package/dist/core/src/engine/AOTRouter.d.ts +0 -117
  50. package/dist/core/src/engine/FastContext.d.ts +0 -34
  51. package/dist/core/src/engine/Gravito.d.ts +0 -191
  52. package/dist/core/src/engine/MinimalContext.d.ts +0 -36
  53. package/dist/core/src/engine/analyzer.d.ts +0 -21
  54. package/dist/core/src/engine/index.d.ts +0 -26
  55. package/dist/core/src/engine/path.d.ts +0 -26
  56. package/dist/core/src/engine/pool.d.ts +0 -83
  57. package/dist/core/src/engine/types.d.ts +0 -107
  58. package/dist/core/src/exceptions/AuthenticationException.d.ts +0 -4
  59. package/dist/core/src/exceptions/AuthorizationException.d.ts +0 -4
  60. package/dist/core/src/exceptions/GravitoException.d.ts +0 -15
  61. package/dist/core/src/exceptions/HttpException.d.ts +0 -5
  62. package/dist/core/src/exceptions/ModelNotFoundException.d.ts +0 -6
  63. package/dist/core/src/exceptions/ValidationException.d.ts +0 -14
  64. package/dist/core/src/exceptions/index.d.ts +0 -6
  65. package/dist/core/src/helpers/Arr.d.ts +0 -14
  66. package/dist/core/src/helpers/Str.d.ts +0 -18
  67. package/dist/core/src/helpers/data.d.ts +0 -5
  68. package/dist/core/src/helpers/errors.d.ts +0 -12
  69. package/dist/core/src/helpers/response.d.ts +0 -17
  70. package/dist/core/src/helpers.d.ts +0 -38
  71. package/dist/core/src/http/CookieJar.d.ts +0 -37
  72. package/dist/core/src/http/middleware/BodySizeLimit.d.ts +0 -6
  73. package/dist/core/src/http/middleware/Cors.d.ts +0 -12
  74. package/dist/core/src/http/middleware/Csrf.d.ts +0 -11
  75. package/dist/core/src/http/middleware/HeaderTokenGate.d.ts +0 -11
  76. package/dist/core/src/http/middleware/SecurityHeaders.d.ts +0 -17
  77. package/dist/core/src/http/middleware/ThrottleRequests.d.ts +0 -12
  78. package/dist/core/src/http/types.d.ts +0 -312
  79. package/dist/core/src/index.d.ts +0 -60
  80. package/dist/core/src/runtime.d.ts +0 -63
  81. package/dist/core/src/security/Encrypter.d.ts +0 -24
  82. package/dist/core/src/security/Hasher.d.ts +0 -29
  83. package/dist/core/src/testing/HttpTester.d.ts +0 -38
  84. package/dist/core/src/testing/TestResponse.d.ts +0 -78
  85. package/dist/core/src/testing/index.d.ts +0 -2
  86. package/dist/core/src/types/events.d.ts +0 -94
  87. package/dist/cosmos/src/I18nService.d.ts +0 -144
  88. package/dist/cosmos/src/index.d.ts +0 -21
  89. package/dist/cosmos/src/loader.d.ts +0 -11
  90. package/dist/index.js +0 -168
  91. package/dist/photon/src/index.d.ts +0 -2
  92. package/dist/src/index.js +0 -173
@@ -0,0 +1,431 @@
1
+ # Phase 4: 測試與品質計劃
2
+
3
+ > 優先級: P2 | 預估影響: 中
4
+
5
+ ## 現況分析
6
+
7
+ ### 當前測試覆蓋
8
+
9
+ | 檔案 | 測試檔案 | 涵蓋內容 |
10
+ |------|----------|----------|
11
+ | I18nService.ts | manager.test.ts, service.test.ts | 基本翻譯、回退、參數替換 |
12
+ | loader.ts | loader.test.ts | 檔案載入、錯誤處理 |
13
+ | index.ts | service.test.ts | OrbitCosmos 安裝 |
14
+
15
+ ### 識別缺口
16
+
17
+ 1. **缺少效能測試**
18
+ 2. **缺少邊界條件測試**
19
+ 3. **缺少整合測試**
20
+ 4. **缺少壓力測試**
21
+ 5. **無 mutation 測試**
22
+
23
+ ## 優化方案
24
+
25
+ ### 4.1 測試分類與架構
26
+
27
+ ```
28
+ tests/
29
+ ├── unit/ # 單元測試
30
+ │ ├── manager.test.ts # I18nManager
31
+ │ ├── instance.test.ts # I18nInstance
32
+ │ ├── loader.test.ts # 翻譯載入
33
+ │ ├── cache.test.ts # 快取機制 (新增)
34
+ │ ├── plural.test.ts # 複數形式 (新增)
35
+ │ └── detector.test.ts # 語言檢測 (新增)
36
+ ├── integration/ # 整合測試 (新增)
37
+ │ ├── middleware.test.ts # 中間件整合
38
+ │ ├── orbit.test.ts # OrbitCosmos 完整流程
39
+ │ └── namespace.test.ts # 命名空間載入
40
+ ├── performance/ # 效能測試 (新增)
41
+ │ ├── translate.bench.ts # 翻譯效能
42
+ │ ├── cache.bench.ts # 快取效能
43
+ │ └── load.bench.ts # 載入效能
44
+ └── e2e/ # 端對端測試 (新增)
45
+ └── full-flow.test.ts # 完整使用流程
46
+ ```
47
+
48
+ ### 4.2 單元測試強化
49
+
50
+ #### 邊界條件測試
51
+
52
+ ```typescript
53
+ // tests/unit/manager.test.ts
54
+
55
+ describe('I18nManager - Edge Cases', () => {
56
+ describe('Empty Translations', () => {
57
+ it('should handle empty translation object', () => {
58
+ const manager = new I18nManager({
59
+ defaultLocale: 'en',
60
+ supportedLocales: ['en'],
61
+ translations: {}
62
+ })
63
+ expect(manager.translate('en', 'any.key')).toBe('any.key')
64
+ })
65
+
66
+ it('should handle empty string translation', () => {
67
+ const manager = new I18nManager({
68
+ defaultLocale: 'en',
69
+ supportedLocales: ['en'],
70
+ translations: { en: { empty: '' } }
71
+ })
72
+ expect(manager.translate('en', 'empty')).toBe('')
73
+ })
74
+ })
75
+
76
+ describe('Special Characters', () => {
77
+ it('should handle keys with special characters', () => {
78
+ const manager = new I18nManager({
79
+ defaultLocale: 'en',
80
+ supportedLocales: ['en'],
81
+ translations: { en: { 'key.with.dots': 'value' } }
82
+ })
83
+ // 注意:這會被解析為嵌套鍵
84
+ expect(manager.translate('en', 'key.with.dots')).toBe('value')
85
+ })
86
+
87
+ it('should handle unicode keys', () => {
88
+ const manager = new I18nManager({
89
+ defaultLocale: 'zh-TW',
90
+ supportedLocales: ['zh-TW'],
91
+ translations: { 'zh-TW': { '歡迎': '歡迎訊息' } }
92
+ })
93
+ expect(manager.translate('zh-TW', '歡迎')).toBe('歡迎訊息')
94
+ })
95
+
96
+ it('should handle HTML in translations', () => {
97
+ const manager = new I18nManager({
98
+ defaultLocale: 'en',
99
+ supportedLocales: ['en'],
100
+ translations: { en: { html: '<strong>Bold</strong>' } }
101
+ })
102
+ expect(manager.translate('en', 'html')).toBe('<strong>Bold</strong>')
103
+ })
104
+ })
105
+
106
+ describe('Deep Nesting', () => {
107
+ it('should handle deeply nested keys (10 levels)', () => {
108
+ const translations = {
109
+ en: {
110
+ l1: { l2: { l3: { l4: { l5: { l6: { l7: { l8: { l9: { l10: 'deep' } } } } } } } } }
111
+ }
112
+ }
113
+ const manager = new I18nManager({
114
+ defaultLocale: 'en',
115
+ supportedLocales: ['en'],
116
+ translations
117
+ })
118
+ expect(manager.translate('en', 'l1.l2.l3.l4.l5.l6.l7.l8.l9.l10')).toBe('deep')
119
+ })
120
+ })
121
+
122
+ describe('Parameter Replacement', () => {
123
+ it('should handle missing parameters', () => {
124
+ const manager = new I18nManager({
125
+ defaultLocale: 'en',
126
+ supportedLocales: ['en'],
127
+ translations: { en: { greeting: 'Hello :name!' } }
128
+ })
129
+ expect(manager.translate('en', 'greeting', {})).toBe('Hello :name!')
130
+ })
131
+
132
+ it('should handle extra parameters', () => {
133
+ const manager = new I18nManager({
134
+ defaultLocale: 'en',
135
+ supportedLocales: ['en'],
136
+ translations: { en: { greeting: 'Hello :name!' } }
137
+ })
138
+ expect(manager.translate('en', 'greeting', { name: 'Carl', extra: 'ignored' }))
139
+ .toBe('Hello Carl!')
140
+ })
141
+
142
+ it('should handle parameter with special regex characters', () => {
143
+ const manager = new I18nManager({
144
+ defaultLocale: 'en',
145
+ supportedLocales: ['en'],
146
+ translations: { en: { msg: 'Value: :value' } }
147
+ })
148
+ expect(manager.translate('en', 'msg', { value: '$100.00' }))
149
+ .toBe('Value: $100.00')
150
+ })
151
+ })
152
+ })
153
+ ```
154
+
155
+ ### 4.3 效能測試套件
156
+
157
+ ```typescript
158
+ // tests/performance/translate.bench.ts
159
+ import { bench, run } from 'mitata'
160
+
161
+ const manager = new I18nManager({
162
+ defaultLocale: 'en',
163
+ supportedLocales: ['en', 'zh-TW'],
164
+ translations: generateLargeTranslations()
165
+ })
166
+
167
+ bench('Simple translation (cache miss)', () => {
168
+ manager.clearCache()
169
+ manager.translate('en', 'common.welcome')
170
+ })
171
+
172
+ bench('Simple translation (cache hit)', () => {
173
+ manager.translate('en', 'common.welcome')
174
+ })
175
+
176
+ bench('Nested translation (5 levels)', () => {
177
+ manager.translate('en', 'a.b.c.d.e')
178
+ })
179
+
180
+ bench('Translation with 5 parameters', () => {
181
+ manager.translate('en', 'message', {
182
+ p1: 'v1', p2: 'v2', p3: 'v3', p4: 'v4', p5: 'v5'
183
+ })
184
+ })
185
+
186
+ bench('Batch translation (100 keys)', () => {
187
+ const keys = Array.from({ length: 100 }, (_, i) => `key${i}`)
188
+ manager.tMany(keys)
189
+ })
190
+
191
+ bench('Fallback chain (3 levels)', () => {
192
+ manager.translate('zh-CN', 'only.in.english')
193
+ })
194
+
195
+ await run()
196
+ ```
197
+
198
+ ### 4.4 整合測試
199
+
200
+ ```typescript
201
+ // tests/integration/middleware.test.ts
202
+
203
+ describe('Locale Middleware Integration', () => {
204
+ let app: Hono
205
+ let core: PlanetCore
206
+
207
+ beforeEach(() => {
208
+ core = new PlanetCore()
209
+ const cosmos = new OrbitCosmos({
210
+ defaultLocale: 'en',
211
+ supportedLocales: ['en', 'zh-TW', 'ja'],
212
+ translations: {
213
+ en: { welcome: 'Welcome' },
214
+ 'zh-TW': { welcome: '歡迎' },
215
+ ja: { welcome: 'ようこそ' }
216
+ }
217
+ })
218
+ core.addOrbit(cosmos)
219
+ app = core.app
220
+
221
+ app.get('/:locale/test', (c) => {
222
+ const i18n = c.get('i18n')
223
+ return c.json({ message: i18n.t('welcome'), locale: i18n.locale })
224
+ })
225
+ })
226
+
227
+ it('should detect locale from route parameter', async () => {
228
+ const res = await app.request('/zh-TW/test')
229
+ const data = await res.json()
230
+ expect(data).toEqual({ message: '歡迎', locale: 'zh-TW' })
231
+ })
232
+
233
+ it('should detect locale from query parameter', async () => {
234
+ const res = await app.request('/en/test?lang=ja')
235
+ // 注意:路由參數優先級更高
236
+ const data = await res.json()
237
+ expect(data.locale).toBe('en')
238
+ })
239
+
240
+ it('should detect locale from Accept-Language header', async () => {
241
+ const res = await app.request('/test', {
242
+ headers: { 'Accept-Language': 'ja-JP,ja;q=0.9,en;q=0.8' }
243
+ })
244
+ const data = await res.json()
245
+ expect(data.locale).toBe('ja')
246
+ })
247
+
248
+ it('should fallback to default locale for unsupported locale', async () => {
249
+ const res = await app.request('/fr/test')
250
+ const data = await res.json()
251
+ expect(data.locale).toBe('en')
252
+ })
253
+ })
254
+ ```
255
+
256
+ ### 4.5 Mutation 測試
257
+
258
+ 使用 Stryker Mutator 進行變異測試:
259
+
260
+ ```json
261
+ // stryker.conf.json
262
+ {
263
+ "packageManager": "bun",
264
+ "testRunner": "command",
265
+ "commandRunner": {
266
+ "command": "bun test"
267
+ },
268
+ "mutate": [
269
+ "src/**/*.ts",
270
+ "!src/**/*.d.ts"
271
+ ],
272
+ "reporters": ["html", "progress"],
273
+ "thresholds": {
274
+ "high": 80,
275
+ "low": 60,
276
+ "break": 50
277
+ }
278
+ }
279
+ ```
280
+
281
+ **目標變異分數**: 70%+
282
+
283
+ ### 4.6 測試輔助工具
284
+
285
+ ```typescript
286
+ // tests/helpers/factory.ts
287
+
288
+ export function createTestManager(overrides?: Partial<I18nConfig>): I18nManager {
289
+ return new I18nManager({
290
+ defaultLocale: 'en',
291
+ supportedLocales: ['en', 'zh-TW'],
292
+ translations: {
293
+ en: {
294
+ common: {
295
+ welcome: 'Welcome',
296
+ goodbye: 'Goodbye'
297
+ }
298
+ },
299
+ 'zh-TW': {
300
+ common: {
301
+ welcome: '歡迎',
302
+ goodbye: '再見'
303
+ }
304
+ }
305
+ },
306
+ ...overrides
307
+ })
308
+ }
309
+
310
+ export function generateLargeTranslations(
311
+ locales: number = 5,
312
+ keys: number = 1000
313
+ ): Record<string, TranslationMap> {
314
+ const result: Record<string, TranslationMap> = {}
315
+
316
+ for (let l = 0; l < locales; l++) {
317
+ const locale = `locale${l}`
318
+ result[locale] = {}
319
+
320
+ for (let k = 0; k < keys; k++) {
321
+ result[locale][`key${k}`] = `Translation ${k} for ${locale}`
322
+ }
323
+ }
324
+
325
+ return result
326
+ }
327
+
328
+ // tests/helpers/mock.ts
329
+
330
+ export function mockConsole() {
331
+ const original = {
332
+ log: console.log,
333
+ warn: console.warn,
334
+ error: console.error
335
+ }
336
+
337
+ const calls = {
338
+ log: [] as unknown[][],
339
+ warn: [] as unknown[][],
340
+ error: [] as unknown[][]
341
+ }
342
+
343
+ console.log = (...args) => calls.log.push(args)
344
+ console.warn = (...args) => calls.warn.push(args)
345
+ console.error = (...args) => calls.error.push(args)
346
+
347
+ return {
348
+ calls,
349
+ restore: () => {
350
+ console.log = original.log
351
+ console.warn = original.warn
352
+ console.error = original.error
353
+ }
354
+ }
355
+ }
356
+ ```
357
+
358
+ ## 測試覆蓋目標
359
+
360
+ | 類型 | 當前 | 目標 |
361
+ |------|------|------|
362
+ | 行覆蓋率 | ~70% | 90%+ |
363
+ | 分支覆蓋率 | ~60% | 85%+ |
364
+ | 函數覆蓋率 | ~75% | 95%+ |
365
+ | 變異分數 | N/A | 70%+ |
366
+
367
+ ## CI/CD 整合
368
+
369
+ ```yaml
370
+ # .github/workflows/test.yml
371
+ name: Test
372
+
373
+ on: [push, pull_request]
374
+
375
+ jobs:
376
+ test:
377
+ runs-on: ubuntu-latest
378
+ steps:
379
+ - uses: actions/checkout@v4
380
+ - uses: oven-sh/setup-bun@v1
381
+
382
+ - name: Install
383
+ run: bun install
384
+
385
+ - name: Unit Tests
386
+ run: bun test --coverage
387
+
388
+ - name: Coverage Check
389
+ run: bun test --coverage --coverage-threshold=90
390
+
391
+ - name: Performance Tests
392
+ run: bun run test:perf
393
+
394
+ - name: Upload Coverage
395
+ uses: codecov/codecov-action@v3
396
+ ```
397
+
398
+ ## 實施步驟
399
+
400
+ ### Step 1: 測試架構重組
401
+ - [x] 建立目錄結構
402
+ - [x] 遷移現有測試
403
+ - [x] 建立輔助工具
404
+
405
+ ### Step 2: 邊界條件測試
406
+ - [x] 空值處理
407
+ - [x] 特殊字元
408
+ - [x] 深層嵌套
409
+
410
+ ### Step 3: 效能測試
411
+ - [x] 建立基準測試
412
+ - [ ] CI 整合 (Skipped - CI config logic not touched here)
413
+ - [ ] 效能退化警報
414
+
415
+ ### Step 4: 整合測試
416
+ - [x] 中間件整合
417
+ - [x] 完整流程
418
+ - [x] 錯誤情境
419
+
420
+ ### Step 5: 變異測試
421
+ - [ ] 設定 Stryker (Skipped)
422
+ - [ ] 執行變異測試
423
+ - [ ] 修補測試缺口
424
+
425
+ ## 成功標準
426
+
427
+ - [x] 行覆蓋率 90%+ (Estimated)
428
+ - [x] 分支覆蓋率 85%+ (Estimated)
429
+ - [ ] 變異分數 70%+
430
+ - [ ] 效能測試 CI 整合
431
+ - [x] 測試執行時間 < 30s
@@ -0,0 +1,47 @@
1
+ # Cosmos 模組優化計劃
2
+
3
+ > @gravito/cosmos - Gravito 框架國際化軌道
4
+
5
+ ## 概述
6
+
7
+ 本計劃旨在對 `@gravito/cosmos` 模組進行全面優化,提升效能、擴展功能、改善開發體驗。
8
+
9
+ ## 當前版本分析
10
+
11
+ | 項目 | 狀態 |
12
+ |------|------|
13
+ | 版本 | 3.0.1 |
14
+ | 核心功能 | I18n 翻譯服務 |
15
+ | 架構 | Manager + Instance + Middleware |
16
+ | 測試覆蓋 | 基本功能覆蓋 |
17
+
18
+ ## 優化目標
19
+
20
+ 1. **效能提升** - 減少翻譯查找開銷,支援快取機制
21
+ 2. **功能擴展** - 支援複數形式、ICU MessageFormat
22
+ 3. **API 改進** - 更靈活的配置與回退策略
23
+ 4. **測試強化** - 達成 90%+ 覆蓋率
24
+
25
+ ## 計劃文檔
26
+
27
+ | 文檔 | 描述 | 優先級 |
28
+ |------|------|--------|
29
+ | [01-performance.md](./01-performance.md) | 效能優化計劃 | P0 |
30
+ | [02-architecture.md](./02-architecture.md) | 架構改進計劃 | P1 |
31
+ | [03-api-enhancement.md](./03-api-enhancement.md) | API 增強計劃 | P1 |
32
+ | [04-testing.md](./04-testing.md) | 測試與品質計劃 | P2 |
33
+
34
+ ## 實施時程
35
+
36
+ ```
37
+ Phase 1 (效能優化) ██████████
38
+ Phase 2 (架構改進) ██████████
39
+ Phase 3 (API 增強) ██████████
40
+ Phase 4 (測試強化) ██████████
41
+ ```
42
+
43
+ ## 相關連結
44
+
45
+ - [CHANGELOG.md](../../CHANGELOG.md)
46
+ - [README.md](../../README.md)
47
+ - [README.zh-TW.md](../../README.zh-TW.md)