@rscc/common-core 0.1.2
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/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/index.cjs +862 -0
- package/dist/index.d.cts +759 -0
- package/dist/index.d.ts +759 -0
- package/dist/index.js +804 -0
- package/package.json +63 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,759 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST 공통 응답 봉투.
|
|
3
|
+
*
|
|
4
|
+
* 원천: contracts/common-response.schema.json (수동 동기화)
|
|
5
|
+
*
|
|
6
|
+
* - 와이어 키는 `success` (Java 필드명 isSuccess 의 직렬화 결과).
|
|
7
|
+
* - `code` 는 문자열 — error-codes.yaml 의 ResultCode 코드 값 중 하나.
|
|
8
|
+
* - `data` 는 실패 응답/데이터 없는 성공 응답에서 키 자체가 생략된다(absent, null 아님).
|
|
9
|
+
*/
|
|
10
|
+
interface CommonResponse<T> {
|
|
11
|
+
success: boolean;
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
data?: T;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 페이지네이션 응답 페이로드 (CommonResponse 의 data 자리에 실림).
|
|
18
|
+
*
|
|
19
|
+
* 원천: contracts/pagination.md (수동 동기화)
|
|
20
|
+
*
|
|
21
|
+
* - `page` 는 **0 시작(0-based)**. 1-기점 표시 변환은 클라이언트 책임.
|
|
22
|
+
* - 빈 페이지의 `content` 는 `[]` (null 아님).
|
|
23
|
+
*/
|
|
24
|
+
interface PageResponse<T> {
|
|
25
|
+
content: T[];
|
|
26
|
+
totalElements: number;
|
|
27
|
+
totalPages: number;
|
|
28
|
+
page: number;
|
|
29
|
+
size: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 400 검증 실패 상세 — 검증 오류 봉투(`ValidationErrorData.errors`)의 요소.
|
|
33
|
+
*
|
|
34
|
+
* 원천: contracts/validation-errors.md (수동 동기화)
|
|
35
|
+
*
|
|
36
|
+
* - `field` 는 Java BindingResult 표기: 세그먼트 `.` 연결, 인덱스 `[N]` 접미
|
|
37
|
+
* (예: `items[0].name`). 객체(클래스)-수준 오류는 빈 문자열 `""`.
|
|
38
|
+
* - `rejectedValue` 는 스칼라만 실리며(문자열은 서버가 최대 100자로 절단),
|
|
39
|
+
* null·비스칼라는 키 자체가 생략된다. PII 주의 — 비밀번호류 필드가 반사될 수
|
|
40
|
+
* 있으므로 화면 로그·외부 전송에 쓰지 말 것.
|
|
41
|
+
*/
|
|
42
|
+
interface FieldErrorDetail {
|
|
43
|
+
field: string;
|
|
44
|
+
reason: string;
|
|
45
|
+
rejectedValue?: string | number | boolean;
|
|
46
|
+
}
|
|
47
|
+
/** 검증 오류 봉투 — 400 검증 실패 응답의 `CommonResponse.data` 페이로드. */
|
|
48
|
+
interface ValidationErrorData {
|
|
49
|
+
errors: FieldErrorDetail[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* data 가 검증 오류 봉투인지 판별하는 타입 가드 — `errors` 배열 존재 + 각 요소의
|
|
53
|
+
* field/reason 이 string 인지 검사한다. rejectedValue·추가 키는 검사하지 않는다
|
|
54
|
+
* (관용적 읽기 — 서버가 선택 키를 추가해도 통과, contracts/README.md 원칙 3).
|
|
55
|
+
*/
|
|
56
|
+
declare function isValidationErrorData(value: unknown): value is ValidationErrorData;
|
|
57
|
+
/**
|
|
58
|
+
* 결과 코드 상수.
|
|
59
|
+
*
|
|
60
|
+
* 원천: contracts/error-codes.yaml (수동 동기화) — 코드 전량.
|
|
61
|
+
* 와이어의 code 는 문자열이다 (숫자 아님).
|
|
62
|
+
*/
|
|
63
|
+
declare const ResultCode: {
|
|
64
|
+
readonly SUCCESS: "200";
|
|
65
|
+
readonly BAD_REQUEST: "400";
|
|
66
|
+
readonly UNAUTHORIZED: "401";
|
|
67
|
+
readonly FORBIDDEN: "403";
|
|
68
|
+
readonly NOT_FOUND: "404";
|
|
69
|
+
readonly METHOD_NOT_ALLOWED: "405";
|
|
70
|
+
readonly CONFLICT: "409";
|
|
71
|
+
readonly UNSUPPORTED_MEDIA_TYPE: "415";
|
|
72
|
+
readonly TOO_MANY_REQUESTS: "429";
|
|
73
|
+
readonly INTERNAL_SERVER_ERROR: "500";
|
|
74
|
+
};
|
|
75
|
+
/** ResultCode 코드 값의 유니온 타입 ("200" | "400" | ...). */
|
|
76
|
+
type ResultCode = (typeof ResultCode)[keyof typeof ResultCode];
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 아웃바운드 회복탄력성 — 지수 백오프 + full jitter 재시도. 런타임 의존성 0(native 만).
|
|
80
|
+
*
|
|
81
|
+
* 일시적 실패(네트워크 오류, 5xx, 429 등)를 짧은 대기 후 재시도해 흐름을 복구한다.
|
|
82
|
+
* **per-call 타임아웃**(개별 요청의 무한 대기 차단)은 전송 계층의 책임이다 —
|
|
83
|
+
* fetch 라면 `AbortSignal.timeout(ms)` 를 요청에 넘기고, 그 signal 을 이 함수의
|
|
84
|
+
* `signal` 로도 넘겨 재시도 루프까지 함께 취소되게 한다. 이 유틸은 재시도 정책과
|
|
85
|
+
* **전체 재시도 시간 예산**(`maxElapsedMs`)을 담당한다.
|
|
86
|
+
*
|
|
87
|
+
* full jitter: 대기시간을 `[0, cap]` 에서 균등 추출한다(cap = 지수 백오프 값). 다수
|
|
88
|
+
* 클라이언트가 동시에 실패했을 때 재시도가 한 시점에 몰리는 thundering herd 를 흩뜨린다.
|
|
89
|
+
*/
|
|
90
|
+
interface RetryOptions {
|
|
91
|
+
/** 최대 재시도 횟수(총 시도 = retries + 1). 기본 3. */
|
|
92
|
+
retries?: number;
|
|
93
|
+
/** 백오프 base(ms). 기본 200. */
|
|
94
|
+
minDelayMs?: number;
|
|
95
|
+
/** 회당 대기 상한(ms). 기본 5000. */
|
|
96
|
+
maxDelayMs?: number;
|
|
97
|
+
/** 지수 배수. 기본 2. */
|
|
98
|
+
factor?: number;
|
|
99
|
+
/** full jitter 적용 여부. 기본 true. */
|
|
100
|
+
jitter?: boolean;
|
|
101
|
+
/** 전체 재시도 시간 예산(ms). 0이면 무제한. 다음 대기가 예산을 넘기면 재시도를 포기한다. 기본 0. */
|
|
102
|
+
maxElapsedMs?: number;
|
|
103
|
+
/** 이 오류(와 0-기점 attempt)를 재시도할지. 기본: 항상 재시도. */
|
|
104
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
105
|
+
/**
|
|
106
|
+
* 계산된 대기(ms)를 최종 대기(ms)로 조정하는 훅 — 예: 429 응답의 Retry-After 를
|
|
107
|
+
* 하한으로 적용({@link parseRetryAfterMs} 참조). 반환값이 유한수가 아니면 무시
|
|
108
|
+
* (계산값 유지)하고, 음수는 0 으로 클램프한다. `maxDelayMs` 상한은 훅 결과에
|
|
109
|
+
* **재적용하지 않는다** — Retry-After 가 백오프 상한보다 길 수 있는 것이 목적이며,
|
|
110
|
+
* 폭주 방어는 기존 `maxElapsedMs` 예산이 담당한다.
|
|
111
|
+
*/
|
|
112
|
+
overrideDelay?: (error: unknown, attempt: number, delayMs: number) => number;
|
|
113
|
+
/** 재시도 직전 콜백(로깅 등). delayMs 는 overrideDelay 적용 후의 최종값. */
|
|
114
|
+
onRetry?: (error: unknown, attempt: number, delayMs: number) => void;
|
|
115
|
+
/** 취소 신호. abort 시 대기/재시도를 즉시 중단하고 reject 한다. */
|
|
116
|
+
signal?: AbortSignal;
|
|
117
|
+
}
|
|
118
|
+
/** 해당 HTTP 상태가 재시도 권장 대상(408/429/500/502/503/504)인지. */
|
|
119
|
+
declare function isRetryableStatus(status: number): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Retry-After 헤더값(delta-seconds 또는 RFC1123 HTTP-date)을 대기 ms 로 파싱한다.
|
|
122
|
+
* 해석 불가면 null. HTTP-date 가 과거/현재 시각이면 0 (해석 불가가 아님). 요일 토큰은
|
|
123
|
+
* 검증하지 않고 무시하며(3언어 공통), 존재하지 않는 날짜·시각 범위 초과는 해석 불가다.
|
|
124
|
+
* Java `Retry.parseRetryAfterMs` / Python `parse_retry_after`(초 단위) 와 동일 규칙.
|
|
125
|
+
*
|
|
126
|
+
* @param nowMs 기준 시각(epoch ms). 기본 Date.now() — 테스트 주입용.
|
|
127
|
+
*/
|
|
128
|
+
declare function parseRetryAfterMs(value: string | null | undefined, nowMs?: number): number | null;
|
|
129
|
+
/**
|
|
130
|
+
* `fn` 을 지수 백오프 + jitter 로 재시도한다. `fn` 이 값을 반환/resolve 하면 그 값을,
|
|
131
|
+
* 재시도가 소진되거나 `shouldRetry` 가 false 면 마지막 오류를 throw 한다.
|
|
132
|
+
*
|
|
133
|
+
* @param fn 0-기점 attempt 를 받는 작업(동기/비동기 모두 허용).
|
|
134
|
+
*/
|
|
135
|
+
declare function retry<T>(fn: (attempt: number) => Promise<T> | T, options?: RetryOptions): Promise<T>;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* API 호출 실패 (실패 봉투 / HTTP 에러) 시 throw 되는 에러.
|
|
139
|
+
*
|
|
140
|
+
* `traceId` 는 장애 문의 시 사용자에게 제시할 수 있는 상관관계 식별자다
|
|
141
|
+
* (contracts/trace.md). 응답 에코 헤더 값이 우선이며, 에코를 읽을 수 없으면
|
|
142
|
+
* (예: CORS 에서 Access-Control-Expose-Headers 미설정) 요청에 부착해 보낸 값을 쓴다.
|
|
143
|
+
*/
|
|
144
|
+
declare class ApiError extends Error {
|
|
145
|
+
/** 결과 코드 — 봉투의 code, 비봉투 응답이면 HTTP 상태 코드 문자열. */
|
|
146
|
+
readonly code: string;
|
|
147
|
+
/** HTTP 상태 코드. */
|
|
148
|
+
readonly status: number;
|
|
149
|
+
/** 이 요청의 traceId (에코 헤더 우선, 없으면 발신 값). */
|
|
150
|
+
readonly traceId: string;
|
|
151
|
+
/**
|
|
152
|
+
* 실패 봉투의 data (예: 검증 오류 봉투 — {@link import("./types").isValidationErrorData}
|
|
153
|
+
* 로 판별). 비봉투 에러/data 부재 시 undefined.
|
|
154
|
+
*/
|
|
155
|
+
readonly data?: unknown;
|
|
156
|
+
/**
|
|
157
|
+
* 응답 `Retry-After` 헤더의 대기 ms — throw 시점에 {@link parseRetryAfterMs} 로
|
|
158
|
+
* 파싱한 값. 헤더 부재/해석 불가면 null.
|
|
159
|
+
*/
|
|
160
|
+
readonly retryAfterMs: number | null;
|
|
161
|
+
/**
|
|
162
|
+
* JSON 파싱 실패 시(strictJson=true, code "INVALID_JSON") 원문 본문 —
|
|
163
|
+
* 진단용으로 앞 {@link MAX_RAW_TEXT_LENGTH}자까지 절단 보존. 그 외 경로는 undefined.
|
|
164
|
+
*/
|
|
165
|
+
readonly rawText?: string;
|
|
166
|
+
constructor(args: {
|
|
167
|
+
code: string;
|
|
168
|
+
message: string;
|
|
169
|
+
status: number;
|
|
170
|
+
traceId: string;
|
|
171
|
+
data?: unknown;
|
|
172
|
+
retryAfterMs?: number | null;
|
|
173
|
+
rawText?: string;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* apiClient 재시도 설정 — 요청을 core {@link retry} 로 감싼다.
|
|
178
|
+
*
|
|
179
|
+
* RetryOptions 중 shouldRetry/overrideDelay/signal 은 클라이언트가 내부 관리한다:
|
|
180
|
+
* overrideDelay 는 Retry-After 하한 적용으로 고정하고, signal 은 요청의
|
|
181
|
+
* `init.signal` 을 fetch 와 재시도 대기 양쪽에 전달한다 (abort 시 대기 즉시 중단).
|
|
182
|
+
*/
|
|
183
|
+
interface ApiClientRetryOptions extends Omit<RetryOptions, "shouldRetry" | "overrideDelay" | "signal"> {
|
|
184
|
+
/** 재시도 판정. 기본: (ApiError && isRetryableStatus(status)) || TypeError(네트워크 오류). */
|
|
185
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
186
|
+
/**
|
|
187
|
+
* 재시도 허용 메서드(대문자). 기본 ["GET","HEAD","OPTIONS","PUT","DELETE"] —
|
|
188
|
+
* 비멱등 POST/PATCH 는 옵트인. 커스텀 shouldRetry 를 줘도 이 게이트는 유지된다.
|
|
189
|
+
*/
|
|
190
|
+
methods?: string[];
|
|
191
|
+
}
|
|
192
|
+
/** 관측 훅 onRequest 인자 — 논리 호출(재시도 묶음) 시작 시점의 요청 정보. */
|
|
193
|
+
interface ApiRequestInfo {
|
|
194
|
+
/** HTTP 메서드 (대문자 정규화). */
|
|
195
|
+
method: string;
|
|
196
|
+
/** 호출 시 넘긴 path 원문 (baseUrl 조인 전). */
|
|
197
|
+
path: string;
|
|
198
|
+
/** 이 논리 호출의 발신 traceId. */
|
|
199
|
+
traceId: string;
|
|
200
|
+
}
|
|
201
|
+
/** 관측 훅 onResponse 인자 — 최종 성공 확정 시점의 응답 정보. */
|
|
202
|
+
interface ApiResponseInfo {
|
|
203
|
+
method: string;
|
|
204
|
+
path: string;
|
|
205
|
+
/** 확정 응답의 traceId (에코 헤더 우선, 없으면 발신 값). */
|
|
206
|
+
traceId: string;
|
|
207
|
+
/** 최종 HTTP 상태 코드. */
|
|
208
|
+
status: number;
|
|
209
|
+
/** onRequest 시점 → 최종 확정까지의 소요 ms (재시도 대기 포함). */
|
|
210
|
+
durationMs: number;
|
|
211
|
+
}
|
|
212
|
+
/** 관측 훅 onError 인자 — 최종 실패 확정 시점의 오류 정보. */
|
|
213
|
+
interface ApiErrorInfo {
|
|
214
|
+
method: string;
|
|
215
|
+
path: string;
|
|
216
|
+
/** ApiError 면 그 traceId, 네트워크 오류 등이면 발신 값. */
|
|
217
|
+
traceId: string;
|
|
218
|
+
/** ApiError 면 HTTP 상태, 네트워크 오류(fetch TypeError 등)면 undefined. */
|
|
219
|
+
status?: number;
|
|
220
|
+
/** onRequest 시점 → 최종 확정까지의 소요 ms (재시도 대기 포함). */
|
|
221
|
+
durationMs: number;
|
|
222
|
+
/** 최종 확정된 오류 원본. */
|
|
223
|
+
error: unknown;
|
|
224
|
+
}
|
|
225
|
+
interface ApiClientConfig {
|
|
226
|
+
/**
|
|
227
|
+
* API 서버 base URL (예: "http://localhost:53002"). 끝 슬래시 유무 무관 —
|
|
228
|
+
* path 와의 경계 슬래시가 자동 단일화된다 ("http://host/" + "/v1" → "http://host/v1").
|
|
229
|
+
* 상대 baseUrl("/api")도 지원.
|
|
230
|
+
*/
|
|
231
|
+
baseUrl: string;
|
|
232
|
+
/** 토큰 공급자. 지정 시 반환값이 truthy 면 `Authorization: Bearer <token>` 부착. */
|
|
233
|
+
getToken?: () => string | null | undefined;
|
|
234
|
+
/** 401 수신 시 throw 직전에 호출되는 콜백 (로그아웃/리다이렉트 등 소비자 정책 주입). */
|
|
235
|
+
onUnauthorized?: (error: ApiError) => void;
|
|
236
|
+
/** fetch 구현체 주입 (테스트용). 기본 globalThis.fetch. */
|
|
237
|
+
fetchImpl?: typeof fetch;
|
|
238
|
+
/** traceId 헤더명. 기본 "X-Trace-Id" (contracts/trace.md). */
|
|
239
|
+
traceIdHeader?: string;
|
|
240
|
+
/**
|
|
241
|
+
* 지정 시 요청을 core retry() 로 감싼다. 미지정 = 재시도 없음(현행 동작).
|
|
242
|
+
*
|
|
243
|
+
* - traceId 는 논리 호출당 1회 생성되어 전 시도에 동일 부착된다
|
|
244
|
+
* (재시도 묶음이 서버 로그에서 단일 trace 로 조회됨).
|
|
245
|
+
* - 429/503 등의 Retry-After 헤더는 계산된 대기의 하한으로 자동 존중된다.
|
|
246
|
+
* - 1회성 body(ReadableStream/FormData)는 재전송이 불가하므로 재시도 없이 1회 실행.
|
|
247
|
+
* - onUnauthorized 는 시도마다 발화될 수 있다 (기본 판정에선 401 비재시도라 1회).
|
|
248
|
+
*/
|
|
249
|
+
retry?: ApiClientRetryOptions;
|
|
250
|
+
/**
|
|
251
|
+
* true 면 성공(2xx) 응답의 본문이 유효한 JSON 이 아닐 때(본문 읽기 `text()` 실패 포함)
|
|
252
|
+
* 조용히 null 을 반환하지 않고 code "INVALID_JSON" 의 ApiError 를 throw 한다
|
|
253
|
+
* (INVALID_JSON 은 클라이언트 로컬 코드 — 서버 봉투 enum(error-codes.yaml) 밖).
|
|
254
|
+
* 원문은 확보된 경우에 한해 {@link ApiError.rawText} 에 2048자 상한으로 절단 보존
|
|
255
|
+
* (읽기 실패 경로는 원문이 없어 undefined). 빈 본문(204 등)은 본문 부재이지 파싱 실패가
|
|
256
|
+
* 아니므로 throw 하지 않고 null 을 반환한다. 비-2xx 실패 경로에는 적용되지 않는다(현행 유지).
|
|
257
|
+
* 기본 false — 비-JSON 2xx 본문은 종전대로 null 반환.
|
|
258
|
+
*/
|
|
259
|
+
strictJson?: boolean;
|
|
260
|
+
/**
|
|
261
|
+
* 관측 훅 — 논리 호출(재시도 묶음) 시작 시 1회 발화 (첫 시도 fetch 직전).
|
|
262
|
+
*
|
|
263
|
+
* 훅 예외 정책 (onResponse/onError 공통): 훅이 던진 예외는 삼켜져 본 요청의
|
|
264
|
+
* 성공/실패 결과에 영향을 주지 않는다. 단 클라이언트 인스턴스당 최초 1회만
|
|
265
|
+
* console.warn 으로 알린 뒤 이후 침묵한다(로그 폭주 방지).
|
|
266
|
+
*/
|
|
267
|
+
onRequest?: (info: ApiRequestInfo) => void;
|
|
268
|
+
/** 관측 훅 — 재시도 포함 최종 성공 확정 시 1회 발화. 예외 정책은 {@link onRequest} 참조. */
|
|
269
|
+
onResponse?: (info: ApiResponseInfo) => void;
|
|
270
|
+
/** 관측 훅 — 재시도 포함 최종 실패 확정 시 1회 발화. 예외 정책은 {@link onRequest} 참조. */
|
|
271
|
+
onError?: (info: ApiErrorInfo) => void;
|
|
272
|
+
}
|
|
273
|
+
/** 성공 결과 + 메타 (traceId 등). requestWithMeta 의 반환형. */
|
|
274
|
+
interface ApiResult<T> {
|
|
275
|
+
data: T;
|
|
276
|
+
/** 이 요청의 traceId (에코 헤더 우선, 없으면 발신 값). */
|
|
277
|
+
traceId: string;
|
|
278
|
+
status: number;
|
|
279
|
+
/**
|
|
280
|
+
* 본문 미소비 원본 Response — 파싱은 `clone()` 사본으로 하므로 `text()`/`json()`
|
|
281
|
+
* 재호출이 가능하다. 단 fetchImpl 이 `clone()` 미지원(비표준 mock)이면 종전대로
|
|
282
|
+
* 소비된 상태다.
|
|
283
|
+
*/
|
|
284
|
+
response: Response;
|
|
285
|
+
}
|
|
286
|
+
interface ApiClient {
|
|
287
|
+
/**
|
|
288
|
+
* 요청을 보내고 CommonResponse 봉투면 data 를 언랩해 반환. 비봉투 JSON 은 그대로 반환.
|
|
289
|
+
* 성공(2xx)이지만 본문이 JSON 이 아니면(text/plain, 204 등 빈 본문 포함) null 이
|
|
290
|
+
* 반환된다(종전 계약 유지) — 단 strictJson=true 면 비어 있지 않은 비-JSON 본문은
|
|
291
|
+
* 대신 INVALID_JSON ApiError 를 throw 한다(빈 본문은 본문 부재라 계속 null).
|
|
292
|
+
*/
|
|
293
|
+
request<T>(path: string, init?: RequestInit): Promise<T>;
|
|
294
|
+
/** request 와 동일하되 traceId·status·원본 Response 메타를 함께 반환. */
|
|
295
|
+
requestWithMeta<T>(path: string, init?: RequestInit): Promise<ApiResult<T>>;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* CommonResponse 봉투를 이해하는 fetch 래퍼 팩토리.
|
|
299
|
+
*
|
|
300
|
+
* 동작 (contracts/common-response.schema.json, trace.md):
|
|
301
|
+
* - 매 요청에 traceId 를 생성해 traceIdHeader(기본 X-Trace-Id)로 부착한다.
|
|
302
|
+
* - 응답이 CommonResponse 봉투면 success 검사 후 data 를 언랩한다
|
|
303
|
+
* (data 부재 시 undefined). 비봉투 JSON 은 그대로 반환.
|
|
304
|
+
* - 실패 봉투 / HTTP 에러는 code·message·traceId 를 담은 ApiError 를 throw.
|
|
305
|
+
* - 401 은 throw 직전에 onUnauthorized 콜백을 호출한다.
|
|
306
|
+
* - retry 지정 시 요청을 core retry() 로 감싼다 ({@link ApiClientRetryOptions} —
|
|
307
|
+
* 멱등 메서드 기본, Retry-After 하한, 시도 간 동일 traceId).
|
|
308
|
+
*
|
|
309
|
+
* 저장소 접근·경로·이벤트명 하드코딩 없음 — 전부 config 주입.
|
|
310
|
+
*/
|
|
311
|
+
declare function createApiClient(config: ApiClientConfig): ApiClient;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* SSE 스트리밍 프레임 파서.
|
|
315
|
+
*
|
|
316
|
+
* 원천: contracts/sse-frames.md (수동 동기화)
|
|
317
|
+
*
|
|
318
|
+
* 프레임은 단일 `data:` 라인 + 빈 줄(`\n\n` 구분). 페이로드는 종료 마커
|
|
319
|
+
* `[DONE]`(리터럴) 을 제외하면 전부 키로 판별하는 단일 키 JSON 객체:
|
|
320
|
+
* conversationId / sources / delta / error.
|
|
321
|
+
*
|
|
322
|
+
* 주의: 프레임 구분자는 계약상 LF(`\n\n`) 고정 — CRLF(`\r\n\r\n`) 로 내려오는
|
|
323
|
+
* 스트림은 지원하지 않는다 (프록시 등이 개행을 CRLF 로 정규화하면 프레임이
|
|
324
|
+
* 분리되지 않아 유실됨. 서버 계약이 LF 를 보장할 때만 사용할 것).
|
|
325
|
+
*/
|
|
326
|
+
/** sources 프레임의 요소 (camelCase — sse-frames.md 의 웹/외부 응답 계약). */
|
|
327
|
+
interface SseSource {
|
|
328
|
+
/** 문서 출처 문자열 (없으면 url 폴백, 그것도 없으면 ""). */
|
|
329
|
+
source: string;
|
|
330
|
+
/** 본문 스니펫 (서버측 길이 상한으로 절단). */
|
|
331
|
+
content: string;
|
|
332
|
+
/** 관련도 점수, 소수 4자리 반올림. 원본 부재 시 0.0. */
|
|
333
|
+
score: number;
|
|
334
|
+
dataSourceId: number | null;
|
|
335
|
+
}
|
|
336
|
+
/** 프레임 판별 유니온 — 모르는 키/파싱 실패는 "skip" (관용적 읽기, non-breaking 확장 허용). */
|
|
337
|
+
type SseFrameEvent = {
|
|
338
|
+
type: "conversationId";
|
|
339
|
+
id: number;
|
|
340
|
+
} | {
|
|
341
|
+
type: "sources";
|
|
342
|
+
sources: SseSource[];
|
|
343
|
+
} | {
|
|
344
|
+
type: "delta";
|
|
345
|
+
delta: string;
|
|
346
|
+
} | {
|
|
347
|
+
type: "error";
|
|
348
|
+
message: string;
|
|
349
|
+
} | {
|
|
350
|
+
type: "done";
|
|
351
|
+
} | {
|
|
352
|
+
type: "skip";
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* 프레임 1개(구분자 `\n\n` 제거 후의 문자열)를 파싱한다. 순수 함수.
|
|
356
|
+
*
|
|
357
|
+
* 파싱 규칙 (sse-frames.md "소비자 파싱 규칙"):
|
|
358
|
+
* - `data:` 라인 부재 → skip (에러 아님)
|
|
359
|
+
* - 페이로드 리터럴 `[DONE]` → done
|
|
360
|
+
* - 키 검사 순서: conversationId(number) → sources(array) → delta(string) → error(string)
|
|
361
|
+
* - 일치 키 없음/JSON 파싱 실패 → 조용히 skip
|
|
362
|
+
*/
|
|
363
|
+
declare function parseSseFrame(frame: string): SseFrameEvent;
|
|
364
|
+
interface SseCallbacks {
|
|
365
|
+
/** 스트림 첫 프레임의 대화 세션 id (search-api 가 prepend). */
|
|
366
|
+
onConversationId?: (id: number) => void;
|
|
367
|
+
/** RAG 근거 문서 — delta 보다 먼저 1회. */
|
|
368
|
+
onSources?: (sources: SseSource[]) => void;
|
|
369
|
+
/** 답변 텍스트 증분 — 0회 이상 반복. */
|
|
370
|
+
onDelta?: (delta: string) => void;
|
|
371
|
+
/** in-band 오류. 이후 [DONE] 이 따라온다. error 이후의 delta 는 전달되지 않는다. */
|
|
372
|
+
onError?: (message: string) => void;
|
|
373
|
+
/** [DONE] 종료 마커 수신. */
|
|
374
|
+
onDone?: () => void;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* fetch Response 의 body 스트림을 UTF-8 디코드 → `\n\n` 프레임 분리 → 콜백 호출.
|
|
378
|
+
*
|
|
379
|
+
* - 청크 경계가 프레임/멀티바이트 문자 중간에 걸려도 안전 (버퍼 누적 + streaming decode).
|
|
380
|
+
* - error 프레임 수신 후에도 [DONE] 까지 읽되, 이후 delta 는 무시한다 (sse-frames.md 규칙 5).
|
|
381
|
+
* - [DONE] 수신 시 즉시 resolve. 스트림이 [DONE] 없이 끝나도 남은 버퍼를 처리하고 resolve.
|
|
382
|
+
*/
|
|
383
|
+
declare function readSseStream(response: {
|
|
384
|
+
body: ReadableStream<Uint8Array> | null;
|
|
385
|
+
}, callbacks: SseCallbacks): Promise<void>;
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* JWT 페이로드 디코더 (contracts/jwt-claims.md).
|
|
389
|
+
*
|
|
390
|
+
* ⚠️ **서명을 검증하지 않는다.** base64url 디코드만 수행하므로 표시·만료 판단 등
|
|
391
|
+
* 신뢰가 필요 없는 클라이언트 용도 전용이다. 인가 판단은 반드시 서버 검증을 거칠 것.
|
|
392
|
+
*/
|
|
393
|
+
/**
|
|
394
|
+
* JWT 의 페이로드(두 번째 세그먼트)를 base64url 디코드해 JSON 객체로 반환한다.
|
|
395
|
+
* 세그먼트 부재·디코드/파싱 실패 시 null. 서명 검증은 하지 않는다 (모듈 주석 참조).
|
|
396
|
+
*/
|
|
397
|
+
declare function decodeJwtPayload(token: string): Record<string, unknown> | null;
|
|
398
|
+
/**
|
|
399
|
+
* exp 클레임(NumericDate, 초)을 epoch 밀리초로 반환. 파싱 실패/클레임 부재 시 null.
|
|
400
|
+
* 서명 검증은 하지 않는다.
|
|
401
|
+
*/
|
|
402
|
+
declare function getTokenExpiry(token: string): number | null;
|
|
403
|
+
/**
|
|
404
|
+
* 토큰 만료 여부. **디코드 불가/exp 부재도 만료로 취급**한다 (fail-closed).
|
|
405
|
+
* 서명 검증은 하지 않는다 — 위조 여부는 알 수 없고 시각만 본다.
|
|
406
|
+
*
|
|
407
|
+
* @param nowMs 비교 기준 시각 (기본 Date.now(), 테스트용 주입 가능)
|
|
408
|
+
*/
|
|
409
|
+
declare function isTokenExpired(token: string, nowMs?: number): boolean;
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* API 키 등 시크릿 문자열 마스킹.
|
|
413
|
+
*
|
|
414
|
+
* 원천: java/rscc-common-core `MaskingUtils.maskSecret` 와 동일 규칙 (수동 동기화).
|
|
415
|
+
*
|
|
416
|
+
* 규칙:
|
|
417
|
+
* - null/undefined/blank(공백뿐) — 그대로 반환
|
|
418
|
+
* - 8자 이하 — 전량 `'*'` (앞뒤를 노출하면 원문 대부분이 드러나므로)
|
|
419
|
+
* - 9자 이상 — 앞 4자 + 뒤 4자만 노출, 가운데는 **항상 4개의 `'•'`** 로 고정
|
|
420
|
+
* (실제 길이만큼 채우지 않는 이유: 마스킹 결과 길이에서 시크릿의 실제 길이 누설 방지)
|
|
421
|
+
*/
|
|
422
|
+
declare function maskSecret(secret: string): string;
|
|
423
|
+
declare function maskSecret(secret: null): null;
|
|
424
|
+
declare function maskSecret(secret: undefined): undefined;
|
|
425
|
+
declare function maskSecret(secret: string | null | undefined): string | null | undefined;
|
|
426
|
+
/**
|
|
427
|
+
* 이름 마스킹 (홍길동 → 홍*동, 김철 → 김*). 원천: Java `MaskingUtils.maskName`.
|
|
428
|
+
* null/blank/외자(1글자)는 원본 반환. 2글자는 첫 글자 + `*`, 3글자 이상은
|
|
429
|
+
* 첫·마지막 글자만 살리고 가운데를 전부 `*`.
|
|
430
|
+
*/
|
|
431
|
+
declare function maskName(name: string): string;
|
|
432
|
+
/**
|
|
433
|
+
* 휴대폰 마스킹 (010-1234-5678 → 010-****-5678). 원천: Java `MaskingUtils.maskPhone`.
|
|
434
|
+
* 하이픈 유무와 무관하게 가운데 3~4자리를 치환. null/blank 는 원본.
|
|
435
|
+
*
|
|
436
|
+
* 주의: 정규식은 부분 문자열 단위로 매칭·치환된다(Java `replaceAll` 파리티).
|
|
437
|
+
* 형식이 보장되지 않는 값을 로그에 노출할 때 반환값을 그대로 신뢰하지 말 것.
|
|
438
|
+
*/
|
|
439
|
+
declare function maskPhone(phone: string): string;
|
|
440
|
+
/**
|
|
441
|
+
* 이메일 마스킹 (test1234@d.com → te******@d.com). 원천: Java `MaskingUtils.maskEmail`.
|
|
442
|
+
* `@` 없으면 원본. 아이디 2글자 이하면 첫 글자만 노출. `@` 다중 시 Java 와 동일하게
|
|
443
|
+
* 첫 `@` 뒤 세그먼트를 도메인으로 취한다.
|
|
444
|
+
*/
|
|
445
|
+
declare function maskEmail(email: string): string;
|
|
446
|
+
/**
|
|
447
|
+
* 카드번호 마스킹 (1234-5678-1234-5678 → 1234-****-****-5678).
|
|
448
|
+
* 원천: Java `MaskingUtils.maskCardNumber`. 하이픈 유무 무관, 가운데 8자리 치환.
|
|
449
|
+
* null/blank 는 원본. maskPhone 과 동일한 부분 매칭 주의사항이 적용된다.
|
|
450
|
+
*/
|
|
451
|
+
declare function maskCardNumber(cardNumber: string): string;
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* 오프셋 없는 LocalDateTime 와이어 직렬화 헬퍼 — contracts/datetime.md.
|
|
455
|
+
*
|
|
456
|
+
* 핵심 규약: **와이어에는 시간대/오프셋을 절대 싣지 않는다.** 값은 서버 기준
|
|
457
|
+
* (운영 전제: KST) 벽시계 시각으로 해석된다.
|
|
458
|
+
*
|
|
459
|
+
* ⚠️ Z 스큐 경고(datetime.md): `new Date(x).toISOString()` 은 UTC 로 변환해 끝에
|
|
460
|
+
* `Z` 를 붙인다. 서버(`LocalDateTime`)는 그 `Z` 를 조용히 버리고 나머지를 KST 벽시계로
|
|
461
|
+
* 재해석해 **9시간 스큐**를 만든다. 이 모듈의 직렬화 함수는 오프셋을 절대 만들지 않아
|
|
462
|
+
* 그 경로를 원천 차단한다. `<input type="datetime-local">` 값은 이미 오프셋 없는
|
|
463
|
+
* 형식이므로 그대로 보내면 되고, Date 객체를 보내야 할 땐 {@link toWireDateTime} 을 쓴다.
|
|
464
|
+
*/
|
|
465
|
+
/**
|
|
466
|
+
* Date 의 **로컬** 필드를 `yyyy-MM-ddTHH:mm:ss`(오프셋 없음)로 직렬화한다.
|
|
467
|
+
* `toISOString()`(UTC/Z) 대신 이 함수를 쓸 것 — 그래야 스큐가 생기지 않는다.
|
|
468
|
+
*/
|
|
469
|
+
declare function toWireDateTime(date: Date): string;
|
|
470
|
+
/** Date 의 로컬 날짜를 `yyyy-MM-dd` 로 직렬화한다. */
|
|
471
|
+
declare function toWireDate(date: Date): string;
|
|
472
|
+
/**
|
|
473
|
+
* 와이어 datetime 문자열(`yyyy-MM-ddTHH:mm[:ss[.fff]]`)을 **로컬 시간대**로 해석한
|
|
474
|
+
* Date 로 파싱한다(사용자·서버가 모두 KST 라는 전제). 끝의 `Z`/오프셋은 무시하고
|
|
475
|
+
* 벽시계 숫자만 취한다(Java Jackson lenient 파리티). 형식 불일치 시 throw.
|
|
476
|
+
*/
|
|
477
|
+
declare function parseWireDateTime(value: string): Date;
|
|
478
|
+
/**
|
|
479
|
+
* 문자열 끝의 `Z` 또는 `±HH:MM`/`±HHMM` 오프셋을 제거한다(발신 전 sanitize).
|
|
480
|
+
* 시각 자체는 변환하지 않고 벽시계 부분만 남긴다. 오프셋이 없으면 원본을 그대로 반환.
|
|
481
|
+
* 날짜부(`T` 앞)의 `-` 는 오프셋으로 오인하지 않는다.
|
|
482
|
+
*/
|
|
483
|
+
declare function stripZone(value: string): string;
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* 한글 초성 변환 — 자동완성 초성 검색용.
|
|
487
|
+
*
|
|
488
|
+
* 원천: Python `rscc_common.text.chosung` 및 r-search `Chosung.java` 와 로직 파리티
|
|
489
|
+
* (호환 자모 19자, 0xAC00 기준 음절 분해, 공백/호환 모음 스킵, 그 외 소문자화).
|
|
490
|
+
*
|
|
491
|
+
* 사용자가 키보드로 단독 자음을 입력하면 호환 자모(U+3131~U+314E, 예: ㅅ=U+3145)가
|
|
492
|
+
* 나오므로, 음절에서 추출하는 초성도 같은 호환 자모로 맞춘다(조합용 자모 U+1100대 아님).
|
|
493
|
+
*
|
|
494
|
+
* 파리티 주의: 비-한글 폴백 소문자화는 JS `String.toLowerCase`(유니코드 전체 매핑)라
|
|
495
|
+
* Java `Character.toLowerCase`(단순 매핑)와 일부 특수문자에서 결과가 다를 수 있으나,
|
|
496
|
+
* 한글 초성 검색 용도에서는 실질 영향이 없다.
|
|
497
|
+
*/
|
|
498
|
+
/**
|
|
499
|
+
* 텍스트를 초성 문자열로 변환한다.
|
|
500
|
+
* - 완성 음절(가~힣) → 초성 호환 자모
|
|
501
|
+
* - 이미 호환 자음(ㄱ~ㅎ) → 그대로
|
|
502
|
+
* - 공백·호환 모음 단독(조합 중 전이) → 무시
|
|
503
|
+
* - 그 외(ASCII 등) → 소문자
|
|
504
|
+
*/
|
|
505
|
+
declare function toChosung(s: string): string;
|
|
506
|
+
/** 입력에 호환 자모 자음(ㄱ~ㅎ)이 하나라도 있으면 초성 질의로 간주한다. */
|
|
507
|
+
declare function isChosungQuery(s: string): boolean;
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* TTL 캐시 + single-flight. 런타임 의존성 0 (native Map/Promise 만).
|
|
511
|
+
*
|
|
512
|
+
* Java `com.rscc.common.cache.TtlCache` / Python `rscc_common.cache` 와 시맨틱 동일
|
|
513
|
+
* (3언어 공유 벡터 CS-01~08 — 각 언어 테스트에 수동 동기화):
|
|
514
|
+
*
|
|
515
|
+
* - 만료 판정: `now - createdAt >= ttl` 이면 만료 (경계 시각 도달 즉시 만료).
|
|
516
|
+
* **lazy expiry** — 만료 항목은 접근/삽입 시점에만 제거한다 (백그라운드 타이머 없음).
|
|
517
|
+
* - 용량: 삽입 후 maxEntries 초과 시 (1) 만료 항목 전량 제거 → (2) 여전히 초과면
|
|
518
|
+
* 삽입 순서(단조 seq) 최소 항목부터 제거. 덮어쓰기(set)는 createdAt·seq 를
|
|
519
|
+
* 갱신한다(재삽입 시맨틱).
|
|
520
|
+
* - single-flight: 동일 키 동시 get 은 로더 1회 실행 후 결과 공유. **실패는 캐시하지
|
|
521
|
+
* 않음** — 대기자 전원에게 동일 오류 전파 후 다음 호출은 로더를 재실행한다.
|
|
522
|
+
* - null/undefined 는 캐시하지 않음 (로더 반환은 그대로 반환·저장 없음, 명시 set 은
|
|
523
|
+
* TypeError — 부정 캐시 방지. Java put 의 NullPointerException 대칭).
|
|
524
|
+
* - 명시적 비범위: LRU 액세스-순 갱신, hit/miss 통계, 제거 리스너, 엔트리별 TTL.
|
|
525
|
+
*/
|
|
526
|
+
interface TtlCacheOptions {
|
|
527
|
+
/** 항목 생존 시간(ms). 필수 > 0 (아니면 RangeError). */
|
|
528
|
+
ttlMs: number;
|
|
529
|
+
/** 최대 엔트리 수. 필수 > 0 (아니면 RangeError). */
|
|
530
|
+
maxEntries: number;
|
|
531
|
+
/** 현재 시각(ms) 공급자. 기본 Date.now — 결정적 테스트용. */
|
|
532
|
+
now?: () => number;
|
|
533
|
+
}
|
|
534
|
+
interface TtlCache<K, V> {
|
|
535
|
+
/**
|
|
536
|
+
* single-flight 로더 경유 조회 — 동일 키 로딩이 진행 중이면 그 Promise 를 공유한다.
|
|
537
|
+
* 로더 실패는 캐시하지 않는다 (대기자 전원 동일 오류, 다음 호출은 로더 재실행).
|
|
538
|
+
* 로더가 null/undefined 를 반환하면 그대로 반환하되 저장하지 않는다.
|
|
539
|
+
*/
|
|
540
|
+
get(key: K, loader: (key: K) => Promise<V> | V): Promise<V>;
|
|
541
|
+
/** 로더 없이 조회 — 미보유/만료 시 undefined. */
|
|
542
|
+
peek(key: K): V | undefined;
|
|
543
|
+
/**
|
|
544
|
+
* 저장 — 기존 키 덮어쓰기는 createdAt·seq 를 갱신한다 (재삽입 시맨틱).
|
|
545
|
+
* null/undefined 는 TypeError (조회 불가 유령 엔트리 방지 — 부정 캐시 방지).
|
|
546
|
+
*/
|
|
547
|
+
set(key: K, value: V): void;
|
|
548
|
+
/** 제거. 항목이 있어 제거됐으면 true. */
|
|
549
|
+
delete(key: K): boolean;
|
|
550
|
+
/** 전량 제거. 진행 중 로딩(inflight)은 취소하지 않는다 — 완료 시 결과가 저장될 수 있다. */
|
|
551
|
+
clear(): void;
|
|
552
|
+
/** 현재 엔트리 수 — 만료됐지만 아직 정리되지 않은 항목 포함 (lazy expiry, 정리 전 기준). */
|
|
553
|
+
size(): number;
|
|
554
|
+
}
|
|
555
|
+
/** TTL 캐시 팩토리. ttlMs/maxEntries 가 양수가 아니면 RangeError 를 throw 한다. */
|
|
556
|
+
declare function createTtlCache<K, V>(options: TtlCacheOptions): TtlCache<K, V>;
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* 경량 서킷 브레이커. 런타임 의존성 0 (native 만).
|
|
560
|
+
*
|
|
561
|
+
* Java `com.rscc.common.util.CircuitBreaker` / Python `rscc_common.circuit_breaker` 와
|
|
562
|
+
* 시맨틱 동일 (3언어 공유 벡터 CB-01~09 — 각 언어 테스트에 수동 동기화. Python 은
|
|
563
|
+
* 시간 단위가 초 — `초 × 1000 == ms`).
|
|
564
|
+
*
|
|
565
|
+
* - **연속(consecutive) 실패 카운트**: `onFailure()` 가 failureThreshold 에 도달(`>=`)하면
|
|
566
|
+
* OPEN. `onSuccess()` 는 카운터를 0 으로 리셋한다 (슬라이딩 윈도 비채택 — 단순성).
|
|
567
|
+
* - **lazy 전이**: OPEN → HALF_OPEN 전이는 `allowRequest()` 호출 시점에 판정한다
|
|
568
|
+
* (백그라운드 타이머 없음 — TtlCache lazy expiry 톤). openDurationMs 경과(`>=`) 후
|
|
569
|
+
* 첫 `allowRequest()` 가 전이와 프로브 슬롯 획득을 원자적으로 한 동작으로 수행한다.
|
|
570
|
+
* - **HALF_OPEN 프로브 1개**: 프로브가 진행 중이면 나머지 `allowRequest()` 는 false.
|
|
571
|
+
* 프로브 성공 → CLOSED(카운터 0), 프로브 실패 → OPEN 재진입(openedAt 갱신 —
|
|
572
|
+
* 전체 openDurationMs 재대기).
|
|
573
|
+
* - **결과 보고 규율**: `allowRequest()==true` 를 받은 호출만 `onSuccess()`/`onFailure()`
|
|
574
|
+
* 를 **정확히 1회** 호출한다 (try/finally 로 보고 보장 권장 — 보고 누락은 아래 프로브
|
|
575
|
+
* 보류를 무기한 지속시킨다). OPEN 대기 중 늦게 도착한 보고는 상태·openedAt·카운터를
|
|
576
|
+
* 바꾸지 않고(no-op — CB-08) 미보고 그랜트 잔량만 소진한다.
|
|
577
|
+
* - **스테일 그랜트 격리**: CLOSED 시절 그랜트가 아직 보고되지 않은 동안에는
|
|
578
|
+
* openDurationMs 가 경과해도 HALF_OPEN 전이(프로브)가 보류된다 — 스테일 보고가 프로브
|
|
579
|
+
* 결과로 오인되는 것을 막아 HALF_OPEN 중 도착하는 보고는 항상 프로브의 것이다 (CB-09).
|
|
580
|
+
*
|
|
581
|
+
* @example 서킷 + 재시도 조합 — 시도 직전 allowRequest 게이트, 결과를 브레이커에 보고.
|
|
582
|
+
* OPEN 차단 오류는 shouldRetry 에서 비재시도로 걸러 즉시 실패시킨다(빠른 실패 유지).
|
|
583
|
+
* ```ts
|
|
584
|
+
* const cb = createCircuitBreaker({ failureThreshold: 5, openDurationMs: 30_000 });
|
|
585
|
+
* const circuitOpen = new Error("서킷 OPEN — 요청 차단");
|
|
586
|
+
* await retry(
|
|
587
|
+
* async () => {
|
|
588
|
+
* if (!cb.allowRequest()) throw circuitOpen;
|
|
589
|
+
* try {
|
|
590
|
+
* const r = await call();
|
|
591
|
+
* cb.onSuccess();
|
|
592
|
+
* return r;
|
|
593
|
+
* } catch (error) {
|
|
594
|
+
* cb.onFailure();
|
|
595
|
+
* throw error;
|
|
596
|
+
* }
|
|
597
|
+
* },
|
|
598
|
+
* {
|
|
599
|
+
* shouldRetry: (error) => error !== circuitOpen, // OPEN 차단은 즉시 실패
|
|
600
|
+
* onRetry: (error, attempt, delayMs) => console.warn(`재시도 ${attempt} (${delayMs}ms)`),
|
|
601
|
+
* }
|
|
602
|
+
* );
|
|
603
|
+
* ```
|
|
604
|
+
*/
|
|
605
|
+
type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
|
|
606
|
+
interface CircuitBreakerOptions {
|
|
607
|
+
/** OPEN 전이 임계 — 연속 실패가 이 값에 도달(`>=`)하면 OPEN. 필수 양의 정수 (아니면 RangeError). */
|
|
608
|
+
failureThreshold: number;
|
|
609
|
+
/** OPEN 유지 시간(ms) — 경과(`>=`) 후 첫 allowRequest() 가 HALF_OPEN 전이. 필수 유한 양수 (아니면 RangeError). */
|
|
610
|
+
openDurationMs: number;
|
|
611
|
+
/** 현재 시각(ms) 공급자. 기본 Date.now — 결정적 테스트용. */
|
|
612
|
+
now?: () => number;
|
|
613
|
+
}
|
|
614
|
+
interface CircuitBreaker {
|
|
615
|
+
/**
|
|
616
|
+
* 요청 통과 여부 — CLOSED 는 true, OPEN 대기 중 false, openDurationMs 경과 시
|
|
617
|
+
* HALF_OPEN 전이 + 프로브 슬롯 획득(true) — 단 CLOSED 시절 미보고 그랜트가 남아 있으면
|
|
618
|
+
* 전이를 보류하고 false (CB-09). HALF_OPEN 프로브 경합 시 나머지 false.
|
|
619
|
+
* true 를 받은 호출만 결과를 onSuccess()/onFailure() 로 보고해야 한다.
|
|
620
|
+
*/
|
|
621
|
+
allowRequest(): boolean;
|
|
622
|
+
/** 성공 보고 — CLOSED 카운터 리셋, HALF_OPEN 프로브 성공 시 CLOSED 복귀. OPEN 중엔 무시. */
|
|
623
|
+
onSuccess(): void;
|
|
624
|
+
/** 실패 보고 — CLOSED 연속 카운트 증가(임계 도달 시 OPEN), HALF_OPEN 프로브 실패 시 OPEN 재진입. OPEN 중엔 무시. */
|
|
625
|
+
onFailure(): void;
|
|
626
|
+
/** 현재 상태 조회 — lazy 전이 특성상 openDurationMs 경과 후에도 allowRequest() 전엔 OPEN 으로 보고된다. */
|
|
627
|
+
state(): CircuitState;
|
|
628
|
+
}
|
|
629
|
+
/** 서킷 브레이커 팩토리 (createTtlCache 관용). 옵션 검증 실패 시 RangeError 를 throw 한다. */
|
|
630
|
+
declare function createCircuitBreaker(options: CircuitBreakerOptions): CircuitBreaker;
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* 토큰버킷 레이트리미터. 런타임 의존성 0 (native 만).
|
|
634
|
+
*
|
|
635
|
+
* Java `com.rscc.common.util.TokenBucket` / Python `rscc_common.rate_limit.TokenBucket`
|
|
636
|
+
* 와 시맨틱 동일 (3언어 공유 벡터 RL-01~07 — 각 언어 테스트에 수동 동기화. Python 은
|
|
637
|
+
* 시간 단위가 초 — `초 × 1000 == ms`).
|
|
638
|
+
*
|
|
639
|
+
* - **연속(continuous) 리필**: `tokens = min(capacity, tokens + elapsed × refillPerSecond)`
|
|
640
|
+
* — tokens 는 실수 누적(절사 없음). 리필은 접근 시점에 lazy 계산한다(타이머 없음).
|
|
641
|
+
* - **초기 토큰 = capacity**: 콜드 스타트 버스트를 허용한다.
|
|
642
|
+
* - `n > capacity` 는 **항상 false** (예외 아님 — 영원히 충족 불가한 요구는 조용히 거부).
|
|
643
|
+
* - **시계 역행 방어**: `elapsed = max(0, now - last)` — 역행 구간은 리필 0 취급.
|
|
644
|
+
*/
|
|
645
|
+
interface TokenBucketOptions {
|
|
646
|
+
/** 버킷 용량(최대 버스트). 필수 양의 정수 (아니면 RangeError). */
|
|
647
|
+
capacity: number;
|
|
648
|
+
/** 초당 리필 토큰 수. 필수 유한 양수 (아니면 RangeError). */
|
|
649
|
+
refillPerSecond: number;
|
|
650
|
+
/** 현재 시각(ms) 공급자. 기본 Date.now — 결정적 테스트용. */
|
|
651
|
+
now?: () => number;
|
|
652
|
+
}
|
|
653
|
+
interface TokenBucket {
|
|
654
|
+
/**
|
|
655
|
+
* 토큰 n개 획득 시도 — 부족하면 대기 없이 즉시 false.
|
|
656
|
+
* n 기본 1, 양의 정수가 아니면 RangeError. `n > capacity` 는 항상 false (예외 없음).
|
|
657
|
+
*/
|
|
658
|
+
tryAcquire(n?: number): boolean;
|
|
659
|
+
}
|
|
660
|
+
/** 토큰버킷 팩토리 (createTtlCache 관용). 옵션 검증 실패 시 RangeError 를 throw 한다. */
|
|
661
|
+
declare function createTokenBucket(options: TokenBucketOptions): TokenBucket;
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* 동시성 제한 Bulkhead. 런타임 의존성 0 (native Promise 만).
|
|
665
|
+
*
|
|
666
|
+
* Java `com.rscc.common.util.Bulkhead` / Python `rscc_common.bulkhead` 와 시맨틱 동일
|
|
667
|
+
* (3언어 공유 벡터 BH-01~05 — 각 언어 테스트에 수동 동기화).
|
|
668
|
+
*
|
|
669
|
+
* - `maxConcurrent` 실행 슬롯 + `maxQueue`(기본 0) 대기 슬롯.
|
|
670
|
+
* - 실행 여유 → 즉시 진입, 대기 여유 → 슬롯이 빌 때까지 대기(FIFO), 둘 다 만석 →
|
|
671
|
+
* **즉시 거부** — {@link BulkheadFullError} 로 reject 하며(동기 throw 아님) 이때
|
|
672
|
+
* 작업 함수는 **호출되지 않는다** (BH-04).
|
|
673
|
+
* - 슬롯 해제는 finally 보장 — 작업이 예외로 끝나도 반환된다 (BH-03).
|
|
674
|
+
*/
|
|
675
|
+
/** Bulkhead 만석 즉시 거부 오류. Java `BulkheadFullException` / Python `BulkheadFullError` 패리티. */
|
|
676
|
+
declare class BulkheadFullError extends Error {
|
|
677
|
+
constructor(message: string);
|
|
678
|
+
}
|
|
679
|
+
interface BulkheadOptions {
|
|
680
|
+
/** 동시 실행 슬롯 수. 필수 양의 정수 (아니면 RangeError). */
|
|
681
|
+
maxConcurrent: number;
|
|
682
|
+
/** 대기 슬롯 수. 기본 0(대기 없이 즉시 거부), 0 이상 정수 (아니면 RangeError). */
|
|
683
|
+
maxQueue?: number;
|
|
684
|
+
}
|
|
685
|
+
interface Bulkhead {
|
|
686
|
+
/**
|
|
687
|
+
* async fn 래핑 — 획득→실행→해제(finally 보장). 거부는 reject(BulkheadFullError) —
|
|
688
|
+
* 동기 throw 아님. 거부 시 fn 은 호출되지 않는다.
|
|
689
|
+
*/
|
|
690
|
+
execute<T>(fn: () => Promise<T> | T): Promise<T>;
|
|
691
|
+
/** 현재 실행 중 작업 수 — 관측·테스트용. */
|
|
692
|
+
activeCount(): number;
|
|
693
|
+
/** 현재 대기 중 작업 수 — 관측·테스트용. */
|
|
694
|
+
queuedCount(): number;
|
|
695
|
+
}
|
|
696
|
+
/** Bulkhead 팩토리 (createTtlCache 관용). 옵션 검증 실패 시 RangeError 를 throw 한다. */
|
|
697
|
+
declare function createBulkhead(options: BulkheadOptions): Bulkhead;
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* 로그 인젝션 방지 sanitize. 런타임 의존성 0 (native 만).
|
|
701
|
+
*
|
|
702
|
+
* Java `com.rscc.common.util.LogSanitizer` / Python `rscc_common.log_sanitize` 와
|
|
703
|
+
* 규칙 동일 (3언어 공유 골든 벡터 LS-01~10 — 각 언어 테스트에 수동 동기화).
|
|
704
|
+
*
|
|
705
|
+
* 외부 입력(헤더·쿼리·본문 필드)을 로그 한 줄에 실을 때 CR/LF 로 가짜 로그 라인을
|
|
706
|
+
* 위조하거나 ANSI ESC 로 터미널을 오염시키는 로그 인젝션을 차단한다.
|
|
707
|
+
*
|
|
708
|
+
* 규칙:
|
|
709
|
+
* 1. 치환 대상: `U+0000–U+001F` 전체(CR·LF·TAB 포함) + `U+007F`(DEL).
|
|
710
|
+
* 2. 각 1문자 → **공백 1자(U+0020)** 1:1 치환. 붕괴(collapse) 없음 — `\r\n` 은 공백 2개.
|
|
711
|
+
* (U+FFFD 비채택 — 비-UTF8 로그 싱크 mojibake·grep 친화성.)
|
|
712
|
+
* 3. maxLength 생략 시 무제한(치환만). 지정 시 4 이상 정수(미만은 RangeError) —
|
|
713
|
+
* 치환 후 길이가 **초과**하면 앞 `maxLength-3` 자 + ASCII `"..."` 로 절단해
|
|
714
|
+
* 결과 총 길이 == maxLength 를 보장한다. 경계(`==`)는 절단 없음.
|
|
715
|
+
* 4. 길이 단위: JS 는 UTF-16 코드유닛 (Java 동일, Python 은 코드포인트) — 보충 평면
|
|
716
|
+
* 문자에서 절단 위치가 언어 간 1 유닛 다를 수 있다 (로그 용도 허용 오차).
|
|
717
|
+
*/
|
|
718
|
+
/**
|
|
719
|
+
* 로그에 싣기 전 외부 입력을 정화한다 — 제어 문자를 공백으로 치환하고,
|
|
720
|
+
* maxLength 지정 시 초과분을 `"..."` 로 절단한다(결과 총 길이 == maxLength).
|
|
721
|
+
*
|
|
722
|
+
* @param value 정화할 값 (string 만 — 런타임 null 은 계약 밖).
|
|
723
|
+
* @param maxLength 결과 최대 길이(UTF-16 코드유닛). 생략 시 무제한. 4 미만이면 RangeError.
|
|
724
|
+
*/
|
|
725
|
+
declare function sanitizeLogValue(value: string, maxLength?: number): string;
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* 사업자등록번호(10자리)·법인등록번호(13자리) 체크섬 검증.
|
|
729
|
+
*
|
|
730
|
+
* 원천: Java `BusinessNumberUtils` / Python `bizno.py` 와 동일 규칙 (수동 동기화).
|
|
731
|
+
*
|
|
732
|
+
* **체크섬만 검증한다** — 실존 여부·구분코드 의미 검증은 범위 밖이다.
|
|
733
|
+
* 정규화는 하이픈(`-`)·스페이스(` `)·탭(`\t`)만 위치 불문 제거하고 그 외 문자는
|
|
734
|
+
* 보존한다(검증 단계에서 걸러짐). 숫자 판정은 ASCII `[0-9]` 만 — 전각 숫자
|
|
735
|
+
* (`123…`)는 3언어 모두 무효 처리한다.
|
|
736
|
+
*/
|
|
737
|
+
/**
|
|
738
|
+
* 사업자·법인번호 표기에서 하이픈(`-`)·스페이스(` `)·탭(`\t`)을 위치 불문 제거한다.
|
|
739
|
+
* 그 외 문자는 보존한다(비대상 문자는 검증 단계에서 무효 처리).
|
|
740
|
+
*/
|
|
741
|
+
declare function normalizeBusinessNumber(value: string): string;
|
|
742
|
+
/**
|
|
743
|
+
* 사업자등록번호(10자리) 체크섬 검증. null/undefined, 정규화 후 ASCII 숫자
|
|
744
|
+
* 10자리가 아닌 값은 false.
|
|
745
|
+
*
|
|
746
|
+
* 검증식: `sum = Σ(i=0..8) dᵢ×wᵢ + (d₈×5)//10`, `check = (10 − sum%10) % 10`,
|
|
747
|
+
* 유효 ⟺ `check == d₉` (가중치 `[1,3,7,1,3,7,1,3,5]`).
|
|
748
|
+
*/
|
|
749
|
+
declare function isValidBusinessNumber(value: string | null | undefined): boolean;
|
|
750
|
+
/**
|
|
751
|
+
* 법인등록번호(13자리) 체크섬 검증. null/undefined, 정규화 후 ASCII 숫자
|
|
752
|
+
* 13자리가 아닌 값은 false.
|
|
753
|
+
*
|
|
754
|
+
* 검증식: 가중치 `1,2` 반복 12개, `sum = Σ(i=0..11) dᵢ×wᵢ`,
|
|
755
|
+
* `check = (10 − sum%10) % 10`, 유효 ⟺ `check == d₁₂`.
|
|
756
|
+
*/
|
|
757
|
+
declare function isValidCorporateNumber(value: string | null | undefined): boolean;
|
|
758
|
+
|
|
759
|
+
export { type ApiClient, type ApiClientConfig, type ApiClientRetryOptions, ApiError, type ApiErrorInfo, type ApiRequestInfo, type ApiResponseInfo, type ApiResult, type Bulkhead, BulkheadFullError, type BulkheadOptions, type CircuitBreaker, type CircuitBreakerOptions, type CircuitState, type CommonResponse, type FieldErrorDetail, type PageResponse, ResultCode, type RetryOptions, type SseCallbacks, type SseFrameEvent, type SseSource, type TokenBucket, type TokenBucketOptions, type TtlCache, type TtlCacheOptions, type ValidationErrorData, createApiClient, createBulkhead, createCircuitBreaker, createTokenBucket, createTtlCache, decodeJwtPayload, getTokenExpiry, isChosungQuery, isRetryableStatus, isTokenExpired, isValidBusinessNumber, isValidCorporateNumber, isValidationErrorData, maskCardNumber, maskEmail, maskName, maskPhone, maskSecret, normalizeBusinessNumber, parseRetryAfterMs, parseSseFrame, parseWireDateTime, readSseStream, retry, sanitizeLogValue, stripZone, toChosung, toWireDate, toWireDateTime };
|