@axboot-mcp/mcp-server 1.0.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.
- package/CLAUDE.md +119 -0
- package/MCP_TOOL_PLAN.md +710 -0
- package/MCP_USAGE.md +914 -0
- package/README.md +168 -0
- package/REPOSITORY_CONVENTIONS.md +250 -0
- package/SEARCH_PARAMS_MCP_TOOL_COMPLETE_PLAN.md +646 -0
- package/SEARCH_PARAMS_PLAN.md +2570 -0
- package/STORE_PATTERNS.md +1178 -0
- package/debug-dto.js +72 -0
- package/generate-banner-store.js +62 -0
- package/generation-plan.json +2176 -0
- package/generation-results.json +1817 -0
- package/package.json +45 -0
- package/scripts/batch-generate-all.js +159 -0
- package/scripts/batch-generate-mcp.js +329 -0
- package/scripts/batch-generate-stores-v2.js +272 -0
- package/scripts/batch-generate-stores.js +179 -0
- package/scripts/batch-plan.json +3810 -0
- package/scripts/batch-process.py +90 -0
- package/scripts/batch-regenerate.js +356 -0
- package/scripts/direct-generate.js +227 -0
- package/scripts/execute-batches.js +1911 -0
- package/scripts/generate-all-stores.js +144 -0
- package/scripts/generate-stores-mcp.js +161 -0
- package/scripts/generate-stores-v2.js +450 -0
- package/scripts/generate-stores-v3.js +412 -0
- package/scripts/generate-stores-v4.js +521 -0
- package/scripts/generate-stores.js +382 -0
- package/scripts/repos-to-process.json +1899 -0
- package/src/config/nh-layout-patterns.ts +166 -0
- package/src/docs/HOOK_GENERATION_PLAN.md +2226 -0
- package/src/docs/NH_STORE_PATTERNS.md +297 -0
- package/src/docs/README.md +216 -0
- package/src/docs/index.ts +28 -0
- package/src/docs/loader.ts +568 -0
- package/src/docs/patterns.json +419 -0
- package/src/docs/practical-examples.md +732 -0
- package/src/docs/quick-start.md +257 -0
- package/src/docs/requirements-analysis-guide.md +364 -0
- package/src/docs/rules.json +321 -0
- package/src/docs/store-pattern-analysis.md +664 -0
- package/src/docs/store-patterns-rules.md +1168 -0
- package/src/docs/store-patterns-usage-guide.md +1835 -0
- package/src/docs/troubleshooting.md +544 -0
- package/src/docs/type-selection-guide.md +572 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/AntD-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +1515 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/DataGrid-/354/202/254/354/232/251/353/262/225.md +866 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/FormItem-/354/202/254/354/232/251/353/262/225.md +903 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/FormModal-/354/202/254/354/232/251/353/262/225.md +1155 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/MCP-/353/260/224/354/235/264/353/270/214/354/275/224/353/224/251-/352/260/200/354/235/264/353/223/234.md +1133 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/MSW-Mock-/353/215/260/354/235/264/355/204/260-/354/202/254/354/232/251/353/262/225.md +579 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/Search-/354/273/264/355/217/254/353/204/214/355/212/270-/354/202/254/354/232/251/353/262/225.md +738 -0
- package/src/docs//354/202/254/354/232/251/353/262/225/Store-/355/214/250/355/204/264-/354/202/254/354/232/251/353/262/225.md +1135 -0
- package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/352/265/254/354/204/261-/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234/354/210/234/354/204/234.md +1805 -0
- package/src/docs//354/202/254/354/232/251/353/262/225//355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/352/260/234/353/260/234-/355/224/204/353/241/254/355/224/204/355/212/270-/352/260/200/354/235/264/353/223/234.md +946 -0
- package/src/docs//354/202/254/354/232/251/353/262/225//355/231/225/354/236/245/355/231/224/353/251/264/355/203/200/354/236/205/353/263/204-/354/203/201/354/204/270-/355/224/204/353/241/254/355/224/204/355/212/270/352/260/200/354/235/264/353/223/234.md +2422 -0
- package/src/features/store-features.ts +232 -0
- package/src/handlers/analyze-requirements.ts +403 -0
- package/src/handlers/analyze.ts +1373 -0
- package/src/handlers/generate-from-requirements.ts +250 -0
- package/src/handlers/generate-hook.ts +950 -0
- package/src/handlers/generate-interactive.ts +840 -0
- package/src/handlers/generate-listdatagrid.ts +521 -0
- package/src/handlers/generate-multi-stores.ts +577 -0
- package/src/handlers/generate-requirements-from-layout.ts +160 -0
- package/src/handlers/generate-search-params.ts +717 -0
- package/src/handlers/generate.ts +911 -0
- package/src/handlers/list-templates.ts +104 -0
- package/src/handlers/scan-metadata.ts +485 -0
- package/src/handlers/suggest-layout.ts +326 -0
- package/src/index.ts +959 -0
- package/src/prompts/search-params.md +793 -0
- package/src/templates/index.ts +107 -0
- package/src/templates/unified.ts +462 -0
- package/store-generation-error-patterns.md +225 -0
- package/test/useAgentStore.ts +136 -0
- package/test-server.js +78 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
# MSW Mock 데이터 사용법
|
|
2
|
+
|
|
3
|
+
> API 응답 데이터를 자동으로 저장하고 재사용하여, 외부 API 의존도를 줄이고 개발 효율을 높이는 방법
|
|
4
|
+
|
|
5
|
+
## 📋 개요
|
|
6
|
+
|
|
7
|
+
이 기능은 MSW(Mock Service Worker)와 IndexedDB를 사용하여:
|
|
8
|
+
- **첫 번째 API 호출**: 실제 API 호출 후 응답 데이터 자동 저장
|
|
9
|
+
- **두 번째부터**: 저장된 mock 데이터 사용 (API 호출 없음)
|
|
10
|
+
- **영구 저장**: 브라우저를 닫아도 데이터 유지
|
|
11
|
+
|
|
12
|
+
## 🎯 주요 장점
|
|
13
|
+
|
|
14
|
+
- ✅ **외부 API 차단 대응**: 네트워크 차단 시에도 개발 가능
|
|
15
|
+
- ✅ **빠른 개발**: API 응답을 기다리지 않고 즉시 데이터 사용
|
|
16
|
+
- ✅ **일관된 데이터**: 동일한 테스트 데이터로 반복 작업
|
|
17
|
+
- ✅ **환경별 설정**: `.env` 파일로 쉽게 ON/OFF 제어
|
|
18
|
+
|
|
19
|
+
## 🚀 적용 방법
|
|
20
|
+
|
|
21
|
+
### 1. MSW 설치
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install msw --save-dev
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. MSW Service Worker 초기화
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx msw init public/ --save
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 3. 필수 파일 생성
|
|
34
|
+
|
|
35
|
+
#### `src/mocks/storage/mockStorage.ts`
|
|
36
|
+
|
|
37
|
+
IndexedDB를 사용한 mock 데이터 저장소:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
const DB_NAME = 'msw-mock-data';
|
|
41
|
+
const DB_VERSION = 1;
|
|
42
|
+
const STORE_NAME = 'api-responses';
|
|
43
|
+
|
|
44
|
+
let db: IDBDatabase | null = null;
|
|
45
|
+
|
|
46
|
+
const initDB = (): Promise<IDBDatabase> => {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
49
|
+
|
|
50
|
+
request.onerror = () => reject(request.error);
|
|
51
|
+
request.onsuccess = () => {
|
|
52
|
+
db = request.result;
|
|
53
|
+
resolve(db);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
request.onupgradeneeded = (event) => {
|
|
57
|
+
const database = (event.target as IDBOpenDBRequest).result;
|
|
58
|
+
if (!database.objectStoreNames.contains(STORE_NAME)) {
|
|
59
|
+
database.createObjectStore(STORE_NAME, { keyPath: 'key' });
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const saveMockData = async (key: string, data: unknown): Promise<void> => {
|
|
66
|
+
if (!db) await initDB();
|
|
67
|
+
|
|
68
|
+
const transaction = db!.transaction([STORE_NAME], 'readwrite');
|
|
69
|
+
const objectStore = transaction.objectStore(STORE_NAME);
|
|
70
|
+
|
|
71
|
+
objectStore.put({ key, data, timestamp: Date.now() });
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const getMockData = async (key: string): Promise<unknown | null> => {
|
|
75
|
+
if (!db) await initDB();
|
|
76
|
+
|
|
77
|
+
const transaction = db!.transaction([STORE_NAME], 'readonly');
|
|
78
|
+
const objectStore = transaction.objectStore(STORE_NAME);
|
|
79
|
+
const request = objectStore.get(key);
|
|
80
|
+
|
|
81
|
+
return new Promise((resolve) => {
|
|
82
|
+
request.onsuccess = () => {
|
|
83
|
+
const record = request.result;
|
|
84
|
+
resolve(record ? record.data : null);
|
|
85
|
+
};
|
|
86
|
+
request.onerror = () => resolve(null);
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const generateKey = (method: string, url: string, params?: Record<string, unknown>): string => {
|
|
91
|
+
const normalizedMethod = method.toUpperCase();
|
|
92
|
+
const normalizedUrl = url.split('?')[0]; // 쿼리 파라미터 제거
|
|
93
|
+
|
|
94
|
+
if (params && Object.keys(params).length > 0) {
|
|
95
|
+
// 객체를 안전하게 쿼리 문자열로 변환
|
|
96
|
+
const searchParams = new URLSearchParams();
|
|
97
|
+
|
|
98
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
99
|
+
if (value !== undefined && value !== null) {
|
|
100
|
+
searchParams.append(key, String(value));
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const paramString = searchParams.toString();
|
|
105
|
+
return `${normalizedMethod}:${normalizedUrl}:${paramString}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return `${normalizedMethod}:${normalizedUrl}`;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// 백업용: 모든 데이터 조회
|
|
112
|
+
export const getAllMockData = async (): Promise<Record<string, unknown>> => {
|
|
113
|
+
// ... IndexedDB에서 모든 데이터 가져오기
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// 리스토어용: 여러 데이터 한 번에 저장
|
|
117
|
+
export const saveMultipleMockData = async (dataMap: Record<string, unknown>): Promise<void> => {
|
|
118
|
+
// ... IndexedDB에 여러 데이터 저장
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### `src/mocks/apiWrapperMock.ts`
|
|
123
|
+
|
|
124
|
+
apiWrapper용 mock 데이터 관리:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { getMockData, generateKey, saveMockData } from './storage/mockStorage';
|
|
128
|
+
|
|
129
|
+
export interface ApiWrapperMockOptions {
|
|
130
|
+
useMock?: boolean;
|
|
131
|
+
method: string;
|
|
132
|
+
route: string;
|
|
133
|
+
body?: any;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const useMockDataIfNeeded = async <T>(
|
|
137
|
+
options: ApiWrapperMockOptions,
|
|
138
|
+
): Promise<{ data: T | null; shouldCallApi: boolean }> => {
|
|
139
|
+
if (import.meta.env.VITE_MSW_ENABLED !== 'true') {
|
|
140
|
+
return { data: null, shouldCallApi: true };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const { method, route, body } = options;
|
|
144
|
+
const key = generateKey(method, route, body);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const mockData = await getMockData(key);
|
|
148
|
+
|
|
149
|
+
if (mockData) {
|
|
150
|
+
console.log(`[apiWrapper Mock] ✅ Using cached data for: ${key}`);
|
|
151
|
+
return { data: mockData as T, shouldCallApi: false };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(`[apiWrapper Mock] ⏳ No cached data for: ${key}`);
|
|
155
|
+
return { data: null, shouldCallApi: true };
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error('[apiWrapper Mock] Failed to get mock data:', error);
|
|
158
|
+
return { data: null, shouldCallApi: true };
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export const saveMockResponseIfNeeded = async (
|
|
163
|
+
options: ApiWrapperMockOptions,
|
|
164
|
+
data: unknown,
|
|
165
|
+
) => {
|
|
166
|
+
if (import.meta.env.VITE_MSW_ENABLED !== 'true') {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const { method, route, body } = options;
|
|
171
|
+
const key = generateKey(method, route, body);
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
await saveMockData(key, data);
|
|
175
|
+
console.log(`[apiWrapper Mock] 💾 Saved mock data for: ${key}`);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
console.error('[apiWrapper Mock] Failed to save mock data:', error);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### `src/mocks/handlers/index.ts`
|
|
183
|
+
|
|
184
|
+
MSW 핸들러 (빈 핸들러로 유지):
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { http } from 'msw';
|
|
188
|
+
|
|
189
|
+
export const handlers = [
|
|
190
|
+
// 실제 mock 처리는 apiWrapper에서 수행
|
|
191
|
+
];
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### `src/mocks/browser.ts`
|
|
195
|
+
|
|
196
|
+
MSW 브라우저 워커:
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { setupWorker } from 'msw/browser';
|
|
200
|
+
import { handlers } from './handlers';
|
|
201
|
+
|
|
202
|
+
type SetupWorker = ReturnType<typeof setupWorker>;
|
|
203
|
+
|
|
204
|
+
let worker: SetupWorker | null = null;
|
|
205
|
+
|
|
206
|
+
export const createMockWorker = (): SetupWorker => {
|
|
207
|
+
if (worker) {
|
|
208
|
+
return worker;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
worker = setupWorker(...handlers);
|
|
212
|
+
return worker;
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### `src/mocks/devtools.ts`
|
|
217
|
+
|
|
218
|
+
개발자 도구 유틸리티:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import {
|
|
222
|
+
getAllMockKeys,
|
|
223
|
+
getMockData,
|
|
224
|
+
deleteMockData,
|
|
225
|
+
clearAllMockData,
|
|
226
|
+
getAllMockData,
|
|
227
|
+
saveMultipleMockData,
|
|
228
|
+
} from './storage/mockStorage';
|
|
229
|
+
|
|
230
|
+
export interface MSWDevTools {
|
|
231
|
+
list: () => Promise<string[]>;
|
|
232
|
+
get: (key: string) => Promise<unknown>;
|
|
233
|
+
delete: (key: string) => Promise<void>;
|
|
234
|
+
clear: () => Promise<void>;
|
|
235
|
+
backup: () => Promise<void>;
|
|
236
|
+
restore: () => Promise<void>;
|
|
237
|
+
help: () => void;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export const setupMSWDevTools = () => {
|
|
241
|
+
if (import.meta.env.DEV) {
|
|
242
|
+
const mswDevTools: MSWDevTools = {
|
|
243
|
+
list: async () => {
|
|
244
|
+
const keys = await getAllMockKeys();
|
|
245
|
+
console.log(`[MSW DevTools] Total ${keys.length} cached APIs:`);
|
|
246
|
+
console.table(keys.map((key) => ({ key })));
|
|
247
|
+
return keys;
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
get: async (key: string) => {
|
|
251
|
+
const data = await getMockData(key);
|
|
252
|
+
if (data) {
|
|
253
|
+
console.log(`[MSW DevTools] Data for: ${key}`, data);
|
|
254
|
+
} else {
|
|
255
|
+
console.warn(`[MSW DevTools] No data found for: ${key}`);
|
|
256
|
+
}
|
|
257
|
+
return data;
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
delete: async (key: string) => {
|
|
261
|
+
await deleteMockData(key);
|
|
262
|
+
console.log(`[MSW DevTools] Deleted: ${key}`);
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
clear: async () => {
|
|
266
|
+
await clearAllMockData();
|
|
267
|
+
console.log('[MSW DevTools] All mock data cleared!');
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
backup: async () => {
|
|
271
|
+
try {
|
|
272
|
+
const dataMap = await getAllMockData();
|
|
273
|
+
const keys = Object.keys(dataMap);
|
|
274
|
+
|
|
275
|
+
if (keys.length === 0) {
|
|
276
|
+
console.warn('[MSW DevTools] No data to backup!');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// JSON 파일 생성 및 다운로드
|
|
281
|
+
const jsonString = JSON.stringify(dataMap, null, 2);
|
|
282
|
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
283
|
+
const url = URL.createObjectURL(blob);
|
|
284
|
+
|
|
285
|
+
const link = document.createElement('a');
|
|
286
|
+
link.href = url;
|
|
287
|
+
const date = new Date().toISOString().split('T')[0];
|
|
288
|
+
link.download = `msw-mock-backup-${date}.json`;
|
|
289
|
+
document.body.appendChild(link);
|
|
290
|
+
link.click();
|
|
291
|
+
document.body.removeChild(link);
|
|
292
|
+
URL.revokeObjectURL(url);
|
|
293
|
+
|
|
294
|
+
console.log(`[MSW DevTools] ✅ Backup complete: ${keys.length} entries saved`);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error('[MSW DevTools] Failed to backup:', error);
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
restore: async () => {
|
|
301
|
+
try {
|
|
302
|
+
const input = document.createElement('input');
|
|
303
|
+
input.type = 'file';
|
|
304
|
+
input.accept = 'application/json';
|
|
305
|
+
|
|
306
|
+
input.onchange = async (event) => {
|
|
307
|
+
const file = (event.target as HTMLInputElement).files?.[0];
|
|
308
|
+
if (!file) return;
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
const text = await file.text();
|
|
312
|
+
const dataMap = JSON.parse(text);
|
|
313
|
+
|
|
314
|
+
// 기존 데이터 삭제 후 복원
|
|
315
|
+
await clearAllMockData();
|
|
316
|
+
await saveMultipleMockData(dataMap);
|
|
317
|
+
|
|
318
|
+
console.log(`[MSW DevTools] ✅ Restore complete: ${Object.keys(dataMap).length} entries restored!`);
|
|
319
|
+
console.log('[MSW DevTools] 💡 Recommended: Refresh the page to apply changes.');
|
|
320
|
+
} catch (error) {
|
|
321
|
+
console.error('[MSW DevTools] Failed to restore:', error);
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
input.click();
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error('[MSW DevTools] Failed to restore:', error);
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
help: () => {
|
|
332
|
+
console.log(`
|
|
333
|
+
[MSW DevTools] 사용법:
|
|
334
|
+
|
|
335
|
+
window.mswDevTools.list() # 저장된 모든 mock 데이터 키 목록
|
|
336
|
+
window.mswDevTools.get('GET:/ao/api/users') # 특정 mock 데이터 조회
|
|
337
|
+
window.mswDevTools.delete('GET:/ao/api/users') # 특정 mock 데이터 삭제
|
|
338
|
+
window.mswDevTools.clear() # 모든 mock 데이터 삭제
|
|
339
|
+
window.mswDevTools.backup() # JSON 파일로 백업 (다운로드)
|
|
340
|
+
window.mswDevTools.restore() # JSON 파일에서 리스토어 (파일 선택)
|
|
341
|
+
|
|
342
|
+
예시:
|
|
343
|
+
// 백업 생성
|
|
344
|
+
await window.mswDevTools.backup()
|
|
345
|
+
|
|
346
|
+
// 리스토어
|
|
347
|
+
await window.mswDevTools.restore()
|
|
348
|
+
|
|
349
|
+
// 모든 저장된 API 목록 보기
|
|
350
|
+
await window.mswDevTools.list()
|
|
351
|
+
`);
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
(window as unknown as { mswDevTools: MSWDevTools }).mswDevTools = mswDevTools;
|
|
356
|
+
console.log('[MSW DevTools] initialized. Type window.mswDevTools.help() for usage.');
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### 4. apiWrapper 수정
|
|
362
|
+
|
|
363
|
+
`src/services/apiWrapper.ts`의 `apiWrapper` 함수에 mock 데이터 확인 로직 추가:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
export const apiWrapper: ApiWrapper = async <P>(
|
|
367
|
+
method: ApiMethod,
|
|
368
|
+
route: string,
|
|
369
|
+
body?: any,
|
|
370
|
+
config: ApiRequestConfig = {},
|
|
371
|
+
) => {
|
|
372
|
+
// ... 기존 코드 ...
|
|
373
|
+
|
|
374
|
+
// MSW Mock 데이터 확인 (apiWrapper 레벨에서 직접 처리)
|
|
375
|
+
if (import.meta.env.DEV && import.meta.env.VITE_MSW_ENABLED === 'true') {
|
|
376
|
+
try {
|
|
377
|
+
const { useMockDataIfNeeded } = await import("../mocks/apiWrapperMock");
|
|
378
|
+
const { data: mockData, shouldCallApi } = await useMockDataIfNeeded<P>({
|
|
379
|
+
method,
|
|
380
|
+
route,
|
|
381
|
+
body,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
if (!shouldCallApi && mockData) {
|
|
385
|
+
// mock 데이터가 있으면 반환
|
|
386
|
+
return { data: mockData, headers: {}, ...{} as any };
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error('[apiWrapper] Failed to check mock data:', error);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
try {
|
|
394
|
+
const { data, headers, ...rest } = await _axios(apiDataPlugin(axiosConfig));
|
|
395
|
+
// ... 기존 에러 처리 코드 ...
|
|
396
|
+
|
|
397
|
+
// 성공 응답 시 mock 데이터 저장
|
|
398
|
+
const { saveMockResponseIfNeeded } = await import("../mocks/apiWrapperMock");
|
|
399
|
+
const mockKey = generateKey(method, route, body);
|
|
400
|
+
saveMockResponseIfNeeded({ method, route, body }, data);
|
|
401
|
+
}
|
|
402
|
+
// ... catch 코드 ...
|
|
403
|
+
};
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### 5. main.tsx 수정
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// MSW (Mock Service Worker) - 개발 환경에서만 활성화
|
|
410
|
+
const initializeMSW = async () => {
|
|
411
|
+
if (import.meta.env.DEV && import.meta.env.VITE_MSW_ENABLED === 'true') {
|
|
412
|
+
const { createMockWorker } = await import('./mocks/browser');
|
|
413
|
+
const { setupMSWDevTools } = await import('./mocks/devtools');
|
|
414
|
+
const worker = createMockWorker();
|
|
415
|
+
|
|
416
|
+
try {
|
|
417
|
+
await worker?.start({
|
|
418
|
+
onUnhandledRequest: 'bypass',
|
|
419
|
+
});
|
|
420
|
+
console.log('[MSW] Mock Service Worker started successfully');
|
|
421
|
+
|
|
422
|
+
setupMSWDevTools();
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.error('[MSW] Failed to start Mock Service Worker:', error);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// MSW 초기화 후 앱 렌더링
|
|
430
|
+
initializeMSW().then(() => {
|
|
431
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
432
|
+
// ... 앱 렌더링 코드 ...
|
|
433
|
+
);
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### 6. 환경 변수 설정
|
|
438
|
+
|
|
439
|
+
`.env.dev`, `.env.alpha`, `.env.beta` 등에 추가:
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
# MSW (Mock Service Worker) 설정
|
|
443
|
+
VITE_MSW_ENABLED=true
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### 7. package.json 업데이트
|
|
447
|
+
|
|
448
|
+
`npx msw init public/ --save` 명령어 실행 후 `package.json`에 자동 추가됨:
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{
|
|
452
|
+
"msw": {
|
|
453
|
+
"workerDirectory": "public"
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## 🛠️ 사용 방법
|
|
459
|
+
|
|
460
|
+
### 1. MSW 활성화
|
|
461
|
+
|
|
462
|
+
`.env.alpha` 등 환경 파일에서 `VITE_MSW_ENABLED=true`로 설정:
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
VITE_MSW_ENABLED=true
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
### 2. 개발 서버 시작
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
npm run dev:alpha
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### 3. DevTools 사용
|
|
475
|
+
|
|
476
|
+
브라우저 개발자 도구 콜솔에서:
|
|
477
|
+
|
|
478
|
+
```javascript
|
|
479
|
+
// 저장된 모든 API 목록
|
|
480
|
+
await window.mswDevTools.list()
|
|
481
|
+
|
|
482
|
+
// 특정 API 데이터 조회
|
|
483
|
+
await window.mswDevTools.get('POST:/ao/api/login/otp/sms:usrLoginId=system&usrPw=...')
|
|
484
|
+
|
|
485
|
+
// 특정 API 데이터 삭제
|
|
486
|
+
await window.mswDevTools.delete('POST:/ao/api/login/otp/sms:...')
|
|
487
|
+
|
|
488
|
+
// 모든 데이터 삭제
|
|
489
|
+
await window.mswDevTools.clear()
|
|
490
|
+
|
|
491
|
+
// 💾 백업 (JSON 파일 다운로드)
|
|
492
|
+
await window.mswDevTools.backup()
|
|
493
|
+
|
|
494
|
+
// 📂 리스토어 (JSON 파일 선택창 열림)
|
|
495
|
+
await window.mswDevTools.restore()
|
|
496
|
+
|
|
497
|
+
// 도움말
|
|
498
|
+
window.mswDevTools.help()
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### 4. 백업 & 리스토어
|
|
502
|
+
|
|
503
|
+
#### 백업 (Backup)
|
|
504
|
+
|
|
505
|
+
IndexedDB에 저장된 모든 mock 데이터를 JSON 파일로 다운로드합니다:
|
|
506
|
+
|
|
507
|
+
```javascript
|
|
508
|
+
await window.mswDevTools.backup()
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
- 파일명: `msw-mock-backup-YYYY-MM-DD.json`
|
|
512
|
+
- 모든 API 응답 데이터가 포함됩니다
|
|
513
|
+
- 팀원들과 공유하거나 다른 PC에서 사용 가능
|
|
514
|
+
|
|
515
|
+
#### 리스토어 (Restore)
|
|
516
|
+
|
|
517
|
+
백업된 JSON 파일을 불러와서 IndexedDB에 복원합니다:
|
|
518
|
+
|
|
519
|
+
```javascript
|
|
520
|
+
await window.mswDevTools.restore()
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
1. 파일 선택창이 열립니다
|
|
524
|
+
2. 백업 파일을 선택하면 자동으로 복원됩니다
|
|
525
|
+
3. 복원 후 페이지 새로고침을 권장합니다
|
|
526
|
+
|
|
527
|
+
**주의사항:**
|
|
528
|
+
- 리스토어 시 **기존 데이터는 모두 삭제**됩니다
|
|
529
|
+
- 다른 프로젝트에서 백업한 파일도 사용 가능합니다 (경로가 동일한 경우)
|
|
530
|
+
|
|
531
|
+
## 📁 파일 구조
|
|
532
|
+
|
|
533
|
+
```
|
|
534
|
+
src/mocks/
|
|
535
|
+
├── browser.ts # MSW 브라우저 워커 설정
|
|
536
|
+
├── handlers/
|
|
537
|
+
│ └── index.ts # MSW 핸들러 (비어있음)
|
|
538
|
+
├── storage/
|
|
539
|
+
│ └── mockStorage.ts # IndexedDB 저장소
|
|
540
|
+
├── apiWrapperMock.ts # apiWrapper용 mock 유틸리티
|
|
541
|
+
└── devtools.ts # 개발자 도구
|
|
542
|
+
|
|
543
|
+
public/
|
|
544
|
+
└── mockServiceWorker.js # MSW 서비스 워커 (자동 생성)
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## 🔑 API 데이터 키 형식
|
|
548
|
+
|
|
549
|
+
```
|
|
550
|
+
METHOD:/API/path:param1=value1¶m2=value2
|
|
551
|
+
|
|
552
|
+
예시:
|
|
553
|
+
GET:/ao/api/users:pageNumber=1&pageSize=20
|
|
554
|
+
POST:/ao/api/users/save
|
|
555
|
+
POST:/ao/api/login/otp/sms:usrLoginId=system&usrPw=...&otpNum=123456
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
## 💡 주의사항
|
|
559
|
+
|
|
560
|
+
1. **로그인 API**: 보안상 로그인 관련 API도 mock으로 저장됩니다. 실제 로그인 테스트 시 주의하세요.
|
|
561
|
+
2. **데이터 무결성**: mock 데이터는 실제 API 응답을 저장하므로, 백엔드 데이터 변경 시 `window.mswDevTools.clear()`로 초기화하세요.
|
|
562
|
+
3. **개발 전용**: 이 기능은 개발 환경에서만 사용하세요. 프로덕션 빌드에서는 자동으로 비활성화됩니다.
|
|
563
|
+
4. **CORS 문제**: apiWrapper 레벨에서 mock을 처리하므로 CORS 문제가 발생하지 않습니다.
|
|
564
|
+
|
|
565
|
+
## 🎯 적용 후 효과
|
|
566
|
+
|
|
567
|
+
### Before (MSW 미적용)
|
|
568
|
+
- 외부 API 차단 시 개발 불가
|
|
569
|
+
- 매번 실제 API 호출 대기
|
|
570
|
+
- 네트워크 지연으로 개발 속도 저하
|
|
571
|
+
|
|
572
|
+
### After (MSW 적용)
|
|
573
|
+
- 외부 API 차단 시에도 개발 가능
|
|
574
|
+
- 첫 호출 이후 즉시 mock 데이터 사용
|
|
575
|
+
- 일관된 테스트 데이터로 빠른 개발
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
*이 가이드는 실제 프로젝트에 적용된 MSW Mock 데이터 기능을 정리한 것입니다.*
|