@xfilecom/xframe 0.1.38 → 0.1.39
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/bin/xframe.js +7 -1
- package/defaults.json +2 -2
- package/package.json +1 -1
- package/template/apps/api/src/main.ts +36 -1
- package/template/docs/SCAFFOLD_CHECKLIST.md +13 -0
- package/template/shared/README.md +1 -1
- package/template/shared/endpoint/endpoint.ts +22 -5
- package/template/web/admin/src/App.tsx +1 -1
- package/template/web/admin/src/main.tsx +10 -1
- package/template/web/admin/src/vite-env.d.ts +3 -2
- package/template/web/admin/vite.config.ts +2 -2
- package/template/web/client/src/App.tsx +1 -1
- package/template/web/client/src/FrontCoreShowcase.tsx +44 -1
- package/template/web/client/src/main.tsx +11 -1
- package/template/web/client/src/vite-env.d.ts +3 -2
- package/template/web/client/vite.config.ts +3 -2
- package/template/web/shared/README.md +5 -0
- package/template/web/shared/src/api/client.ts +66 -7
- package/template/web/shared/src/api/commonResponse.ts +53 -5
- package/template/web/shared/src/api/methods/health.method.ts +20 -1
- package/template/web/shared/src/api/methods/index.ts +8 -1
- package/template/web/shared/src/api/request.ts +33 -1
- package/template/web/shared/src/api/types.ts +10 -1
- package/template/web/shared/src/context/StoreProvider.tsx +44 -3
- package/template/web/shared/src/hooks/useAppStore.ts +28 -3
- package/template/web/shared/src/hooks/useHealthStatus.ts +25 -2
- package/template/web/shared/src/import-meta.d.ts +13 -0
- package/template/web/shared/src/methods/health.ts +16 -1
- package/template/web/shared/src/store/api-cache-store.ts +19 -0
- package/template/web/shared/src/store/root-store.ts +142 -8
- package/template/web/shared/src/store/session-store.ts +21 -2
- package/template/web/shared/src/types/ui.ts +7 -1
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* RootStore — 프론트 “앱 오케스트레이션” 허브 (MobX)
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 포함 관계
|
|
7
|
+
* ---------
|
|
8
|
+
* - **sessionStore** (싱글톤): JWT 등 — `api/client`가 Authorization 헤더에 반영.
|
|
9
|
+
* - **apiCacheStore** (싱글톤): `getCachedOrExecute`용 짧은 TTL 캐시.
|
|
10
|
+
* - **paramStore** (인스턴스): 화면별 URL/폼 파라미터 스키마. `getStore: () => this`로
|
|
11
|
+
* 검증 시 RootStore에 접근 가능.
|
|
12
|
+
*
|
|
13
|
+
* HTTP / UI 연동 (가장 중요)
|
|
14
|
+
* ---------------------------
|
|
15
|
+
* 생성자에서 `setApiHooks`로 axios와 연결한다:
|
|
16
|
+
*
|
|
17
|
+
* | 훅 | 호출 시점 | 템플릿 기본 동작 |
|
|
18
|
+
* |----|-----------|------------------|
|
|
19
|
+
* | onRequestStart/End | 각 apiClient 요청 (skip 아닐 때) | `_requestDepth` 증감 → `indicator.loading` |
|
|
20
|
+
* | onError | HTTP 실패 응답·네트워크 등 | `addToast(..., 'error')` |
|
|
21
|
+
* | onCommonBusinessError | HTTP 2xx + 본문 `code !== 0` | 동일 |
|
|
22
|
+
*
|
|
23
|
+
* 따라서 **대부분의 API 에러 알림은 페이지가 아니라 여기서 토스트**로 처리된다.
|
|
24
|
+
* `sendMessage` / `execute`의 catch에서는 `axios.isAxiosError`·`CommonResponseRejectedError`일 때
|
|
25
|
+
* **중복 토스트를 피하도록** 이미 인터셉터에서 처리된 경우를 건너뛴다.
|
|
26
|
+
*
|
|
27
|
+
* 토스트 UX (템플릿 기본)
|
|
28
|
+
* ----------------------
|
|
29
|
+
* - **error**: 자동 제거 없음 — 사용자가 닫기만 가능 (`StoreProvider`에서 `dismissible` true).
|
|
30
|
+
* - **info / success / warn**: `TOAST_AUTO_DISMISS_MS` 후 자동 제거, 닫기 버튼 없음.
|
|
31
|
+
*
|
|
32
|
+
* execute / sendMessage
|
|
33
|
+
* ----------------------
|
|
34
|
+
* - **sendMessage**: 단일 `EndpointDef` + 옵션. `showIndicator`에 따라 (1) 로컬 `setIndicator`와
|
|
35
|
+
* (2) axios `skipGlobalIndicator`를 맞춘다. 전역 바만 쓰려면 엔드포인트/`showIndicator` 정책을 통일할 것.
|
|
36
|
+
* - **execute**: `ApiMethodConfig` 기반 워크플로 — 검증, `sendMessage` 또는 다중 `sendRequest`,
|
|
37
|
+
* `post` 훅, `redirectPath`, 토스트 옵션 등.
|
|
38
|
+
* - **다중 엔드포인트 배치** (`config.endpoints`): `_multiEndpointBatch`로 axios 전역 카운터를
|
|
39
|
+
* 잠시 억제하는 패턴이 있음(배치 중 개별 요청이 바를 깜빡이지 않게).
|
|
40
|
+
*
|
|
41
|
+
* Health 데모 필드
|
|
42
|
+
* ----------------
|
|
43
|
+
* `healthData`, `healthError`, `lastHealthAt` — `useHealthStatus` + 샘플 UI용.
|
|
44
|
+
* 프로덕션 앱에서는 도메인 스토어로 옮기거나 제거해도 된다.
|
|
3
45
|
*/
|
|
4
46
|
|
|
5
47
|
import axios from 'axios';
|
|
@@ -31,10 +73,17 @@ import type { ApiErrorItem, IndicatorState, SendMessageOptions, ToastItem } from
|
|
|
31
73
|
import type { ToastSeverity } from '../types/ui';
|
|
32
74
|
import type { HealthData } from '../types/api';
|
|
33
75
|
|
|
76
|
+
/** 토스트·에러 항목 등에 쓰는 짧은 고유 id (타임스탬프 + 랜덤) */
|
|
34
77
|
function genId() {
|
|
35
78
|
return `id_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
36
79
|
}
|
|
37
80
|
|
|
81
|
+
/**
|
|
82
|
+
* 코어 SDK 스타일 `{ data: T | T[] }` 래핑을 한 겹 벗긴다.
|
|
83
|
+
* 배열이면 첫 요소를 대표값으로 쓰는 레거시 규칙이 있음 (`unwrapDataArray`).
|
|
84
|
+
* @param res - API 응답 본문(또는 이미 펼친 T)
|
|
85
|
+
* @param unwrapDataArray - false면 `res`를 그대로 T로 취급
|
|
86
|
+
*/
|
|
38
87
|
function unwrapCoreSdk<T>(res: T | CoreSdkWrapped<T>, unwrapDataArray: boolean): T | null {
|
|
39
88
|
if (!unwrapDataArray) return res as T;
|
|
40
89
|
const r = res as CoreSdkWrapped<T>;
|
|
@@ -45,6 +94,9 @@ function unwrapCoreSdk<T>(res: T | CoreSdkWrapped<T>, unwrapDataArray: boolean):
|
|
|
45
94
|
return res as T;
|
|
46
95
|
}
|
|
47
96
|
|
|
97
|
+
/**
|
|
98
|
+
* API 에러 본문 등에서 사람이 읽을 문자열 후보를 재귀적으로 추출한다.
|
|
99
|
+
*/
|
|
48
100
|
function extractMessageString(value: unknown): string | null {
|
|
49
101
|
if (value == null) return null;
|
|
50
102
|
if (typeof value === 'string') return value;
|
|
@@ -59,6 +111,10 @@ function extractMessageString(value: unknown): string | null {
|
|
|
59
111
|
return null;
|
|
60
112
|
}
|
|
61
113
|
|
|
114
|
+
/**
|
|
115
|
+
* `sendMessage`/`execute` catch 등에서 토스트에 넣을 문구를 만든다.
|
|
116
|
+
* @param e - AxiosError, CommonResponseRejectedError, 일반 Error 등
|
|
117
|
+
*/
|
|
62
118
|
function getErrorMessage(e: unknown): string {
|
|
63
119
|
if (e instanceof CommonResponseRejectedError) return e.message;
|
|
64
120
|
const err = e as { response?: { data?: unknown }; message?: string };
|
|
@@ -81,10 +137,17 @@ export class RootStore {
|
|
|
81
137
|
getStore: () => this,
|
|
82
138
|
});
|
|
83
139
|
|
|
140
|
+
/** 전역 로딩 + (선택) 메시지, 현재 실행 중 method 이름 등 */
|
|
84
141
|
private _indicator: IndicatorState = { loading: false, methodLoading: null };
|
|
142
|
+
/** 패널용 에러 리스트 — 토스트와 별도 */
|
|
85
143
|
private _errors: ApiErrorItem[] = [];
|
|
144
|
+
/** StoreProvider 전역 토스트 스택 데이터 소스 */
|
|
86
145
|
private _toasts: ToastItem[] = [];
|
|
146
|
+
/** info/success/warn 자동 제거용 — `removeToast`·타이머 콜백에서 정리 */
|
|
147
|
+
private _toastAutoDismissTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
148
|
+
/** axios onRequestStart가 중첩 호출될 때를 위한 깊이 카운터 */
|
|
87
149
|
private _requestDepth = 0;
|
|
150
|
+
/** execute 다중 엔드포인트 루프 중 전역 인디케이터 억제 */
|
|
88
151
|
private _multiEndpointBatch = false;
|
|
89
152
|
|
|
90
153
|
/** 데모·useHealthStatus 용 타임스탬프 */
|
|
@@ -94,6 +157,9 @@ export class RootStore {
|
|
|
94
157
|
healthData: HealthData | null = null;
|
|
95
158
|
healthError: string | null = null;
|
|
96
159
|
|
|
160
|
+
/**
|
|
161
|
+
* MobX 관찰 가능 필드로 등록하고, 세션 로그아웃 시 캐시 비우기·axios 훅을 `rootStore`에 연결한다.
|
|
162
|
+
*/
|
|
97
163
|
constructor() {
|
|
98
164
|
makeAutoObservable(this);
|
|
99
165
|
this.sessionStore.registerLogoutCallback(() => {
|
|
@@ -119,30 +185,40 @@ export class RootStore {
|
|
|
119
185
|
});
|
|
120
186
|
}
|
|
121
187
|
|
|
188
|
+
/** 전역 로딩·옵션 메시지·현재 method 로딩 키 */
|
|
122
189
|
get indicator(): IndicatorState {
|
|
123
190
|
return this._indicator;
|
|
124
191
|
}
|
|
125
192
|
|
|
193
|
+
/** 패널/리스트용 에러 히스토리 (토스트와 별도) */
|
|
126
194
|
get errors(): ApiErrorItem[] {
|
|
127
195
|
return this._errors;
|
|
128
196
|
}
|
|
129
197
|
|
|
198
|
+
/** `GlobalToastStackView`가 구독하는 스택 */
|
|
130
199
|
get toasts(): ToastItem[] {
|
|
131
200
|
return this._toasts;
|
|
132
201
|
}
|
|
133
202
|
|
|
134
|
-
/**
|
|
203
|
+
/**
|
|
204
|
+
* 마지막 health 성공 시각(밀리초). 화살표 필드라 `useAppStore(s => s.setLastHealthAt)`로 꺼내 호출해도 안전.
|
|
205
|
+
* @param t - `Date.now()` 등
|
|
206
|
+
*/
|
|
135
207
|
setLastHealthAt = (t: number) => {
|
|
136
208
|
this.lastHealthAt = t;
|
|
137
209
|
};
|
|
138
210
|
|
|
139
|
-
/**
|
|
211
|
+
/** `/health` 요청 직전: `healthError`만 초기화 (진행 표시는 axios `indicator`) */
|
|
140
212
|
applyHealthFetchStart = () => {
|
|
141
213
|
runInAction(() => {
|
|
142
214
|
this.healthError = null;
|
|
143
215
|
});
|
|
144
216
|
};
|
|
145
217
|
|
|
218
|
+
/**
|
|
219
|
+
* `/health` 성공 시 스토어에 페이로드 반영.
|
|
220
|
+
* @param data - 인터셉터에서 봉투 제거된 뒤의 본문
|
|
221
|
+
*/
|
|
146
222
|
applyHealthFetchSuccess = (data: HealthData) => {
|
|
147
223
|
runInAction(() => {
|
|
148
224
|
this.healthError = null;
|
|
@@ -151,12 +227,16 @@ export class RootStore {
|
|
|
151
227
|
});
|
|
152
228
|
};
|
|
153
229
|
|
|
230
|
+
/**
|
|
231
|
+
* `/health` 실패 시 `healthError`에 정규화된 메시지 저장 (토스트는 인터셉터가 담당할 수 있음).
|
|
232
|
+
*/
|
|
154
233
|
applyHealthFetchFailure = (err: unknown) => {
|
|
155
234
|
runInAction(() => {
|
|
156
235
|
this.healthError = getErrorMessage(err);
|
|
157
236
|
});
|
|
158
237
|
};
|
|
159
238
|
|
|
239
|
+
/** axios `onRequestStart`에서 호출: 중첩 요청 시 깊이만 증가, 첫 요청에서만 `loading: true` */
|
|
160
240
|
private _startRequest() {
|
|
161
241
|
this._requestDepth++;
|
|
162
242
|
if (this._requestDepth === 1) {
|
|
@@ -164,6 +244,7 @@ export class RootStore {
|
|
|
164
244
|
}
|
|
165
245
|
}
|
|
166
246
|
|
|
247
|
+
/** axios `onRequestEnd`에서 호출: 깊이 0이 되면 `loading: false` */
|
|
167
248
|
private _endRequest() {
|
|
168
249
|
this._requestDepth = Math.max(0, this._requestDepth - 1);
|
|
169
250
|
if (this._requestDepth === 0) {
|
|
@@ -171,41 +252,80 @@ export class RootStore {
|
|
|
171
252
|
}
|
|
172
253
|
}
|
|
173
254
|
|
|
255
|
+
/**
|
|
256
|
+
* `sendMessage` 등에서 쓰는 로컬 인디케이터(전역 바와 별개로 메시지 동반 가능).
|
|
257
|
+
*/
|
|
174
258
|
setIndicator(loading: boolean, message?: string) {
|
|
175
259
|
this._indicator = { ...this._indicator, loading, message };
|
|
176
260
|
}
|
|
177
261
|
|
|
262
|
+
/**
|
|
263
|
+
* `executeMethod`가 어떤 키를 실행 중인지 UI에 표시할 때 사용.
|
|
264
|
+
*/
|
|
178
265
|
setMethodLoading(methodName: string | null) {
|
|
179
266
|
this._indicator = { ...this._indicator, methodLoading: methodName };
|
|
180
267
|
}
|
|
181
268
|
|
|
269
|
+
/**
|
|
270
|
+
* 토스트가 아닌 “에러 패널”용 항목 추가 (최대 50건 유지).
|
|
271
|
+
*/
|
|
182
272
|
addError(message: string, code?: string | number) {
|
|
183
273
|
this._errors = [{ id: genId(), message, code, createdAt: Date.now() }, ...this._errors].slice(0, 50);
|
|
184
274
|
}
|
|
185
275
|
|
|
276
|
+
/** `addError`로 넣은 한 건 제거 */
|
|
186
277
|
clearError(id: string) {
|
|
187
278
|
this._errors = this._errors.filter((e) => e.id !== id);
|
|
188
279
|
}
|
|
189
280
|
|
|
281
|
+
/** 에러 패널 전체 비우기 */
|
|
190
282
|
clearAllErrors() {
|
|
191
283
|
this._errors = [];
|
|
192
284
|
}
|
|
193
285
|
|
|
194
286
|
private static readonly TOAST_AUTO_DISMISS_MS = 2000;
|
|
195
287
|
|
|
288
|
+
/**
|
|
289
|
+
* 동일 메시지+심각도 연속 스팸 방지.
|
|
290
|
+
* - **error**: 타이머 없음 — 닫기만 제거 (`GlobalToastStackView`에서 error만 `dismissible`).
|
|
291
|
+
* - **그 외**: `TOAST_AUTO_DISMISS_MS` 후 자동 제거.
|
|
292
|
+
* @param message - 토스트 본문
|
|
293
|
+
* @param severity - `info` | `success` | `warn` | `error`
|
|
294
|
+
*/
|
|
196
295
|
addToast(message: string, severity: ToastSeverity = 'info') {
|
|
197
296
|
if (this._toasts.some((t) => t.message === message && t.severity === severity)) return;
|
|
198
297
|
const id = genId();
|
|
199
298
|
this._toasts = [...this._toasts, { id, message, severity, createdAt: Date.now() }].slice(-20);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
299
|
+
if (severity !== 'error') {
|
|
300
|
+
const tid = setTimeout(() => {
|
|
301
|
+
runInAction(() => {
|
|
302
|
+
this._toastAutoDismissTimers.delete(id);
|
|
303
|
+
this.removeToast(id);
|
|
304
|
+
});
|
|
305
|
+
}, RootStore.TOAST_AUTO_DISMISS_MS);
|
|
306
|
+
this._toastAutoDismissTimers.set(id, tid);
|
|
307
|
+
}
|
|
203
308
|
}
|
|
204
309
|
|
|
310
|
+
/** 닫기 클릭 또는 비-error 자동 제거 타이머에서 호출 */
|
|
205
311
|
removeToast(id: string) {
|
|
312
|
+
const pending = this._toastAutoDismissTimers.get(id);
|
|
313
|
+
if (pending !== undefined) {
|
|
314
|
+
clearTimeout(pending);
|
|
315
|
+
this._toastAutoDismissTimers.delete(id);
|
|
316
|
+
}
|
|
206
317
|
this._toasts = this._toasts.filter((t) => t.id !== id);
|
|
207
318
|
}
|
|
208
319
|
|
|
320
|
+
/**
|
|
321
|
+
* 등록된 `ApiMethodConfig` 워크플로 실행.
|
|
322
|
+
* - `endpoints` 배열이 있으면 순차 `sendRequest` (다중 배치 플래그 사용).
|
|
323
|
+
* - 아니면 단일 `sendMessage` 후 unwrap/post/redirect.
|
|
324
|
+
*
|
|
325
|
+
* @param config - 엔드포인트·UI 옵션·validate·post·redirectPath 등
|
|
326
|
+
* @param payload - 메서드 인자(바디/쿼리 빌더에서 사용)
|
|
327
|
+
* @param ctx - 라우터 `navigate` 등 실행 컨텍스트
|
|
328
|
+
*/
|
|
209
329
|
async execute<TPayload = unknown, TResult = unknown>(
|
|
210
330
|
config: ApiMethodConfig<TPayload, TResult>,
|
|
211
331
|
payload?: TPayload,
|
|
@@ -322,6 +442,10 @@ export class RootStore {
|
|
|
322
442
|
return finalResult;
|
|
323
443
|
}
|
|
324
444
|
|
|
445
|
+
/**
|
|
446
|
+
* `methods` 레지스트리에 등록된 키로 `execute` 호출. `indicator.methodLoading` 설정.
|
|
447
|
+
* @param methodName - 예: `'health'`
|
|
448
|
+
*/
|
|
325
449
|
async executeMethod<K extends MethodName>(
|
|
326
450
|
methodName: K,
|
|
327
451
|
payload?: (typeof methods)[K] extends ApiMethodConfig<infer P, unknown> ? P : never,
|
|
@@ -343,6 +467,11 @@ export class RootStore {
|
|
|
343
467
|
}
|
|
344
468
|
}
|
|
345
469
|
|
|
470
|
+
/**
|
|
471
|
+
* 캐시 히트 시 네트워크 생략, 미스 시 `executeMethod` 후 저장.
|
|
472
|
+
* @param cacheKey - 호출자 정의 문자열(화면+파라미터 해시 등)
|
|
473
|
+
* @param ttlMs - 만료(ms); 생략 시 만료 없음
|
|
474
|
+
*/
|
|
346
475
|
async getCachedOrExecute<K extends MethodName>(
|
|
347
476
|
cacheKey: string,
|
|
348
477
|
methodName: K,
|
|
@@ -360,8 +489,13 @@ export class RootStore {
|
|
|
360
489
|
}
|
|
361
490
|
|
|
362
491
|
/**
|
|
363
|
-
*
|
|
364
|
-
* `showIndicator
|
|
492
|
+
* 단일 엔드포인트 호출 래퍼.
|
|
493
|
+
* - `showIndicator`: 옵션 → `endpoint.showIndicator` → 기본 true.
|
|
494
|
+
* - `skipGlobalIndicator: !showIndicator` 로 axios 전역 바와 로컬 setIndicator를 정렬.
|
|
495
|
+
* - 에러 시: Axios/봉투 비즈니스 에러는 인터셉터가 이미 토스트 → catch에서는 중복 방지.
|
|
496
|
+
*
|
|
497
|
+
* @param endpoint - shared `EndpointDef`
|
|
498
|
+
* @param options - 쿼리·바디·토스트·인디케이터 옵션
|
|
365
499
|
*/
|
|
366
500
|
async sendMessage<T = unknown, R = unknown>(endpoint: EndpointDef, options: SendMessageOptions<T> = {}): Promise<R> {
|
|
367
501
|
const {
|
|
@@ -1,25 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* SessionStore — 인증 세션 (싱글톤)
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* - `token`이 설정되면 `api/client` 요청 인터셉터가 `Authorization: Bearer …`를 붙인다.
|
|
7
|
+
* - `logout()` / `setToken(null)` 시 `registerLogoutCallback`으로 등록된 루틴 실행
|
|
8
|
+
* (템플릿에서는 RootStore가 `apiCacheStore.clear()` 연동).
|
|
9
|
+
*
|
|
10
|
+
* 앱별로 refresh 토큰·저장소(localStorage) 동기화는 소비자 앱에서 확장한다.
|
|
11
|
+
*/
|
|
12
|
+
|
|
1
13
|
import { makeAutoObservable } from 'mobx';
|
|
2
14
|
|
|
3
|
-
/** Bearer 토큰 등 — api/client 가 Authorization 헤더에 반영 */
|
|
4
15
|
export class SessionStore {
|
|
5
16
|
token: string | null = null;
|
|
6
17
|
private _onLogout?: () => void;
|
|
7
18
|
|
|
19
|
+
/** MobX 관찰 필드로 등록 */
|
|
8
20
|
constructor() {
|
|
9
21
|
makeAutoObservable(this);
|
|
10
22
|
}
|
|
11
23
|
|
|
12
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* 로그아웃 시 한 번 호출할 콜백 등록 (중복 등록 시 마지막 것이 유효).
|
|
26
|
+
* @param cb - 예: API 캐시 클리어
|
|
27
|
+
*/
|
|
13
28
|
registerLogoutCallback(cb: () => void) {
|
|
14
29
|
this._onLogout = cb;
|
|
15
30
|
}
|
|
16
31
|
|
|
32
|
+
/**
|
|
33
|
+
* 액세스 토큰 설정. `null`로 두면 Bearer 제거; 이전에 토큰이 있었는데 `null`이면 `_onLogout` 실행.
|
|
34
|
+
*/
|
|
17
35
|
setToken(token: string | null) {
|
|
18
36
|
const had = this.token != null;
|
|
19
37
|
this.token = token;
|
|
20
38
|
if (had && token == null) this._onLogout?.();
|
|
21
39
|
}
|
|
22
40
|
|
|
41
|
+
/** `setToken(null)`과 동일 */
|
|
23
42
|
logout() {
|
|
24
43
|
this.setToken(null);
|
|
25
44
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* UI 상태 타입 — RootStore·StoreProvider·sendMessage 옵션과 공유
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* - **ToastItem**: MobX 배열 → `GlobalToastStackView`에서 front-core `Toast`로 매핑 (error만 닫기 버튼).
|
|
7
|
+
* - **IndicatorState**: 전역 로딩 바(`loading`) + (선택) 문구, 현재 method 이름 표시용 슬롯.
|
|
8
|
+
* - **SendMessageOptions**: `sendRequest`에 그대로 안 넘어가고 RootStore가 해석 후 일부만 전달.
|
|
3
9
|
*/
|
|
4
10
|
|
|
5
11
|
export type ToastSeverity = 'info' | 'success' | 'warn' | 'error';
|