@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/dist/index.js ADDED
@@ -0,0 +1,804 @@
1
+ // src/types.ts
2
+ function isValidationErrorData(value) {
3
+ if (typeof value !== "object" || value === null) return false;
4
+ const errors = value.errors;
5
+ if (!Array.isArray(errors)) return false;
6
+ return errors.every(
7
+ (e) => typeof e === "object" && e !== null && typeof e.field === "string" && typeof e.reason === "string"
8
+ );
9
+ }
10
+ var ResultCode = {
11
+ SUCCESS: "200",
12
+ BAD_REQUEST: "400",
13
+ UNAUTHORIZED: "401",
14
+ FORBIDDEN: "403",
15
+ NOT_FOUND: "404",
16
+ METHOD_NOT_ALLOWED: "405",
17
+ CONFLICT: "409",
18
+ UNSUPPORTED_MEDIA_TYPE: "415",
19
+ TOO_MANY_REQUESTS: "429",
20
+ INTERNAL_SERVER_ERROR: "500"
21
+ };
22
+
23
+ // src/retry.ts
24
+ var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
25
+ function isRetryableStatus(status) {
26
+ return RETRYABLE_STATUS.has(status);
27
+ }
28
+ var DELTA_SECONDS_PATTERN = /^[0-9]{1,10}$/;
29
+ var HTTP_DATE_PATTERN = /^[A-Za-z]{3}, ([0-9]{2}) [A-Za-z]{3} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} GMT$/;
30
+ function parseRetryAfterMs(value, nowMs = Date.now()) {
31
+ if (value == null) return null;
32
+ const trimmed = value.trim();
33
+ if (DELTA_SECONDS_PATTERN.test(trimmed)) return Number(trimmed) * 1e3;
34
+ const httpDate = HTTP_DATE_PATTERN.exec(trimmed);
35
+ if (httpDate) {
36
+ const target = Date.parse(trimmed);
37
+ if (Number.isNaN(target)) return null;
38
+ if (new Date(target).getUTCDate() !== Number(httpDate[1])) return null;
39
+ return Math.max(0, target - nowMs);
40
+ }
41
+ return null;
42
+ }
43
+ function backoffDelay(attempt, minDelayMs, maxDelayMs, factor, jitter) {
44
+ const cap = Math.min(maxDelayMs, minDelayMs * Math.pow(factor, attempt));
45
+ return jitter ? Math.random() * cap : cap;
46
+ }
47
+ function abortError(signal) {
48
+ return signal.reason instanceof Error ? signal.reason : new Error("\uC791\uC5C5\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
49
+ }
50
+ function sleep(ms, signal) {
51
+ return new Promise((resolve, reject) => {
52
+ if (signal?.aborted) {
53
+ reject(abortError(signal));
54
+ return;
55
+ }
56
+ const onAbort = () => {
57
+ clearTimeout(timer);
58
+ reject(abortError(signal));
59
+ };
60
+ const timer = setTimeout(() => {
61
+ signal?.removeEventListener("abort", onAbort);
62
+ resolve();
63
+ }, ms);
64
+ signal?.addEventListener("abort", onAbort, { once: true });
65
+ });
66
+ }
67
+ async function retry(fn, options = {}) {
68
+ const {
69
+ retries = 3,
70
+ minDelayMs = 200,
71
+ maxDelayMs = 5e3,
72
+ factor = 2,
73
+ jitter = true,
74
+ maxElapsedMs = 0,
75
+ shouldRetry = () => true,
76
+ overrideDelay,
77
+ onRetry,
78
+ signal
79
+ } = options;
80
+ const start = Date.now();
81
+ let attempt = 0;
82
+ for (; ; ) {
83
+ if (signal?.aborted) throw abortError(signal);
84
+ try {
85
+ return await fn(attempt);
86
+ } catch (error) {
87
+ if (signal?.aborted) throw abortError(signal);
88
+ if (attempt >= retries || !shouldRetry(error, attempt)) throw error;
89
+ let delay = backoffDelay(attempt, minDelayMs, maxDelayMs, factor, jitter);
90
+ if (overrideDelay) {
91
+ const adjusted = overrideDelay(error, attempt, delay);
92
+ if (Number.isFinite(adjusted)) delay = Math.max(0, adjusted);
93
+ }
94
+ if (maxElapsedMs > 0 && Date.now() - start + delay > maxElapsedMs) throw error;
95
+ onRetry?.(error, attempt, delay);
96
+ await sleep(delay, signal);
97
+ attempt++;
98
+ }
99
+ }
100
+ }
101
+
102
+ // src/apiClient.ts
103
+ var ApiError = class extends Error {
104
+ constructor(args) {
105
+ super(args.message);
106
+ this.name = "ApiError";
107
+ this.code = args.code;
108
+ this.status = args.status;
109
+ this.traceId = args.traceId;
110
+ this.data = args.data;
111
+ this.retryAfterMs = args.retryAfterMs ?? null;
112
+ this.rawText = args.rawText;
113
+ }
114
+ };
115
+ var DEFAULT_RETRY_METHODS = ["GET", "HEAD", "OPTIONS", "PUT", "DELETE"];
116
+ var MAX_RAW_TEXT_LENGTH = 2048;
117
+ function joinUrl(baseUrl, path) {
118
+ if (path === "") return baseUrl;
119
+ if (path.startsWith("?") || path.startsWith("#")) return baseUrl.replace(/\/+$/, "") + path;
120
+ return baseUrl.replace(/\/+$/, "") + "/" + path.replace(/^\/+/, "");
121
+ }
122
+ function defaultShouldRetry(error) {
123
+ if (error instanceof ApiError) return isRetryableStatus(error.status);
124
+ return error instanceof TypeError;
125
+ }
126
+ function generateTraceId() {
127
+ const c = globalThis.crypto;
128
+ if (c && typeof c.randomUUID === "function") return c.randomUUID().slice(0, 8);
129
+ return Math.random().toString(16).slice(2, 10).padEnd(8, "0");
130
+ }
131
+ function isCommonResponse(value) {
132
+ if (typeof value !== "object" || value === null) return false;
133
+ const v = value;
134
+ return typeof v.success === "boolean" && typeof v.code === "string" && typeof v.message === "string";
135
+ }
136
+ function createApiClient(config) {
137
+ const traceIdHeader = config.traceIdHeader ?? "X-Trace-Id";
138
+ let hookWarned = false;
139
+ const safeHook = (fn) => {
140
+ try {
141
+ fn();
142
+ } catch (error) {
143
+ if (!hookWarned) {
144
+ hookWarned = true;
145
+ console.warn("[apiClient] \uAD00\uCE21 \uD6C5 \uC608\uC678 \u2014 \uBB34\uC2DC\uB429\uB2C8\uB2E4.", error);
146
+ }
147
+ }
148
+ };
149
+ const nowMs = () => globalThis.performance?.now() ?? Date.now();
150
+ async function requestWithMeta(path, init = {}) {
151
+ const sentTraceId = generateTraceId();
152
+ const method = (init.method ?? "GET").toUpperCase();
153
+ const attemptOnce = async () => {
154
+ const headers = new Headers(init.headers);
155
+ headers.set(traceIdHeader, sentTraceId);
156
+ const isFormData = typeof FormData !== "undefined" && init.body instanceof FormData;
157
+ if (!headers.has("Content-Type") && !isFormData) {
158
+ headers.set("Content-Type", "application/json");
159
+ }
160
+ if (config.getToken && !headers.has("Authorization")) {
161
+ const token = config.getToken();
162
+ if (token) headers.set("Authorization", `Bearer ${token}`);
163
+ }
164
+ const fetchFn = config.fetchImpl ?? globalThis.fetch;
165
+ const response = await fetchFn(joinUrl(config.baseUrl, path), { ...init, headers });
166
+ const traceId = response.headers.get(traceIdHeader) ?? sentTraceId;
167
+ let raw = null;
168
+ try {
169
+ const source = typeof response.clone === "function" ? response.clone() : response;
170
+ raw = await source.text();
171
+ } catch {
172
+ if (config.strictJson && response.ok) {
173
+ throw new ApiError({
174
+ code: "INVALID_JSON",
175
+ message: "\uC751\uB2F5 \uBCF8\uBB38\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
176
+ status: response.status,
177
+ traceId,
178
+ retryAfterMs: parseRetryAfterMs(response.headers.get("Retry-After"))
179
+ });
180
+ }
181
+ raw = null;
182
+ }
183
+ let json = null;
184
+ if (raw !== null) {
185
+ if (raw.trim() === "") {
186
+ json = null;
187
+ } else {
188
+ try {
189
+ json = JSON.parse(raw);
190
+ } catch {
191
+ if (config.strictJson && response.ok) {
192
+ throw new ApiError({
193
+ code: "INVALID_JSON",
194
+ message: "\uC751\uB2F5 \uBCF8\uBB38\uC774 \uC720\uD6A8\uD55C JSON \uC774 \uC544\uB2D9\uB2C8\uB2E4.",
195
+ status: response.status,
196
+ traceId,
197
+ retryAfterMs: parseRetryAfterMs(response.headers.get("Retry-After")),
198
+ rawText: raw.slice(0, MAX_RAW_TEXT_LENGTH)
199
+ });
200
+ }
201
+ json = null;
202
+ }
203
+ }
204
+ }
205
+ const fail = (code, message, data) => {
206
+ const error = new ApiError({
207
+ code,
208
+ message,
209
+ status: response.status,
210
+ traceId,
211
+ data,
212
+ retryAfterMs: parseRetryAfterMs(response.headers.get("Retry-After"))
213
+ });
214
+ if (response.status === 401) config.onUnauthorized?.(error);
215
+ throw error;
216
+ };
217
+ if (isCommonResponse(json)) {
218
+ if (!response.ok || !json.success) fail(json.code, json.message, json.data);
219
+ return { data: json.data ?? void 0, traceId, status: response.status, response };
220
+ }
221
+ if (!response.ok) {
222
+ const j = json;
223
+ const message = typeof j?.message === "string" && j.message || typeof j?.error === "string" && j.error || `API \uC624\uB958: ${response.status} ${response.statusText}`;
224
+ fail(String(response.status), message);
225
+ }
226
+ return { data: json, traceId, status: response.status, response };
227
+ };
228
+ const execute = () => {
229
+ const retryConfig = config.retry;
230
+ if (!retryConfig) return attemptOnce();
231
+ if (!(retryConfig.methods ?? DEFAULT_RETRY_METHODS).includes(method)) return attemptOnce();
232
+ const isOneShotBody = typeof ReadableStream !== "undefined" && init.body instanceof ReadableStream || typeof FormData !== "undefined" && init.body instanceof FormData;
233
+ if (isOneShotBody) return attemptOnce();
234
+ const { shouldRetry = defaultShouldRetry, methods: _methods, ...retryOptions } = retryConfig;
235
+ return retry(attemptOnce, {
236
+ ...retryOptions,
237
+ shouldRetry,
238
+ // Retry-After 를 계산된 대기의 하한으로 적용 (배치1 overrideDelay 훅 시맨틱 —
239
+ // maxDelayMs 상한 미재적용, 폭주 방어는 maxElapsedMs 예산 담당).
240
+ overrideDelay: (error, _attempt, delayMs) => error instanceof ApiError && error.retryAfterMs != null ? Math.max(delayMs, error.retryAfterMs) : delayMs,
241
+ signal: init.signal ?? void 0
242
+ });
243
+ };
244
+ if (!config.onRequest && !config.onResponse && !config.onError) return execute();
245
+ const startedAt = nowMs();
246
+ safeHook(() => config.onRequest?.({ method, path, traceId: sentTraceId }));
247
+ try {
248
+ const result = await execute();
249
+ safeHook(
250
+ () => config.onResponse?.({
251
+ method,
252
+ path,
253
+ traceId: result.traceId,
254
+ status: result.status,
255
+ durationMs: nowMs() - startedAt
256
+ })
257
+ );
258
+ return result;
259
+ } catch (error) {
260
+ safeHook(
261
+ () => config.onError?.({
262
+ method,
263
+ path,
264
+ // ApiError 면 에코 traceId, 네트워크 오류 등 비-ApiError 는 발신 값.
265
+ traceId: error instanceof ApiError ? error.traceId : sentTraceId,
266
+ status: error instanceof ApiError ? error.status : void 0,
267
+ durationMs: nowMs() - startedAt,
268
+ error
269
+ })
270
+ );
271
+ throw error;
272
+ }
273
+ }
274
+ async function request(path, init) {
275
+ const result = await requestWithMeta(path, init);
276
+ return result.data;
277
+ }
278
+ return { request, requestWithMeta };
279
+ }
280
+
281
+ // src/sse.ts
282
+ function parseSseFrame(frame) {
283
+ const dataLine = frame.split("\n").find((l) => l.startsWith("data:"))?.replace(/^data:\s*/, "");
284
+ if (dataLine === void 0 || dataLine === "") return { type: "skip" };
285
+ if (dataLine === "[DONE]") return { type: "done" };
286
+ try {
287
+ const obj = JSON.parse(dataLine);
288
+ if (typeof obj.conversationId === "number") {
289
+ return { type: "conversationId", id: obj.conversationId };
290
+ }
291
+ if (Array.isArray(obj.sources)) {
292
+ return { type: "sources", sources: obj.sources };
293
+ }
294
+ if (typeof obj.delta === "string") return { type: "delta", delta: obj.delta };
295
+ if (typeof obj.error === "string") return { type: "error", message: obj.error };
296
+ } catch {
297
+ }
298
+ return { type: "skip" };
299
+ }
300
+ async function readSseStream(response, callbacks) {
301
+ if (!response.body) {
302
+ throw new Error("SSE \uC751\uB2F5\uC5D0 body \uC2A4\uD2B8\uB9BC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
303
+ }
304
+ const reader = response.body.getReader();
305
+ const decoder = new TextDecoder();
306
+ let buffer = "";
307
+ let errored = false;
308
+ const dispatch = (frame) => {
309
+ const ev = parseSseFrame(frame);
310
+ switch (ev.type) {
311
+ case "conversationId":
312
+ callbacks.onConversationId?.(ev.id);
313
+ break;
314
+ case "sources":
315
+ callbacks.onSources?.(ev.sources);
316
+ break;
317
+ case "delta":
318
+ if (!errored) callbacks.onDelta?.(ev.delta);
319
+ break;
320
+ case "error":
321
+ errored = true;
322
+ callbacks.onError?.(ev.message);
323
+ break;
324
+ case "done":
325
+ callbacks.onDone?.();
326
+ return true;
327
+ case "skip":
328
+ break;
329
+ }
330
+ return false;
331
+ };
332
+ try {
333
+ while (true) {
334
+ const { done, value } = await reader.read();
335
+ if (done) break;
336
+ buffer += decoder.decode(value, { stream: true });
337
+ let sepIdx;
338
+ while ((sepIdx = buffer.indexOf("\n\n")) !== -1) {
339
+ const frame = buffer.slice(0, sepIdx);
340
+ buffer = buffer.slice(sepIdx + 2);
341
+ if (dispatch(frame)) {
342
+ await reader.cancel().catch(() => void 0);
343
+ return;
344
+ }
345
+ }
346
+ }
347
+ buffer += decoder.decode();
348
+ if (buffer.trim() !== "") dispatch(buffer);
349
+ } finally {
350
+ reader.releaseLock();
351
+ }
352
+ }
353
+
354
+ // src/jwt.ts
355
+ function decodeJwtPayload(token) {
356
+ try {
357
+ const seg = token.split(".")[1];
358
+ if (!seg) return null;
359
+ let b64 = seg.replace(/-/g, "+").replace(/_/g, "/");
360
+ const rem = b64.length % 4;
361
+ if (rem === 1) return null;
362
+ if (rem > 0) b64 += "=".repeat(4 - rem);
363
+ const bin = atob(b64);
364
+ const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0));
365
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
366
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
367
+ } catch {
368
+ return null;
369
+ }
370
+ }
371
+ function getTokenExpiry(token) {
372
+ const payload = decodeJwtPayload(token);
373
+ const exp = payload?.exp;
374
+ return typeof exp === "number" ? exp * 1e3 : null;
375
+ }
376
+ function isTokenExpired(token, nowMs = Date.now()) {
377
+ const expiresAt = getTokenExpiry(token);
378
+ return expiresAt === null || expiresAt < nowMs;
379
+ }
380
+
381
+ // src/masking.ts
382
+ var JAVA_BLANK_PATTERN = /^[\t-\r\u001C-\u001F \u1680\u2000-\u2006\u2008-\u200A\u2028\u2029\u205F\u3000]*$/;
383
+ function maskSecret(secret) {
384
+ if (secret == null || JAVA_BLANK_PATTERN.test(secret)) return secret;
385
+ if (secret.length <= 8) return "*".repeat(secret.length);
386
+ return secret.slice(0, 4) + "\u2022\u2022\u2022\u2022" + secret.slice(-4);
387
+ }
388
+ function maskName(name) {
389
+ if (name == null || JAVA_BLANK_PATTERN.test(name)) return name;
390
+ const length = name.length;
391
+ if (length <= 1) return name;
392
+ if (length === 2) return name[0] + "*";
393
+ return name[0] + "*".repeat(length - 2) + name[length - 1];
394
+ }
395
+ function maskPhone(phone) {
396
+ if (phone == null || JAVA_BLANK_PATTERN.test(phone)) return phone;
397
+ return phone.replace(/(\d{2,3})-?(\d{3,4})-?(\d{4})/g, "$1-****-$3");
398
+ }
399
+ function maskEmail(email) {
400
+ if (email == null || JAVA_BLANK_PATTERN.test(email) || !email.includes("@")) return email;
401
+ const [id = "", domain = ""] = email.split("@");
402
+ if (id.length <= 2) return id.slice(0, 1) + "*@" + domain;
403
+ return id.slice(0, 2) + "*".repeat(id.length - 2) + "@" + domain;
404
+ }
405
+ function maskCardNumber(cardNumber) {
406
+ if (cardNumber == null || JAVA_BLANK_PATTERN.test(cardNumber)) return cardNumber;
407
+ return cardNumber.replace(/(\d{4})-?(\d{4})-?(\d{4})-?(\d{4})/g, "$1-****-****-$4");
408
+ }
409
+
410
+ // src/datetime.ts
411
+ var pad = (n) => String(n).padStart(2, "0");
412
+ function toWireDateTime(date) {
413
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
414
+ }
415
+ function toWireDate(date) {
416
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
417
+ }
418
+ function parseWireDateTime(value) {
419
+ const stripped = stripZone(value.trim());
420
+ const m = stripped.match(
421
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?(?:\.(\d+))?$/
422
+ );
423
+ if (!m) throw new Error(`\uC62C\uBC14\uB978 datetime \uD615\uC2DD\uC774 \uC544\uB2D9\uB2C8\uB2E4: ${value}`);
424
+ const [, y, mo, day, h, mi, s = "0", frac = "0"] = m;
425
+ const ms = Number((frac + "000").slice(0, 3));
426
+ const date = new Date(Number(y), Number(mo) - 1, Number(day), Number(h), Number(mi), Number(s), ms);
427
+ if (date.getFullYear() !== Number(y) || date.getMonth() !== Number(mo) - 1 || date.getDate() !== Number(day) || date.getHours() !== Number(h) || date.getMinutes() !== Number(mi) || date.getSeconds() !== Number(s)) {
428
+ throw new Error(`\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 datetime \uAC12\uC785\uB2C8\uB2E4: ${value}`);
429
+ }
430
+ return date;
431
+ }
432
+ function stripZone(value) {
433
+ if (value.endsWith("Z") || value.endsWith("z")) return value.slice(0, -1);
434
+ const tIndex = value.indexOf("T");
435
+ if (tIndex === -1) return value;
436
+ const timePart = value.slice(tIndex);
437
+ const offset = timePart.match(/[+-]\d{2}:?\d{2}$/);
438
+ if (offset && offset.index !== void 0) {
439
+ return value.slice(0, tIndex) + timePart.slice(0, offset.index);
440
+ }
441
+ return value;
442
+ }
443
+
444
+ // src/chosung.ts
445
+ var CHO = String.fromCharCode(
446
+ 12593,
447
+ 12594,
448
+ 12596,
449
+ 12599,
450
+ 12600,
451
+ 12601,
452
+ 12609,
453
+ 12610,
454
+ 12611,
455
+ 12613,
456
+ 12614,
457
+ 12615,
458
+ 12616,
459
+ 12617,
460
+ 12618,
461
+ 12619,
462
+ 12620,
463
+ 12621,
464
+ 12622
465
+ );
466
+ var HANGUL_BASE = 44032;
467
+ var HANGUL_END = 55203;
468
+ var CHO_DIV = 21 * 28;
469
+ var COMPAT_CONS_START = 12593;
470
+ var COMPAT_CONS_END = 12622;
471
+ var COMPAT_VOWEL_START = 12623;
472
+ var COMPAT_VOWEL_END = 12643;
473
+ function toChosung(s) {
474
+ if (!s) return "";
475
+ let out = "";
476
+ for (const ch of s) {
477
+ if (ch === " " || ch === " ") continue;
478
+ const c = ch.codePointAt(0);
479
+ if (c >= COMPAT_VOWEL_START && c <= COMPAT_VOWEL_END) continue;
480
+ if (c >= HANGUL_BASE && c <= HANGUL_END) {
481
+ out += CHO[Math.floor((c - HANGUL_BASE) / CHO_DIV)];
482
+ } else if (c >= COMPAT_CONS_START && c <= COMPAT_CONS_END) {
483
+ out += ch;
484
+ } else {
485
+ out += ch.toLowerCase();
486
+ }
487
+ }
488
+ return out;
489
+ }
490
+ function isChosungQuery(s) {
491
+ if (!s) return false;
492
+ for (const ch of s) {
493
+ const c = ch.codePointAt(0);
494
+ if (c >= COMPAT_CONS_START && c <= COMPAT_CONS_END) return true;
495
+ }
496
+ return false;
497
+ }
498
+
499
+ // src/cache.ts
500
+ function createTtlCache(options) {
501
+ const { ttlMs, maxEntries, now = Date.now } = options;
502
+ if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
503
+ throw new RangeError("ttlMs \uB294 0 \uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4.");
504
+ }
505
+ if (!Number.isInteger(maxEntries) || maxEntries <= 0) {
506
+ throw new RangeError("maxEntries \uB294 0 \uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4.");
507
+ }
508
+ const store = /* @__PURE__ */ new Map();
509
+ const inflight = /* @__PURE__ */ new Map();
510
+ let nextSeq = 0;
511
+ const isExpired = (entry) => now() - entry.createdAt >= ttlMs;
512
+ function set(key, value) {
513
+ if (value === null || value === void 0) {
514
+ throw new TypeError("null/undefined \uB294 \uCE90\uC2DC\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
515
+ }
516
+ store.set(key, { value, createdAt: now(), seq: nextSeq++ });
517
+ if (store.size <= maxEntries) return;
518
+ for (const [k, e] of store) {
519
+ if (isExpired(e)) store.delete(k);
520
+ }
521
+ while (store.size > maxEntries) {
522
+ let oldestKey;
523
+ let oldestSeq = Number.POSITIVE_INFINITY;
524
+ for (const [k, e] of store) {
525
+ if (e.seq < oldestSeq) {
526
+ oldestSeq = e.seq;
527
+ oldestKey = k;
528
+ }
529
+ }
530
+ store.delete(oldestKey);
531
+ }
532
+ }
533
+ function peek(key) {
534
+ const entry = store.get(key);
535
+ if (!entry) return void 0;
536
+ if (isExpired(entry)) {
537
+ store.delete(key);
538
+ return void 0;
539
+ }
540
+ return entry.value;
541
+ }
542
+ function get(key, loader) {
543
+ const entry = store.get(key);
544
+ if (entry) {
545
+ if (!isExpired(entry)) return Promise.resolve(entry.value);
546
+ store.delete(key);
547
+ }
548
+ const pending = inflight.get(key);
549
+ if (pending) return pending;
550
+ let loaded;
551
+ try {
552
+ loaded = loader(key);
553
+ } catch (error) {
554
+ return Promise.reject(error);
555
+ }
556
+ const promise = (async () => {
557
+ try {
558
+ const value = await loaded;
559
+ if (value !== null && value !== void 0) set(key, value);
560
+ return value;
561
+ } finally {
562
+ inflight.delete(key);
563
+ }
564
+ })();
565
+ inflight.set(key, promise);
566
+ return promise;
567
+ }
568
+ return {
569
+ get,
570
+ peek,
571
+ set,
572
+ delete: (key) => store.delete(key),
573
+ clear: () => store.clear(),
574
+ size: () => store.size
575
+ };
576
+ }
577
+
578
+ // src/circuitBreaker.ts
579
+ function createCircuitBreaker(options) {
580
+ const { failureThreshold, openDurationMs, now = Date.now } = options;
581
+ if (!Number.isInteger(failureThreshold) || failureThreshold <= 0) {
582
+ throw new RangeError("failureThreshold \uB294 0 \uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4.");
583
+ }
584
+ if (!Number.isFinite(openDurationMs) || openDurationMs <= 0) {
585
+ throw new RangeError("openDurationMs \uB294 0 \uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4.");
586
+ }
587
+ let state = "CLOSED";
588
+ let consecutiveFailures = 0;
589
+ let openedAtMs = 0;
590
+ let probeInFlight = false;
591
+ let inFlightGrants = 0;
592
+ function allowRequest() {
593
+ if (state === "CLOSED") {
594
+ inFlightGrants++;
595
+ return true;
596
+ }
597
+ if (state === "OPEN") {
598
+ if (now() - openedAtMs >= openDurationMs) {
599
+ if (inFlightGrants > 0) {
600
+ return false;
601
+ }
602
+ state = "HALF_OPEN";
603
+ probeInFlight = true;
604
+ return true;
605
+ }
606
+ return false;
607
+ }
608
+ if (probeInFlight) return false;
609
+ probeInFlight = true;
610
+ return true;
611
+ }
612
+ function onSuccess() {
613
+ if (state === "CLOSED") {
614
+ inFlightGrants = Math.max(0, inFlightGrants - 1);
615
+ consecutiveFailures = 0;
616
+ return;
617
+ }
618
+ if (state === "HALF_OPEN") {
619
+ state = "CLOSED";
620
+ consecutiveFailures = 0;
621
+ probeInFlight = false;
622
+ return;
623
+ }
624
+ inFlightGrants = Math.max(0, inFlightGrants - 1);
625
+ }
626
+ function onFailure() {
627
+ if (state === "CLOSED") {
628
+ inFlightGrants = Math.max(0, inFlightGrants - 1);
629
+ consecutiveFailures++;
630
+ if (consecutiveFailures >= failureThreshold) {
631
+ state = "OPEN";
632
+ openedAtMs = now();
633
+ }
634
+ return;
635
+ }
636
+ if (state === "HALF_OPEN") {
637
+ state = "OPEN";
638
+ openedAtMs = now();
639
+ probeInFlight = false;
640
+ return;
641
+ }
642
+ inFlightGrants = Math.max(0, inFlightGrants - 1);
643
+ }
644
+ return {
645
+ allowRequest,
646
+ onSuccess,
647
+ onFailure,
648
+ state: () => state
649
+ };
650
+ }
651
+
652
+ // src/tokenBucket.ts
653
+ function createTokenBucket(options) {
654
+ const { capacity, refillPerSecond, now = Date.now } = options;
655
+ if (!Number.isInteger(capacity) || capacity <= 0) {
656
+ throw new RangeError("capacity \uB294 0 \uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4.");
657
+ }
658
+ if (!Number.isFinite(refillPerSecond) || refillPerSecond <= 0) {
659
+ throw new RangeError("refillPerSecond \uB294 0 \uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4.");
660
+ }
661
+ let tokens = capacity;
662
+ let lastMs = now();
663
+ function tryAcquire(n = 1) {
664
+ if (!Number.isInteger(n) || n <= 0) {
665
+ throw new RangeError("n \uC740 0 \uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4: " + n);
666
+ }
667
+ if (n > capacity) return false;
668
+ const nowMs = now();
669
+ const elapsedMs = Math.max(0, nowMs - lastMs);
670
+ if (elapsedMs > 0) {
671
+ tokens = Math.min(capacity, tokens + elapsedMs / 1e3 * refillPerSecond);
672
+ lastMs = nowMs;
673
+ }
674
+ if (tokens >= n) {
675
+ tokens -= n;
676
+ return true;
677
+ }
678
+ return false;
679
+ }
680
+ return { tryAcquire };
681
+ }
682
+
683
+ // src/bulkhead.ts
684
+ var BulkheadFullError = class extends Error {
685
+ constructor(message) {
686
+ super(message);
687
+ this.name = "BulkheadFullError";
688
+ }
689
+ };
690
+ function createBulkhead(options) {
691
+ const { maxConcurrent, maxQueue = 0 } = options;
692
+ if (!Number.isInteger(maxConcurrent) || maxConcurrent <= 0) {
693
+ throw new RangeError("maxConcurrent \uB294 0 \uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4.");
694
+ }
695
+ if (!Number.isInteger(maxQueue) || maxQueue < 0) {
696
+ throw new RangeError("maxQueue \uB294 0 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.");
697
+ }
698
+ let active = 0;
699
+ const waiters = [];
700
+ function release() {
701
+ const next = waiters.shift();
702
+ if (next) {
703
+ next();
704
+ } else {
705
+ active--;
706
+ }
707
+ }
708
+ async function execute(fn) {
709
+ if (active < maxConcurrent) {
710
+ active++;
711
+ } else if (waiters.length < maxQueue) {
712
+ await new Promise((resolve) => waiters.push(resolve));
713
+ } else {
714
+ throw new BulkheadFullError(
715
+ `Bulkhead \uB9CC\uC11D \u2014 \uC2E4\uD589 ${maxConcurrent} + \uB300\uAE30 ${maxQueue} \uC2AC\uB86F \uCD08\uACFC`
716
+ );
717
+ }
718
+ try {
719
+ return await fn();
720
+ } finally {
721
+ release();
722
+ }
723
+ }
724
+ return {
725
+ execute,
726
+ activeCount: () => active,
727
+ queuedCount: () => waiters.length
728
+ };
729
+ }
730
+
731
+ // src/logSanitize.ts
732
+ var CONTROL_CHARS_PATTERN = /[\u0000-\u001f\u007f]/g;
733
+ function sanitizeLogValue(value, maxLength) {
734
+ if (maxLength !== void 0 && (!Number.isInteger(maxLength) || maxLength < 4)) {
735
+ throw new RangeError("maxLength \uB294 4 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: " + maxLength);
736
+ }
737
+ const sanitized = value.replace(CONTROL_CHARS_PATTERN, " ");
738
+ if (maxLength === void 0 || sanitized.length <= maxLength) return sanitized;
739
+ return sanitized.slice(0, maxLength - 3) + "...";
740
+ }
741
+
742
+ // src/bizno.ts
743
+ var SEPARATOR_PATTERN = /[-\t ]/g;
744
+ var BUSINESS_DIGITS_PATTERN = /^[0-9]{10}$/;
745
+ var CORPORATE_DIGITS_PATTERN = /^[0-9]{13}$/;
746
+ var BUSINESS_WEIGHTS = [1, 3, 7, 1, 3, 7, 1, 3, 5];
747
+ var digitAt = (digits, i) => digits.charCodeAt(i) - 48;
748
+ function normalizeBusinessNumber(value) {
749
+ if (value == null) return value;
750
+ return value.replace(SEPARATOR_PATTERN, "");
751
+ }
752
+ function isValidBusinessNumber(value) {
753
+ if (value == null) return false;
754
+ const digits = normalizeBusinessNumber(value);
755
+ if (!BUSINESS_DIGITS_PATTERN.test(digits)) return false;
756
+ let sum = 0;
757
+ for (const [i, weight] of BUSINESS_WEIGHTS.entries()) sum += digitAt(digits, i) * weight;
758
+ sum += Math.floor(digitAt(digits, 8) * 5 / 10);
759
+ const check = (10 - sum % 10) % 10;
760
+ return check === digitAt(digits, 9);
761
+ }
762
+ function isValidCorporateNumber(value) {
763
+ if (value == null) return false;
764
+ const digits = normalizeBusinessNumber(value);
765
+ if (!CORPORATE_DIGITS_PATTERN.test(digits)) return false;
766
+ let sum = 0;
767
+ for (let i = 0; i < 12; i++) sum += digitAt(digits, i) * (i % 2 === 0 ? 1 : 2);
768
+ const check = (10 - sum % 10) % 10;
769
+ return check === digitAt(digits, 12);
770
+ }
771
+ export {
772
+ ApiError,
773
+ BulkheadFullError,
774
+ ResultCode,
775
+ createApiClient,
776
+ createBulkhead,
777
+ createCircuitBreaker,
778
+ createTokenBucket,
779
+ createTtlCache,
780
+ decodeJwtPayload,
781
+ getTokenExpiry,
782
+ isChosungQuery,
783
+ isRetryableStatus,
784
+ isTokenExpired,
785
+ isValidBusinessNumber,
786
+ isValidCorporateNumber,
787
+ isValidationErrorData,
788
+ maskCardNumber,
789
+ maskEmail,
790
+ maskName,
791
+ maskPhone,
792
+ maskSecret,
793
+ normalizeBusinessNumber,
794
+ parseRetryAfterMs,
795
+ parseSseFrame,
796
+ parseWireDateTime,
797
+ readSseStream,
798
+ retry,
799
+ sanitizeLogValue,
800
+ stripZone,
801
+ toChosung,
802
+ toWireDate,
803
+ toWireDateTime
804
+ };