timsquad 3.3.0 → 3.5.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 (196) hide show
  1. package/README.ko.md +288 -0
  2. package/README.md +158 -151
  3. package/dist/commands/compile.d.ts +3 -0
  4. package/dist/commands/compile.d.ts.map +1 -0
  5. package/dist/commands/compile.js +170 -0
  6. package/dist/commands/compile.js.map +1 -0
  7. package/dist/commands/daemon.d.ts.map +1 -1
  8. package/dist/commands/daemon.js +95 -5
  9. package/dist/commands/daemon.js.map +1 -1
  10. package/dist/commands/full.js +1 -0
  11. package/dist/commands/full.js.map +1 -1
  12. package/dist/commands/git/pr.js +6 -5
  13. package/dist/commands/git/pr.js.map +1 -1
  14. package/dist/commands/git/release.js +2 -7
  15. package/dist/commands/git/release.js.map +1 -1
  16. package/dist/commands/improve.js +2 -2
  17. package/dist/commands/improve.js.map +1 -1
  18. package/dist/commands/init.d.ts.map +1 -1
  19. package/dist/commands/init.js +12 -3
  20. package/dist/commands/init.js.map +1 -1
  21. package/dist/commands/log.d.ts.map +1 -1
  22. package/dist/commands/log.js +2 -2
  23. package/dist/commands/log.js.map +1 -1
  24. package/dist/commands/metrics.d.ts.map +1 -1
  25. package/dist/commands/metrics.js +6 -2
  26. package/dist/commands/metrics.js.map +1 -1
  27. package/dist/commands/retro.js +8 -8
  28. package/dist/commands/retro.js.map +1 -1
  29. package/dist/commands/session.js +3 -3
  30. package/dist/commands/session.js.map +1 -1
  31. package/dist/commands/skills.d.ts +12 -0
  32. package/dist/commands/skills.d.ts.map +1 -0
  33. package/dist/commands/skills.js +228 -0
  34. package/dist/commands/skills.js.map +1 -0
  35. package/dist/commands/status.js +1 -1
  36. package/dist/commands/status.js.map +1 -1
  37. package/dist/commands/upgrade.d.ts.map +1 -1
  38. package/dist/commands/upgrade.js +23 -1
  39. package/dist/commands/upgrade.js.map +1 -1
  40. package/dist/daemon/entry.js +3 -3
  41. package/dist/daemon/entry.js.map +1 -1
  42. package/dist/daemon/event-queue.d.ts.map +1 -1
  43. package/dist/daemon/event-queue.js +2 -2
  44. package/dist/daemon/event-queue.js.map +1 -1
  45. package/dist/daemon/index.d.ts +4 -2
  46. package/dist/daemon/index.d.ts.map +1 -1
  47. package/dist/daemon/index.js +214 -52
  48. package/dist/daemon/index.js.map +1 -1
  49. package/dist/daemon/jsonl-watcher.d.ts +1 -0
  50. package/dist/daemon/jsonl-watcher.d.ts.map +1 -1
  51. package/dist/daemon/jsonl-watcher.js.map +1 -1
  52. package/dist/daemon/meta-cache.d.ts +1 -0
  53. package/dist/daemon/meta-cache.d.ts.map +1 -1
  54. package/dist/daemon/meta-cache.js +9 -0
  55. package/dist/daemon/meta-cache.js.map +1 -1
  56. package/dist/daemon/session-notes.d.ts +33 -0
  57. package/dist/daemon/session-notes.d.ts.map +1 -0
  58. package/dist/daemon/session-notes.js +74 -0
  59. package/dist/daemon/session-notes.js.map +1 -0
  60. package/dist/daemon/session-state.d.ts +27 -0
  61. package/dist/daemon/session-state.d.ts.map +1 -0
  62. package/dist/daemon/session-state.js +165 -0
  63. package/dist/daemon/session-state.js.map +1 -0
  64. package/dist/daemon/shutdown.d.ts.map +1 -1
  65. package/dist/daemon/shutdown.js +9 -1
  66. package/dist/daemon/shutdown.js.map +1 -1
  67. package/dist/index.js +4 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/lib/agent-generator.d.ts +4 -0
  70. package/dist/lib/agent-generator.d.ts.map +1 -1
  71. package/dist/lib/agent-generator.js +52 -3
  72. package/dist/lib/agent-generator.js.map +1 -1
  73. package/dist/lib/compile-rules.d.ts +66 -0
  74. package/dist/lib/compile-rules.d.ts.map +1 -0
  75. package/dist/lib/compile-rules.js +114 -0
  76. package/dist/lib/compile-rules.js.map +1 -0
  77. package/dist/lib/compiler.d.ts +105 -0
  78. package/dist/lib/compiler.d.ts.map +1 -0
  79. package/dist/lib/compiler.js +368 -0
  80. package/dist/lib/compiler.js.map +1 -0
  81. package/dist/lib/config.d.ts +1 -0
  82. package/dist/lib/config.d.ts.map +1 -1
  83. package/dist/lib/config.js +8 -1
  84. package/dist/lib/config.js.map +1 -1
  85. package/dist/lib/project.d.ts.map +1 -1
  86. package/dist/lib/project.js +8 -3
  87. package/dist/lib/project.js.map +1 -1
  88. package/dist/lib/skill-generator.d.ts.map +1 -1
  89. package/dist/lib/skill-generator.js +22 -1
  90. package/dist/lib/skill-generator.js.map +1 -1
  91. package/dist/lib/template.d.ts.map +1 -1
  92. package/dist/lib/template.js +6 -0
  93. package/dist/lib/template.js.map +1 -1
  94. package/dist/types/config.d.ts +1 -0
  95. package/dist/types/config.d.ts.map +1 -1
  96. package/dist/types/config.js +12 -1
  97. package/dist/types/config.js.map +1 -1
  98. package/dist/types/project.d.ts +1 -1
  99. package/dist/types/project.d.ts.map +1 -1
  100. package/dist/types/project.js +2 -0
  101. package/dist/types/project.js.map +1 -1
  102. package/package.json +4 -4
  103. package/templates/base/agents/base/tsq-architect.md +2 -2
  104. package/templates/base/agents/overlays/domain/mobile/_common.md +13 -0
  105. package/templates/base/knowledge/checklists/plan-quality.md +31 -0
  106. package/templates/base/knowledge/checklists/stability-verification.md +14 -0
  107. package/templates/base/skills/controller/SKILL.md +111 -0
  108. package/templates/base/skills/controller/references/README.md +35 -0
  109. package/templates/base/skills/controller/rules/README.md +18 -0
  110. package/templates/base/skills/mobile/dart/SKILL.md +69 -0
  111. package/templates/base/skills/mobile/dart/rules/async-patterns.md +112 -0
  112. package/templates/base/skills/mobile/dart/rules/code-style.md +96 -0
  113. package/templates/base/skills/mobile/dart/rules/null-safety.md +84 -0
  114. package/templates/base/skills/mobile/dart/rules/type-system.md +111 -0
  115. package/templates/base/skills/mobile/flutter/SKILL.md +89 -0
  116. package/templates/base/skills/mobile/flutter/ci-cd/SKILL.md +82 -0
  117. package/templates/base/skills/mobile/flutter/ci-cd/references/ci-cd-pipeline.md +314 -0
  118. package/templates/base/skills/mobile/flutter/ci-cd/rules/code-signing.md +106 -0
  119. package/templates/base/skills/mobile/flutter/ci-cd/rules/codemagic-setup.md +116 -0
  120. package/templates/base/skills/mobile/flutter/ci-cd/rules/fastlane-setup.md +105 -0
  121. package/templates/base/skills/mobile/flutter/ci-cd/rules/github-actions.md +112 -0
  122. package/templates/base/skills/mobile/flutter/ci-cd/rules/store-deployment.md +106 -0
  123. package/templates/base/skills/mobile/flutter/ci-cd/rules/versioning.md +107 -0
  124. package/templates/base/skills/mobile/flutter/i18n/SKILL.md +78 -0
  125. package/templates/base/skills/mobile/flutter/i18n/references/i18n-architecture.md +225 -0
  126. package/templates/base/skills/mobile/flutter/i18n/rules/arb-files.md +182 -0
  127. package/templates/base/skills/mobile/flutter/i18n/rules/locale-switching.md +226 -0
  128. package/templates/base/skills/mobile/flutter/i18n/rules/localization-setup.md +137 -0
  129. package/templates/base/skills/mobile/flutter/i18n/rules/plural-gender.md +159 -0
  130. package/templates/base/skills/mobile/flutter/i18n/rules/text-direction.md +199 -0
  131. package/templates/base/skills/mobile/flutter/monitoring/SKILL.md +81 -0
  132. package/templates/base/skills/mobile/flutter/monitoring/references/monitoring-architecture.md +269 -0
  133. package/templates/base/skills/mobile/flutter/monitoring/rules/analytics.md +227 -0
  134. package/templates/base/skills/mobile/flutter/monitoring/rules/crashlytics-setup.md +195 -0
  135. package/templates/base/skills/mobile/flutter/monitoring/rules/logging.md +258 -0
  136. package/templates/base/skills/mobile/flutter/monitoring/rules/performance-monitoring.md +248 -0
  137. package/templates/base/skills/mobile/flutter/monitoring/rules/sentry-integration.md +249 -0
  138. package/templates/base/skills/mobile/flutter/networking/SKILL.md +88 -0
  139. package/templates/base/skills/mobile/flutter/networking/references/api-client-architecture.md +305 -0
  140. package/templates/base/skills/mobile/flutter/networking/rules/caching.md +212 -0
  141. package/templates/base/skills/mobile/flutter/networking/rules/connectivity.md +213 -0
  142. package/templates/base/skills/mobile/flutter/networking/rules/dio-setup.md +159 -0
  143. package/templates/base/skills/mobile/flutter/networking/rules/error-handling.md +209 -0
  144. package/templates/base/skills/mobile/flutter/networking/rules/interceptors.md +205 -0
  145. package/templates/base/skills/mobile/flutter/networking/rules/retrofit-patterns.md +194 -0
  146. package/templates/base/skills/mobile/flutter/push-notifications/SKILL.md +87 -0
  147. package/templates/base/skills/mobile/flutter/push-notifications/references/notification-architecture.md +340 -0
  148. package/templates/base/skills/mobile/flutter/push-notifications/references/platform-setup.md +286 -0
  149. package/templates/base/skills/mobile/flutter/push-notifications/rules/background-processing.md +308 -0
  150. package/templates/base/skills/mobile/flutter/push-notifications/rules/deep-linking.md +217 -0
  151. package/templates/base/skills/mobile/flutter/push-notifications/rules/fcm-setup.md +164 -0
  152. package/templates/base/skills/mobile/flutter/push-notifications/rules/local-notifications.md +262 -0
  153. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-handling.md +210 -0
  154. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-permissions.md +246 -0
  155. package/templates/base/skills/mobile/flutter/push-notifications/rules/rich-notifications.md +320 -0
  156. package/templates/base/skills/mobile/flutter/references/freezed-patterns.md +162 -0
  157. package/templates/base/skills/mobile/flutter/references/project-structure.md +170 -0
  158. package/templates/base/skills/mobile/flutter/rules/animations.md +112 -0
  159. package/templates/base/skills/mobile/flutter/rules/architecture.md +121 -0
  160. package/templates/base/skills/mobile/flutter/rules/navigation-routing.md +117 -0
  161. package/templates/base/skills/mobile/flutter/rules/performance.md +112 -0
  162. package/templates/base/skills/mobile/flutter/rules/platform-adaptive.md +126 -0
  163. package/templates/base/skills/mobile/flutter/rules/state-management.md +110 -0
  164. package/templates/base/skills/mobile/flutter/rules/testing.md +131 -0
  165. package/templates/base/skills/mobile/flutter/rules/widget-conventions.md +122 -0
  166. package/templates/base/skills/mobile/flutter/security/SKILL.md +86 -0
  167. package/templates/base/skills/mobile/flutter/security/references/mobile-security-checklist.md +168 -0
  168. package/templates/base/skills/mobile/flutter/security/rules/api-key-protection.md +206 -0
  169. package/templates/base/skills/mobile/flutter/security/rules/authentication.md +248 -0
  170. package/templates/base/skills/mobile/flutter/security/rules/data-protection.md +271 -0
  171. package/templates/base/skills/mobile/flutter/security/rules/obfuscation.md +213 -0
  172. package/templates/base/skills/mobile/flutter/security/rules/secure-storage.md +171 -0
  173. package/templates/base/skills/mobile/flutter/security/rules/ssl-pinning.md +197 -0
  174. package/templates/base/skills/stability-verification/SKILL.md +64 -0
  175. package/templates/base/skills/stability-verification/references/release-checklist.md +34 -0
  176. package/templates/base/skills/stability-verification/references/security-fix-patterns.md +112 -0
  177. package/templates/base/skills/stability-verification/rules/verification-layers.md +67 -0
  178. package/templates/base/skills/stability-verification/rules/verification-workflow.md +69 -0
  179. package/templates/base/skills/stability-verification/scripts/verify.sh +294 -0
  180. package/templates/platforms/claude-code/CLAUDE.md.template +25 -0
  181. package/templates/platforms/claude-code/rules/build-gate.md +28 -0
  182. package/templates/platforms/claude-code/rules/completion-verification.md +30 -0
  183. package/templates/platforms/claude-code/rules/context-monitor.md +23 -0
  184. package/templates/platforms/claude-code/rules/plan-review.md +45 -0
  185. package/templates/platforms/claude-code/rules/quality-guards.md +43 -0
  186. package/templates/platforms/claude-code/rules/session-notes.md +18 -0
  187. package/templates/platforms/claude-code/rules/skill-suggest.md +27 -0
  188. package/templates/platforms/claude-code/scripts/build-gate.sh +73 -0
  189. package/templates/platforms/claude-code/scripts/completion-guard.sh +93 -0
  190. package/templates/platforms/claude-code/scripts/phase-guard.sh +79 -0
  191. package/templates/platforms/claude-code/scripts/safe-guard.sh +83 -0
  192. package/templates/platforms/claude-code/scripts/skill-rules.json +85 -0
  193. package/templates/platforms/claude-code/scripts/skill-suggest.sh +105 -0
  194. package/templates/platforms/claude-code/settings.json +111 -3
  195. package/templates/project-types/mobile-app/config.yaml +123 -0
  196. package/templates/project-types/mobile-app/process/workflow.xml +191 -0
@@ -0,0 +1,305 @@
1
+ ---
2
+ title: API Client Architecture
3
+ category: reference
4
+ source: internal
5
+ tags: architecture, directory, provider, repository, mock, testing
6
+ ---
7
+
8
+ # API Client Architecture
9
+
10
+ 네트워크 레이어 전체 아키텍처. 디렉토리 구조, Provider 그래프, Repository 패턴, Mock 테스트 전략.
11
+
12
+ ## Key Concepts
13
+
14
+ - **중앙화**: 네트워크 관련 코드를 `core/networking/` 에 집중 (feature 횡단 관심사)
15
+ - **계층 분리**: Dio → Interceptor → API Client → Repository → Provider → UI
16
+ - **테스트 가능성**: API Client 추상화로 Mock 구현 용이
17
+ - **오프라인 우선**: 캐시 + 큐 + connectivity 감지를 기본 탑재
18
+
19
+ ## Directory Structure
20
+
21
+ ```
22
+ lib/
23
+ ├── core/
24
+ │ └── networking/
25
+ │ ├── dio_provider.dart # Dio 싱글톤 Provider
26
+ │ ├── app_config.dart # 환경 설정 (baseUrl 등)
27
+ │ ├── interceptors/
28
+ │ │ ├── auth_interceptor.dart # 토큰 주입 + 401 리프레시
29
+ │ │ ├── retry_interceptor.dart # 지수 백오프 재시도
30
+ │ │ ├── logging_interceptor.dart # 요청/응답 로깅
31
+ │ │ └── error_transform_interceptor.dart # DioException → NetworkFailure
32
+ │ ├── errors/
33
+ │ │ ├── network_failure.dart # sealed class 에러 타입
34
+ │ │ └── result.dart # Result<T> 타입
35
+ │ ├── cache/
36
+ │ │ ├── cache_config.dart # 캐시 정책 팩토리
37
+ │ │ ├── cache_store_provider.dart # HiveCacheStore Provider
38
+ │ │ └── cache_manager.dart # 캐시 클리어/무효화
39
+ │ ├── connectivity/
40
+ │ │ ├── connectivity_notifier.dart # 연결 상태 스트림
41
+ │ │ ├── offline_queue.dart # 오프라인 요청 큐
42
+ │ │ └── offline_banner.dart # 오프라인 UI 위젯
43
+ │ └── api/
44
+ │ ├── user_api.dart # @RestApi 클라이언트
45
+ │ ├── match_api.dart # @RestApi 클라이언트
46
+ │ └── ...
47
+
48
+ ├── features/
49
+ │ └── user/
50
+ │ ├── data/
51
+ │ │ ├── models/
52
+ │ │ │ ├── user_response.dart # freezed 응답 모델
53
+ │ │ │ └── create_user_request.dart # freezed 요청 모델
54
+ │ │ └── repositories/
55
+ │ │ └── user_repository_impl.dart # API 호출 + 에러 변환
56
+ │ ├── domain/
57
+ │ │ ├── entities/
58
+ │ │ │ └── user.dart # 도메인 엔티티
59
+ │ │ └── repositories/
60
+ │ │ └── user_repository.dart # abstract
61
+ │ └── presentation/
62
+ │ └── providers/
63
+ │ └── user_provider.dart # UI 상태 관리
64
+ ```
65
+
66
+ ## Provider Dependency Graph
67
+
68
+ ```
69
+ AppConfig
70
+
71
+
72
+ DioProvider ──────────────────────────────────┐
73
+ │ │
74
+ ├─ AuthInterceptor ← TokenStorage │
75
+ ├─ RetryInterceptor │
76
+ ├─ LoggingInterceptor (dev only) │
77
+ ├─ ErrorTransformInterceptor │
78
+ └─ DioCacheInterceptor ← CacheStore │
79
+
80
+ ┌──────────────────────────────────────────┘
81
+
82
+
83
+ API Clients (Retrofit)
84
+ │ UserApi, MatchApi, ...
85
+
86
+
87
+ Repositories
88
+ │ UserRepository, MatchRepository, ...
89
+ │ (API 호출 + Result<T> 반환)
90
+
91
+
92
+ Notifiers / Providers
93
+ │ UserNotifier, MatchNotifier, ...
94
+ │ (비즈니스 로직 + UI 상태)
95
+
96
+
97
+ Widgets (UI)
98
+
99
+ ConnectivityNotifier ────► OfflineQueueManager
100
+ │ │
101
+ └── OfflineBanner (UI) └── 연결 복구 시 큐 처리
102
+ ```
103
+
104
+ ## Provider 코드
105
+
106
+ ```dart
107
+ // core/networking/providers.dart — 네트워크 레이어 Provider 모음
108
+
109
+ /// 앱 설정
110
+ final appConfigProvider = Provider<AppConfig>((ref) => AppConfig.dev);
111
+
112
+ /// Dio 싱글톤
113
+ final dioProvider = Provider<Dio>((ref) {
114
+ final config = ref.watch(appConfigProvider);
115
+ final dio = Dio(BaseOptions(
116
+ baseUrl: config.baseUrl,
117
+ connectTimeout: const Duration(seconds: 15),
118
+ receiveTimeout: const Duration(seconds: 15),
119
+ sendTimeout: const Duration(seconds: 15),
120
+ headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},
121
+ ));
122
+
123
+ dio.interceptors.addAll([
124
+ ref.watch(authInterceptorProvider),
125
+ ref.watch(retryInterceptorProvider),
126
+ if (config.environment == AppEnvironment.dev)
127
+ ref.watch(loggingInterceptorProvider),
128
+ ref.watch(errorTransformInterceptorProvider),
129
+ ]);
130
+
131
+ return dio;
132
+ });
133
+
134
+ /// API Clients
135
+ final userApiProvider = Provider<UserApi>((ref) {
136
+ return UserApi(ref.watch(dioProvider));
137
+ });
138
+
139
+ /// Repositories
140
+ final userRepositoryProvider = Provider<UserRepository>((ref) {
141
+ return UserRepositoryImpl(api: ref.watch(userApiProvider));
142
+ });
143
+
144
+ /// Connectivity
145
+ final connectivityProvider =
146
+ StreamNotifierProvider<ConnectivityNotifier, NetworkStatus>(
147
+ ConnectivityNotifier.new,
148
+ );
149
+
150
+ final isOnlineProvider = Provider<bool>((ref) {
151
+ return ref.watch(connectivityProvider).valueOrNull == NetworkStatus.online;
152
+ });
153
+ ```
154
+
155
+ ## Repository Pattern
156
+
157
+ ```dart
158
+ /// Repository 추상 (domain 레이어)
159
+ abstract class UserRepository {
160
+ Future<Result<User>> getUser(String id);
161
+ Future<Result<List<User>>> getUsers({int page = 1});
162
+ Future<Result<User>> createUser(CreateUserRequest request);
163
+ Future<Result<void>> deleteUser(String id);
164
+ }
165
+
166
+ /// Repository 구현 (data 레이어)
167
+ class UserRepositoryImpl implements UserRepository {
168
+ final UserApi _api;
169
+
170
+ UserRepositoryImpl({required UserApi api}) : _api = api;
171
+
172
+ @override
173
+ Future<Result<User>> getUser(String id) async {
174
+ try {
175
+ final response = await _api.getUser(id);
176
+ return Success(response.toDomain());
177
+ } on DioException catch (e) {
178
+ return Failure(NetworkFailure.fromDioException(e));
179
+ }
180
+ }
181
+
182
+ @override
183
+ Future<Result<List<User>>> getUsers({int page = 1}) async {
184
+ try {
185
+ final response = await _api.getUsers(page, 20);
186
+ return Success(response.data.map((r) => r.toDomain()).toList());
187
+ } on DioException catch (e) {
188
+ return Failure(NetworkFailure.fromDioException(e));
189
+ }
190
+ }
191
+
192
+ // ... createUser, deleteUser 동일 패턴
193
+ }
194
+ ```
195
+
196
+ ## Mock Testing Strategy
197
+
198
+ ```dart
199
+ /// Mock API Client
200
+ class MockUserApi implements UserApi {
201
+ @override
202
+ Future<UserResponse> getUser(String id) async {
203
+ return UserResponse(
204
+ id: id,
205
+ name: 'Test User',
206
+ email: 'test@example.com',
207
+ createdAt: DateTime.now(),
208
+ );
209
+ }
210
+
211
+ @override
212
+ Future<PaginatedResponse<UserResponse>> getUsers(int page, int limit) async {
213
+ return PaginatedResponse(
214
+ data: List.generate(limit, (i) => UserResponse(
215
+ id: 'user_$i',
216
+ name: 'User $i',
217
+ email: 'user$i@example.com',
218
+ createdAt: DateTime.now(),
219
+ )),
220
+ total: 100,
221
+ page: page,
222
+ lastPage: 5,
223
+ );
224
+ }
225
+
226
+ // ...
227
+ }
228
+
229
+ /// 테스트에서 Provider 오버라이드
230
+ void main() {
231
+ group('UserNotifier', () {
232
+ late ProviderContainer container;
233
+
234
+ setUp(() {
235
+ container = ProviderContainer(overrides: [
236
+ userApiProvider.overrideWithValue(MockUserApi()),
237
+ ]);
238
+ });
239
+
240
+ tearDown(() => container.dispose());
241
+
242
+ test('loads user successfully', () async {
243
+ final repository = container.read(userRepositoryProvider);
244
+ final result = await repository.getUser('user_1');
245
+
246
+ expect(result, isA<Success<User>>());
247
+ expect((result as Success).data.name, 'Test User');
248
+ });
249
+ });
250
+ }
251
+
252
+ /// 에러 시나리오 Mock
253
+ class ErrorUserApi implements UserApi {
254
+ @override
255
+ Future<UserResponse> getUser(String id) async {
256
+ throw DioException(
257
+ requestOptions: RequestOptions(path: '/users/$id'),
258
+ type: DioExceptionType.connectionTimeout,
259
+ );
260
+ }
261
+
262
+ // ...
263
+ }
264
+
265
+ /// 에러 테스트
266
+ test('returns TimeoutFailure on connection timeout', () async {
267
+ final container = ProviderContainer(overrides: [
268
+ userApiProvider.overrideWithValue(ErrorUserApi()),
269
+ ]);
270
+
271
+ final repository = container.read(userRepositoryProvider);
272
+ final result = await repository.getUser('user_1');
273
+
274
+ expect(result, isA<Failure<User>>());
275
+ expect((result as Failure).failure, isA<TimeoutFailure>());
276
+ });
277
+ ```
278
+
279
+ ## Initialization Flow
280
+
281
+ ```
282
+ 앱 시작 (main.dart)
283
+
284
+ ├─ 1. WidgetsFlutterBinding.ensureInitialized()
285
+ ├─ 2. AppConfig 결정 (환경 변수 / 빌드 플래그)
286
+ ├─ 3. CacheStore 초기화 (HiveCacheStore)
287
+ ├─ 4. ProviderScope(overrides: [appConfig, cacheStore])
288
+ └─ 5. runApp()
289
+
290
+ └─ App 위젯 build
291
+ ├─ DioProvider 자동 생성 (lazy)
292
+ ├─ ConnectivityNotifier 스트림 시작
293
+ └─ OfflineBanner 조건부 표시
294
+ ```
295
+
296
+ ## Common Pitfalls
297
+
298
+ 1. **Dio 다중 인스턴스**: Feature마다 Dio 생성 → 인터셉터 미적용, 토큰 누락
299
+ 2. **인터셉터 순서**: Error Transform이 Auth 앞 → 401 리프레시 불가
300
+ 3. **토큰 리프레시 순환**: Auth 인터셉터가 메인 Dio로 리프레시 → 무한 루프
301
+ 4. **캐시 + 인증 에러**: 401 응답 캐시 → 로그인 후에도 에러 반환
302
+ 5. **오프라인 큐 영속성**: 메모리 큐만 사용 → 앱 재시작 시 유실
303
+ 6. **connectTimeout vs receiveTimeout**: connect는 TCP 연결, receive는 데이터 수신 — 둘 다 설정 필수
304
+ 7. **validateStatus 미설정**: 4xx도 DioException → 정상 에러 응답 처리 불가
305
+ 8. **build_runner 미실행**: Retrofit `.g.dart` 미생성 → 컴파일 에러
@@ -0,0 +1,212 @@
1
+ ---
2
+ title: HTTP Caching Strategy
3
+ impact: MEDIUM
4
+ impactDescription: "캐시 미사용 → 불필요한 네트워크 호출, 느린 로딩, 데이터 요금 낭비"
5
+ tags: cache, dio-cache-interceptor, etag, offline, performance
6
+ ---
7
+
8
+ ## HTTP Caching Strategy
9
+
10
+ **Impact: MEDIUM (캐시 미사용 → 불필요한 네트워크 호출, 느린 로딩, 데이터 요금 낭비)**
11
+
12
+ dio_cache_interceptor로 HTTP 응답 캐시.
13
+ ETag/Last-Modified 검증, 오프라인 fallback, 정책별 캐시 전략.
14
+
15
+ ### 의존성
16
+
17
+ ```yaml
18
+ # pubspec.yaml
19
+ dependencies:
20
+ dio: ^5.7.0
21
+ dio_cache_interceptor: ^3.5.0
22
+ dio_cache_interceptor_hive_store: ^3.2.0 # 영구 저장소
23
+ ```
24
+
25
+ ### 캐시 인터셉터 설정
26
+
27
+ **Incorrect (캐시 없이 매번 네트워크 호출):**
28
+ ```dart
29
+ final dio = Dio();
30
+ // → 동일 데이터 반복 요청, 배터리 + 데이터 낭비
31
+ // → 오프라인 시 데이터 표시 불가
32
+ ```
33
+
34
+ **Correct (dio_cache_interceptor 통합):**
35
+ ```dart
36
+ /// 캐시 저장소 초기화
37
+ Future<CacheStore> initCacheStore() async {
38
+ final dir = await getApplicationDocumentsDirectory();
39
+ return HiveCacheStore(
40
+ '${dir.path}/http_cache',
41
+ hiveBoxName: 'dio_cache',
42
+ );
43
+ }
44
+
45
+ /// 캐시 옵션 팩토리
46
+ class CacheConfig {
47
+ /// 기본 캐시 정책 — 네트워크 우선, 실패 시 캐시
48
+ static CacheOptions defaultPolicy(CacheStore store) {
49
+ return CacheOptions(
50
+ store: store,
51
+ policy: CachePolicy.request, // 네트워크 요청 + 캐시 갱신
52
+ maxStale: const Duration(days: 7), // 오프라인 시 7일간 캐시 유효
53
+ hitCacheOnErrorExcept: [401, 403], // 에러 시 캐시 반환 (인증 에러 제외)
54
+ );
55
+ }
56
+
57
+ /// 강제 캐시 — 네트워크 미사용 (빠른 로딩)
58
+ static CacheOptions forceCache(CacheStore store) {
59
+ return CacheOptions(
60
+ store: store,
61
+ policy: CachePolicy.forceCache,
62
+ );
63
+ }
64
+
65
+ /// 캐시 무시 — 항상 최신 데이터
66
+ static CacheOptions noCache(CacheStore store) {
67
+ return CacheOptions(
68
+ store: store,
69
+ policy: CachePolicy.noCache,
70
+ );
71
+ }
72
+
73
+ /// stale-while-revalidate — 캐시 먼저 반환, 백그라운드 갱신
74
+ static CacheOptions refreshIfStale(CacheStore store) {
75
+ return CacheOptions(
76
+ store: store,
77
+ policy: CachePolicy.refreshForceCache,
78
+ maxStale: const Duration(hours: 1),
79
+ );
80
+ }
81
+ }
82
+
83
+ /// Dio에 캐시 인터셉터 추가
84
+ final cacheStoreProvider = FutureProvider<CacheStore>((ref) async {
85
+ return await initCacheStore();
86
+ });
87
+
88
+ final cacheDioProvider = Provider<Dio>((ref) {
89
+ final dio = ref.watch(dioProvider);
90
+ final cacheStore = ref.watch(cacheStoreProvider).valueOrNull;
91
+
92
+ if (cacheStore != null) {
93
+ final cacheOptions = CacheConfig.defaultPolicy(cacheStore);
94
+ dio.interceptors.add(
95
+ DioCacheInterceptor(options: cacheOptions),
96
+ );
97
+ }
98
+
99
+ return dio;
100
+ });
101
+ ```
102
+
103
+ ### 요청별 캐시 정책 오버라이드
104
+
105
+ ```dart
106
+ /// API에서 요청별 캐시 정책 지정
107
+ class ProductRepository {
108
+ final Dio _dio;
109
+ final CacheStore _cacheStore;
110
+
111
+ ProductRepository({required Dio dio, required CacheStore cacheStore})
112
+ : _dio = dio,
113
+ _cacheStore = cacheStore;
114
+
115
+ /// 상품 목록 — 기본 캐시 (네트워크 + 캐시 갱신)
116
+ Future<Result<List<Product>>> getProducts() async {
117
+ try {
118
+ final response = await _dio.get('/products');
119
+ return Success(_parseProducts(response.data));
120
+ } on DioException catch (e) {
121
+ return Failure(NetworkFailure.fromDioException(e));
122
+ }
123
+ }
124
+
125
+ /// 상품 상세 — 캐시 우선 (자주 변경되지 않음)
126
+ Future<Result<Product>> getProduct(String id) async {
127
+ try {
128
+ final response = await _dio.get(
129
+ '/products/$id',
130
+ options: CacheConfig.refreshIfStale(_cacheStore).toOptions(),
131
+ );
132
+ return Success(Product.fromJson(response.data));
133
+ } on DioException catch (e) {
134
+ return Failure(NetworkFailure.fromDioException(e));
135
+ }
136
+ }
137
+
138
+ /// 결제 정보 — 캐시 금지 (항상 최신)
139
+ Future<Result<PaymentInfo>> getPaymentInfo() async {
140
+ try {
141
+ final response = await _dio.get(
142
+ '/payment/info',
143
+ options: CacheConfig.noCache(_cacheStore).toOptions(),
144
+ );
145
+ return Success(PaymentInfo.fromJson(response.data));
146
+ } on DioException catch (e) {
147
+ return Failure(NetworkFailure.fromDioException(e));
148
+ }
149
+ }
150
+ }
151
+ ```
152
+
153
+ ### ETag / Last-Modified 활용
154
+
155
+ ```dart
156
+ // dio_cache_interceptor가 자동 처리하는 HTTP 헤더:
157
+ //
158
+ // 서버 응답:
159
+ // ETag: "abc123"
160
+ // Last-Modified: Wed, 15 Jan 2026 10:00:00 GMT
161
+ // Cache-Control: max-age=3600
162
+ //
163
+ // 클라이언트 재요청 (자동):
164
+ // If-None-Match: "abc123"
165
+ // If-Modified-Since: Wed, 15 Jan 2026 10:00:00 GMT
166
+ //
167
+ // 서버 응답:
168
+ // 304 Not Modified → 캐시된 데이터 사용 (전송 비용 없음)
169
+ // 200 OK → 새 데이터로 캐시 갱신
170
+
171
+ // 서버가 ETag/Last-Modified를 지원하면 자동으로 최적화됨
172
+ // 별도 클라이언트 코드 필요 없음
173
+ ```
174
+
175
+ ### 캐시 관리
176
+
177
+ ```dart
178
+ /// 캐시 클리어 (로그아웃 시, 디버깅 시)
179
+ class CacheManager {
180
+ final CacheStore _store;
181
+
182
+ CacheManager({required CacheStore store}) : _store = store;
183
+
184
+ /// 전체 캐시 클리어
185
+ Future<void> clearAll() async {
186
+ await _store.clean();
187
+ }
188
+
189
+ /// 특정 URL 캐시 삭제
190
+ Future<void> invalidate(String url) async {
191
+ await _store.delete(CacheOptions.defaultCacheKeyBuilder(
192
+ RequestOptions(path: url),
193
+ ));
194
+ }
195
+ }
196
+
197
+ final cacheManagerProvider = Provider<CacheManager>((ref) {
198
+ final store = ref.watch(cacheStoreProvider).valueOrNull;
199
+ if (store == null) throw StateError('Cache store not initialized');
200
+ return CacheManager(store: store);
201
+ });
202
+ ```
203
+
204
+ ### 규칙
205
+
206
+ - GET 요청에만 캐시 적용 — POST/PUT/DELETE는 캐시 금지
207
+ - 기본 정책: 네트워크 우선 + 실패 시 캐시 fallback (`hitCacheOnErrorExcept`)
208
+ - 결제/인증 관련 API는 `CachePolicy.noCache` 강제
209
+ - `maxStale: 7일` — 오프라인 시 캐시 유효 기간
210
+ - 인증 에러(401, 403)는 캐시 반환 제외 — 만료 토큰으로 캐시된 데이터 방지
211
+ - 로그아웃 시 전체 캐시 클리어 — 사용자 데이터 잔류 방지
212
+ - HiveCacheStore 사용 — 앱 재시작 후에도 캐시 유지 (메모리 캐시보다 안정)