@rscc/common-core 0.1.2 → 0.3.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/dist/index.cjs CHANGED
@@ -22,36 +22,71 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  ApiError: () => ApiError,
24
24
  BulkheadFullError: () => BulkheadFullError,
25
+ CircuitOpenError: () => CircuitOpenError,
25
26
  ResultCode: () => ResultCode,
27
+ WEBHOOK_SIGNATURE_HEADER: () => WEBHOOK_SIGNATURE_HEADER,
28
+ abbreviateAmount: () => abbreviateAmount,
29
+ ageByYear: () => ageByYear,
30
+ ageInsurance: () => ageInsurance,
31
+ ageMan: () => ageMan,
32
+ attachJosa: () => attachJosa,
33
+ buildListQuery: () => buildListQuery,
34
+ bulkFailures: () => bulkFailures,
35
+ classifyPhoneNumber: () => classifyPhoneNumber,
36
+ composeHangul: () => composeHangul,
26
37
  createApiClient: () => createApiClient,
38
+ createBulkResultBuilder: () => createBulkResultBuilder,
27
39
  createBulkhead: () => createBulkhead,
40
+ createBusinessDays: () => createBusinessDays,
28
41
  createCircuitBreaker: () => createCircuitBreaker,
42
+ createFeatureFlags: () => createFeatureFlags,
29
43
  createTokenBucket: () => createTokenBucket,
30
44
  createTtlCache: () => createTtlCache,
31
45
  decodeJwtPayload: () => decodeJwtPayload,
46
+ decomposeHangul: () => decomposeHangul,
47
+ formatPhoneNumber: () => formatPhoneNumber,
48
+ generateIdempotencyKey: () => generateIdempotencyKey,
32
49
  getTokenExpiry: () => getTokenExpiry,
50
+ isBulkResult: () => isBulkResult,
33
51
  isChosungQuery: () => isChosungQuery,
52
+ isForeignerRrn: () => isForeignerRrn,
34
53
  isRetryableStatus: () => isRetryableStatus,
35
54
  isTokenExpired: () => isTokenExpired,
36
55
  isValidBusinessNumber: () => isValidBusinessNumber,
37
56
  isValidCorporateNumber: () => isValidCorporateNumber,
57
+ isValidRrn: () => isValidRrn,
38
58
  isValidationErrorData: () => isValidationErrorData,
59
+ kindsForExtension: () => kindsForExtension,
39
60
  maskCardNumber: () => maskCardNumber,
40
61
  maskEmail: () => maskEmail,
41
62
  maskName: () => maskName,
42
63
  maskPhone: () => maskPhone,
43
64
  maskSecret: () => maskSecret,
65
+ matchesHangul: () => matchesHangul,
44
66
  normalizeBusinessNumber: () => normalizeBusinessNumber,
67
+ normalizePhoneNumber: () => normalizePhoneNumber,
68
+ normalizeRrn: () => normalizeRrn,
69
+ parseFlag: () => parseFlag,
45
70
  parseRetryAfterMs: () => parseRetryAfterMs,
46
71
  parseSseFrame: () => parseSseFrame,
47
72
  parseWireDateTime: () => parseWireDateTime,
73
+ pickJosa: () => pickJosa,
48
74
  readSseStream: () => readSseStream,
49
75
  retry: () => retry,
76
+ rrnBirthDate: () => rrnBirthDate,
77
+ rrnChecksumOkLegacy: () => rrnChecksumOkLegacy,
50
78
  sanitizeLogValue: () => sanitizeLogValue,
79
+ signWebhook: () => signWebhook,
80
+ sniffFile: () => sniffFile,
51
81
  stripZone: () => stripZone,
52
82
  toChosung: () => toChosung,
83
+ toE164: () => toE164,
84
+ toFormalNotation: () => toFormalNotation,
85
+ toKoreanWords: () => toKoreanWords,
53
86
  toWireDate: () => toWireDate,
54
- toWireDateTime: () => toWireDateTime
87
+ toWireDateTime: () => toWireDateTime,
88
+ validateUpload: () => validateUpload,
89
+ verifyWebhook: () => verifyWebhook
55
90
  });
56
91
  module.exports = __toCommonJS(index_exports);
57
92
 
@@ -77,6 +112,18 @@ var ResultCode = {
77
112
  INTERNAL_SERVER_ERROR: "500"
78
113
  };
79
114
 
115
+ // src/idempotency.ts
116
+ var FALLBACK_GROUPS = [8, 4, 4, 4, 12];
117
+ function generateIdempotencyKey() {
118
+ const c = globalThis.crypto;
119
+ if (c && typeof c.randomUUID === "function") return c.randomUUID();
120
+ return FALLBACK_GROUPS.map((len) => {
121
+ let group = "";
122
+ for (let i = 0; i < len; i++) group += Math.floor(Math.random() * 16).toString(16);
123
+ return group;
124
+ }).join("-");
125
+ }
126
+
80
127
  // src/retry.ts
81
128
  var RETRYABLE_STATUS = /* @__PURE__ */ new Set([408, 429, 500, 502, 503, 504]);
82
129
  function isRetryableStatus(status) {
@@ -170,6 +217,7 @@ var ApiError = class extends Error {
170
217
  }
171
218
  };
172
219
  var DEFAULT_RETRY_METHODS = ["GET", "HEAD", "OPTIONS", "PUT", "DELETE"];
220
+ var DEFAULT_IDEMPOTENCY_METHODS = ["POST", "PATCH"];
173
221
  var MAX_RAW_TEXT_LENGTH = 2048;
174
222
  function joinUrl(baseUrl, path) {
175
223
  if (path === "") return baseUrl;
@@ -207,11 +255,15 @@ function createApiClient(config) {
207
255
  async function requestWithMeta(path, init = {}) {
208
256
  const sentTraceId = generateTraceId();
209
257
  const method = (init.method ?? "GET").toUpperCase();
258
+ const idempotencyHeader = config.idempotency?.header ?? "Idempotency-Key";
259
+ const idempotencyKey = config.idempotency && (config.idempotency.methods ?? DEFAULT_IDEMPOTENCY_METHODS).includes(method) ? generateIdempotencyKey() : null;
210
260
  const attemptOnce = async () => {
211
261
  const headers = new Headers(init.headers);
212
262
  headers.set(traceIdHeader, sentTraceId);
213
- const isFormData = typeof FormData !== "undefined" && init.body instanceof FormData;
214
- if (!headers.has("Content-Type") && !isFormData) {
263
+ if (idempotencyKey !== null && !headers.has(idempotencyHeader)) {
264
+ headers.set(idempotencyHeader, idempotencyKey);
265
+ }
266
+ if (!headers.has("Content-Type") && typeof init.body === "string") {
215
267
  headers.set("Content-Type", "application/json");
216
268
  }
217
269
  if (config.getToken && !headers.has("Authorization")) {
@@ -276,8 +328,8 @@ function createApiClient(config) {
276
328
  return { data: json.data ?? void 0, traceId, status: response.status, response };
277
329
  }
278
330
  if (!response.ok) {
279
- const j = json;
280
- const message = typeof j?.message === "string" && j.message || typeof j?.error === "string" && j.error || `API \uC624\uB958: ${response.status} ${response.statusText}`;
331
+ const j2 = json;
332
+ const message = typeof j2?.message === "string" && j2.message || typeof j2?.error === "string" && j2.error || `API \uC624\uB958: ${response.status} ${response.statusText}`;
281
333
  fail(String(response.status), message);
282
334
  }
283
335
  return { data: json, traceId, status: response.status, response };
@@ -633,6 +685,12 @@ function createTtlCache(options) {
633
685
  }
634
686
 
635
687
  // src/circuitBreaker.ts
688
+ var CircuitOpenError = class extends Error {
689
+ constructor(message = "\uC11C\uD0B7 OPEN \u2014 \uC694\uCCAD \uCC28\uB2E8") {
690
+ super(message);
691
+ this.name = "CircuitOpenError";
692
+ }
693
+ };
636
694
  function createCircuitBreaker(options) {
637
695
  const { failureThreshold, openDurationMs, now = Date.now } = options;
638
696
  if (!Number.isInteger(failureThreshold) || failureThreshold <= 0) {
@@ -645,6 +703,7 @@ function createCircuitBreaker(options) {
645
703
  let consecutiveFailures = 0;
646
704
  let openedAtMs = 0;
647
705
  let probeInFlight = false;
706
+ let probeStartedAtMs = 0;
648
707
  let inFlightGrants = 0;
649
708
  function allowRequest() {
650
709
  if (state === "CLOSED") {
@@ -652,18 +711,24 @@ function createCircuitBreaker(options) {
652
711
  return true;
653
712
  }
654
713
  if (state === "OPEN") {
655
- if (now() - openedAtMs >= openDurationMs) {
656
- if (inFlightGrants > 0) {
714
+ const nowMs2 = now();
715
+ const elapsedMs = nowMs2 - openedAtMs;
716
+ if (elapsedMs < openDurationMs) return false;
717
+ if (inFlightGrants > 0) {
718
+ if (elapsedMs < 2 * openDurationMs) {
657
719
  return false;
658
720
  }
659
- state = "HALF_OPEN";
660
- probeInFlight = true;
661
- return true;
721
+ inFlightGrants = 0;
662
722
  }
663
- return false;
723
+ state = "HALF_OPEN";
724
+ probeInFlight = true;
725
+ probeStartedAtMs = nowMs2;
726
+ return true;
664
727
  }
665
- if (probeInFlight) return false;
728
+ const nowMs = now();
729
+ if (probeInFlight && nowMs - probeStartedAtMs < openDurationMs) return false;
666
730
  probeInFlight = true;
731
+ probeStartedAtMs = nowMs;
667
732
  return true;
668
733
  }
669
734
  function onSuccess() {
@@ -698,13 +763,40 @@ function createCircuitBreaker(options) {
698
763
  }
699
764
  inFlightGrants = Math.max(0, inFlightGrants - 1);
700
765
  }
766
+ function onIgnore() {
767
+ if (state === "HALF_OPEN") {
768
+ probeInFlight = false;
769
+ return;
770
+ }
771
+ inFlightGrants = Math.max(0, inFlightGrants - 1);
772
+ }
773
+ async function execute(fn) {
774
+ if (!allowRequest()) {
775
+ throw new CircuitOpenError();
776
+ }
777
+ let result;
778
+ try {
779
+ result = await fn();
780
+ } catch (error) {
781
+ if (isAbortError(error)) onIgnore();
782
+ else onFailure();
783
+ throw error;
784
+ }
785
+ onSuccess();
786
+ return result;
787
+ }
701
788
  return {
702
789
  allowRequest,
703
790
  onSuccess,
704
791
  onFailure,
792
+ onIgnore,
793
+ execute,
705
794
  state: () => state
706
795
  };
707
796
  }
797
+ function isAbortError(error) {
798
+ return typeof error === "object" && error !== null && error.name === "AbortError";
799
+ }
708
800
 
709
801
  // src/tokenBucket.ts
710
802
  function createTokenBucket(options) {
@@ -825,38 +917,930 @@ function isValidCorporateNumber(value) {
825
917
  const check = (10 - sum % 10) % 10;
826
918
  return check === digitAt(digits, 12);
827
919
  }
920
+
921
+ // src/webhook.ts
922
+ var WEBHOOK_SIGNATURE_HEADER = "X-Rscc-Signature";
923
+ var DEFAULT_TOLERANCE_SECONDS = 300;
924
+ var encoder = new TextEncoder();
925
+ function toBytes(payload) {
926
+ return typeof payload === "string" ? encoder.encode(payload) : payload;
927
+ }
928
+ async function computeSignatureHex(secret, timestampSeconds, payload) {
929
+ const body = toBytes(payload);
930
+ const prefix = encoder.encode(`${timestampSeconds}.`);
931
+ const message = new Uint8Array(prefix.length + body.length);
932
+ message.set(prefix, 0);
933
+ message.set(body, prefix.length);
934
+ const key = await globalThis.crypto.subtle.importKey(
935
+ "raw",
936
+ encoder.encode(secret),
937
+ { name: "HMAC", hash: "SHA-256" },
938
+ false,
939
+ ["sign"]
940
+ );
941
+ const signature = await globalThis.crypto.subtle.sign("HMAC", key, message);
942
+ let hex = "";
943
+ for (const byte of new Uint8Array(signature)) hex += byte.toString(16).padStart(2, "0");
944
+ return hex;
945
+ }
946
+ function timingSafeHexEquals(a, b) {
947
+ if (a.length !== b.length) return false;
948
+ let diff = 0;
949
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
950
+ return diff === 0;
951
+ }
952
+ async function signWebhook(secret, payload, timestampSeconds) {
953
+ const hex = await computeSignatureHex(secret, timestampSeconds, payload);
954
+ return `t=${timestampSeconds},v1=${hex}`;
955
+ }
956
+ async function verifyWebhook(opts) {
957
+ if (opts.header == null || opts.header === "") return false;
958
+ const secrets = typeof opts.secrets === "string" ? [opts.secrets] : opts.secrets;
959
+ if (secrets.length === 0) return false;
960
+ const timestamps = [];
961
+ const candidates = [];
962
+ for (const element of opts.header.split(",")) {
963
+ const eq = element.indexOf("=");
964
+ if (eq < 0) continue;
965
+ const key = element.slice(0, eq).trim();
966
+ const value = element.slice(eq + 1).trim();
967
+ if (key === "t") timestamps.push(value);
968
+ else if (key === "v1") candidates.push(value);
969
+ }
970
+ if (timestamps.length !== 1 || candidates.length === 0) return false;
971
+ if (!/^[0-9]+$/.test(timestamps[0])) return false;
972
+ const t = Number(timestamps[0]);
973
+ const now = opts.nowSeconds ?? Math.floor(Date.now() / 1e3);
974
+ const tolerance = opts.toleranceSeconds ?? DEFAULT_TOLERANCE_SECONDS;
975
+ if (Math.abs(now - t) > tolerance) return false;
976
+ let matched = false;
977
+ for (const secret of secrets) {
978
+ const expected = await computeSignatureHex(secret, t, opts.payload);
979
+ for (const candidate of candidates) {
980
+ if (timingSafeHexEquals(expected, candidate)) matched = true;
981
+ }
982
+ }
983
+ return matched;
984
+ }
985
+
986
+ // src/bulk.ts
987
+ function isBulkResult(value) {
988
+ if (typeof value !== "object" || value === null) return false;
989
+ const v = value;
990
+ if (typeof v.total !== "number" || typeof v.succeeded !== "number" || typeof v.failed !== "number") {
991
+ return false;
992
+ }
993
+ if (!Array.isArray(v.items)) return false;
994
+ return v.items.every(
995
+ (item) => typeof item === "object" && item !== null && typeof item.index === "number"
996
+ // 각 요소 index 필수 (BK-04)
997
+ );
998
+ }
999
+ function bulkFailures(result) {
1000
+ return result.items.filter((item) => item.code !== void 0);
1001
+ }
1002
+ function createBulkResultBuilder() {
1003
+ let succeeded = 0;
1004
+ let failed = 0;
1005
+ const items = [];
1006
+ const builder = {
1007
+ success(index, id) {
1008
+ succeeded += 1;
1009
+ if (id !== void 0) items.push({ index, id });
1010
+ return builder;
1011
+ },
1012
+ failure(index, code, message) {
1013
+ failed += 1;
1014
+ items.push({ index, code, message });
1015
+ return builder;
1016
+ },
1017
+ build() {
1018
+ return { total: succeeded + failed, succeeded, failed, items: [...items] };
1019
+ }
1020
+ };
1021
+ return builder;
1022
+ }
1023
+
1024
+ // src/upload.ts
1025
+ function bytesAt(head, offset, bytes) {
1026
+ if (head.length < offset + bytes.length) return false;
1027
+ for (let i = 0; i < bytes.length; i++) {
1028
+ if (head[offset + i] !== bytes[i]) return false;
1029
+ }
1030
+ return true;
1031
+ }
1032
+ var HWP3_SIGNATURE = [..."HWP Document File"].map((ch) => ch.charCodeAt(0));
1033
+ var SNIFF_TABLE = [
1034
+ { kind: "png", matches: (h) => bytesAt(h, 0, [137, 80, 78, 71, 13, 10, 26, 10]) },
1035
+ // 3바이트 — JFIF/EXIF/원시 모두 커버.
1036
+ { kind: "jpeg", matches: (h) => bytesAt(h, 0, [255, 216, 255]) },
1037
+ // "GIF87a" / "GIF89a"
1038
+ {
1039
+ kind: "gif",
1040
+ matches: (h) => bytesAt(h, 0, [71, 73, 70, 56, 55, 97]) || bytesAt(h, 0, [71, 73, 70, 56, 57, 97])
1041
+ },
1042
+ // RIFF 컨테이너 — 4~7 바이트(크기)는 임의, 8~11 이 "WEBP" 여야 한다.
1043
+ {
1044
+ kind: "webp",
1045
+ matches: (h) => bytesAt(h, 0, [82, 73, 70, 70]) && bytesAt(h, 8, [87, 69, 66, 80])
1046
+ },
1047
+ // "%PDF-"
1048
+ { kind: "pdf", matches: (h) => bytesAt(h, 0, [37, 80, 68, 70, 45]) },
1049
+ // PK♥♦ 외 2종 — docx/xlsx/pptx/hwpx 도 ZIP (컨테이너 수준 판정).
1050
+ {
1051
+ kind: "zip",
1052
+ matches: (h) => bytesAt(h, 0, [80, 75, 3, 4]) || bytesAt(h, 0, [80, 75, 5, 6]) || bytesAt(h, 0, [80, 75, 7, 8])
1053
+ },
1054
+ // MS 복합문서 — hwp(5.0)/doc/xls/ppt (컨테이너 수준 판정).
1055
+ { kind: "cfbf", matches: (h) => bytesAt(h, 0, [208, 207, 17, 224, 161, 177, 26, 225]) },
1056
+ { kind: "hwp3", matches: (h) => bytesAt(h, 0, HWP3_SIGNATURE) }
1057
+ ];
1058
+ var EXTENSION_KINDS = {
1059
+ png: ["png"],
1060
+ jpg: ["jpeg"],
1061
+ jpeg: ["jpeg"],
1062
+ gif: ["gif"],
1063
+ webp: ["webp"],
1064
+ pdf: ["pdf"],
1065
+ zip: ["zip"],
1066
+ docx: ["zip"],
1067
+ xlsx: ["zip"],
1068
+ pptx: ["zip"],
1069
+ hwpx: ["zip"],
1070
+ doc: ["cfbf"],
1071
+ xls: ["cfbf"],
1072
+ ppt: ["cfbf"],
1073
+ hwp: ["cfbf", "hwp3"]
1074
+ // HWP 5.0 = CFBF 컨테이너, 3.x = 자체 시그니처
1075
+ };
1076
+ function sniffFile(head) {
1077
+ const bytes = head instanceof Uint8Array ? head : new Uint8Array(head);
1078
+ for (const { kind, matches } of SNIFF_TABLE) {
1079
+ if (matches(bytes)) return kind;
1080
+ }
1081
+ return null;
1082
+ }
1083
+ function kindsForExtension(ext) {
1084
+ const normalized = (ext.startsWith(".") ? ext.slice(1) : ext).toLowerCase();
1085
+ return new Set(EXTENSION_KINDS[normalized] ?? []);
1086
+ }
1087
+ function extensionOf(fileName) {
1088
+ const dot = fileName.lastIndexOf(".");
1089
+ if (dot < 0 || dot === fileName.length - 1) return "";
1090
+ return fileName.slice(dot + 1).toLowerCase();
1091
+ }
1092
+ function validateUpload(input) {
1093
+ if (input.size > input.maxSizeBytes) {
1094
+ return { ok: false, reason: "size", message: "\uC5C5\uB85C\uB4DC \uAC00\uB2A5\uD55C \uCD5C\uB300 \uD06C\uAE30\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4." };
1095
+ }
1096
+ const ext = extensionOf(input.fileName);
1097
+ const allowed = input.allowedExtensions.map(
1098
+ (e) => (e.startsWith(".") ? e.slice(1) : e).toLowerCase()
1099
+ );
1100
+ if (ext === "" || !allowed.includes(ext)) {
1101
+ return { ok: false, reason: "extension", message: `\uD5C8\uC6A9\uB418\uC9C0 \uC54A\uB294 \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4: ${ext}` };
1102
+ }
1103
+ const extra = input.extraMappings?.[ext];
1104
+ const kinds = extra !== void 0 ? new Set(extra) : kindsForExtension(ext);
1105
+ const kind = sniffFile(input.head);
1106
+ if (kind === null || !kinds.has(kind)) {
1107
+ return {
1108
+ ok: false,
1109
+ reason: "content-mismatch",
1110
+ message: "\uD30C\uC77C \uB0B4\uC6A9\uC774 \uD655\uC7A5\uC790\uC640 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4."
1111
+ };
1112
+ }
1113
+ return { ok: true };
1114
+ }
1115
+
1116
+ // src/query.ts
1117
+ var RESERVED_FILTER_NAMES = /* @__PURE__ */ new Set(["page", "size", "sort"]);
1118
+ function buildListQuery(options) {
1119
+ const params = new URLSearchParams();
1120
+ if (options.page !== void 0) params.append("page", String(options.page));
1121
+ if (options.size !== void 0) params.append("size", String(options.size));
1122
+ for (const { field, direction } of options.sort ?? []) {
1123
+ params.append("sort", direction === void 0 ? field : `${field},${direction}`);
1124
+ }
1125
+ for (const [name, value] of Object.entries(options.filters ?? {})) {
1126
+ if (RESERVED_FILTER_NAMES.has(name)) {
1127
+ throw new RangeError(`\uC608\uC57D\uB41C \uD30C\uB77C\uBBF8\uD130 \uC774\uB984\uC740 \uD544\uD130\uB85C \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${name}`);
1128
+ }
1129
+ if (value === null || value === void 0) continue;
1130
+ const values = Array.isArray(value) ? value : [value];
1131
+ for (const v of values) params.append(name, String(v));
1132
+ }
1133
+ return params;
1134
+ }
1135
+
1136
+ // src/featureFlags.ts
1137
+ var TRUE_VALUES = /* @__PURE__ */ new Set(["true", "1", "on", "yes"]);
1138
+ function parseFlag(value) {
1139
+ if (typeof value === "boolean") return value;
1140
+ if (value == null) return false;
1141
+ return TRUE_VALUES.has(value.trim().toLowerCase());
1142
+ }
1143
+ function createFeatureFlags(source) {
1144
+ return {
1145
+ isEnabled(key, defaultValue = false) {
1146
+ const value = Object.prototype.hasOwnProperty.call(source, key) ? source[key] : void 0;
1147
+ if (value === void 0) return defaultValue;
1148
+ return parseFlag(value);
1149
+ }
1150
+ };
1151
+ }
1152
+
1153
+ // src/rrn.ts
1154
+ var SEPARATOR_PATTERN2 = /[-\t ]/g;
1155
+ var RRN_DIGITS_PATTERN = /^[0-9]{13}$/;
1156
+ var LEGACY_WEIGHTS = [2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5];
1157
+ var digitAt2 = (digits, i) => digits.charCodeAt(i) - 48;
1158
+ var isLeapYear = (year) => year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
1159
+ var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1160
+ function centuryOf(genderCode) {
1161
+ if (genderCode === 1 || genderCode === 2 || genderCode === 5 || genderCode === 6) return 1900;
1162
+ if (genderCode === 3 || genderCode === 4 || genderCode === 7 || genderCode === 8) return 2e3;
1163
+ return null;
1164
+ }
1165
+ function parseRrn(value) {
1166
+ if (value == null) return null;
1167
+ const digits = normalizeRrn(value);
1168
+ if (!RRN_DIGITS_PATTERN.test(digits)) return null;
1169
+ const century = centuryOf(digitAt2(digits, 6));
1170
+ if (century === null) return null;
1171
+ const year = century + digitAt2(digits, 0) * 10 + digitAt2(digits, 1);
1172
+ const month = digitAt2(digits, 2) * 10 + digitAt2(digits, 3);
1173
+ const day = digitAt2(digits, 4) * 10 + digitAt2(digits, 5);
1174
+ if (month < 1 || month > 12) return null;
1175
+ const maxDay = month === 2 && isLeapYear(year) ? 29 : DAYS_IN_MONTH[month - 1];
1176
+ if (day < 1 || day > maxDay) return null;
1177
+ return { digits, year, month, day };
1178
+ }
1179
+ function normalizeRrn(value) {
1180
+ if (value == null) return value;
1181
+ return value.replace(SEPARATOR_PATTERN2, "");
1182
+ }
1183
+ function isValidRrn(value) {
1184
+ return parseRrn(value) !== null;
1185
+ }
1186
+ function isForeignerRrn(value) {
1187
+ const parsed = parseRrn(value);
1188
+ if (parsed === null) return false;
1189
+ const genderCode = digitAt2(parsed.digits, 6);
1190
+ return genderCode >= 5 && genderCode <= 8;
1191
+ }
1192
+ function rrnChecksumOkLegacy(value) {
1193
+ const parsed = parseRrn(value);
1194
+ if (parsed === null) return false;
1195
+ const { digits } = parsed;
1196
+ let sum = 0;
1197
+ for (const [i, weight] of LEGACY_WEIGHTS.entries()) sum += digitAt2(digits, i) * weight;
1198
+ let check = (11 - sum % 11) % 10;
1199
+ const genderCode = digitAt2(digits, 6);
1200
+ if (genderCode >= 5 && genderCode <= 8) check = (check + 2) % 10;
1201
+ return check === digitAt2(digits, 12);
1202
+ }
1203
+ function rrnBirthDate(value) {
1204
+ const parsed = parseRrn(value);
1205
+ if (parsed === null) return null;
1206
+ const pad2 = (n) => String(n).padStart(2, "0");
1207
+ return `${parsed.year}-${pad2(parsed.month)}-${pad2(parsed.day)}`;
1208
+ }
1209
+
1210
+ // src/josa.ts
1211
+ var HANGUL_BASE2 = 44032;
1212
+ var HANGUL_END2 = 55203;
1213
+ var JONG_RIEUL = 8;
1214
+ var DIGIT_READINGS = "\uC601\uC77C\uC774\uC0BC\uC0AC\uC624\uC721\uCE60\uD314\uAD6C";
1215
+ var JOSA_TABLE = {
1216
+ "\uC740/\uB294": ["\uC740", "\uB294", "\uC740(\uB294)"],
1217
+ "\uC774/\uAC00": ["\uC774", "\uAC00", "\uC774(\uAC00)"],
1218
+ "\uC744/\uB97C": ["\uC744", "\uB97C", "\uC744(\uB97C)"],
1219
+ "\uACFC/\uC640": ["\uACFC", "\uC640", "\uACFC(\uC640)"],
1220
+ "(\uC73C)\uB85C": ["\uC73C\uB85C", "\uB85C", "(\uC73C)\uB85C"],
1221
+ "\uC544/\uC57C": ["\uC544", "\uC57C", "\uC544(\uC57C)"]
1222
+ };
1223
+ function lastJongOf(word) {
1224
+ let end = word.length;
1225
+ while (end > 0 && (word[end - 1] === " " || word[end - 1] === " ")) end--;
1226
+ if (end === 0) return null;
1227
+ let c = word.codePointAt(end - 1);
1228
+ if (c >= 56320 && c <= 57343 && end >= 2) c = word.codePointAt(end - 2);
1229
+ if (c >= 48 && c <= 57) {
1230
+ c = DIGIT_READINGS.codePointAt(c - 48);
1231
+ }
1232
+ if (c >= HANGUL_BASE2 && c <= HANGUL_END2) return (c - HANGUL_BASE2) % 28;
1233
+ return null;
1234
+ }
1235
+ function pickJosa(word, josa) {
1236
+ const entry = JOSA_TABLE[josa];
1237
+ if (entry === void 0) throw new RangeError(`\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC870\uC0AC \uC30D\uC785\uB2C8\uB2E4: ${josa}`);
1238
+ const jong = lastJongOf(word);
1239
+ if (jong === null) return entry[2];
1240
+ if (josa === "(\uC73C)\uB85C") return jong === 0 || jong === JONG_RIEUL ? "\uB85C" : "\uC73C\uB85C";
1241
+ return jong > 0 ? entry[0] : entry[1];
1242
+ }
1243
+ function attachJosa(word, josa) {
1244
+ let end = word.length;
1245
+ while (end > 0 && (word[end - 1] === " " || word[end - 1] === " ")) end--;
1246
+ return word.slice(0, end) + pickJosa(word, josa);
1247
+ }
1248
+
1249
+ // src/age.ts
1250
+ var DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
1251
+ var isLeapYear2 = (year) => year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
1252
+ var DAYS_IN_MONTH2 = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1253
+ function parseDate(value, label) {
1254
+ const m = typeof value === "string" ? value.match(DATE_PATTERN) : null;
1255
+ if (!m) throw new RangeError(`${label}\uC740(\uB294) "YYYY-MM-DD" \uD615\uC2DD\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: ${value}`);
1256
+ const year = Number(m[1]);
1257
+ const month = Number(m[2]);
1258
+ const day = Number(m[3]);
1259
+ const maxDay = month >= 1 && month <= 12 ? month === 2 && isLeapYear2(year) ? 29 : DAYS_IN_MONTH2[month - 1] : 0;
1260
+ if (month < 1 || month > 12 || day < 1 || day > maxDay) {
1261
+ throw new RangeError(`\uC2E4\uC874\uD558\uC9C0 \uC54A\uB294 \uB0A0\uC9DC\uC785\uB2C8\uB2E4: ${value}`);
1262
+ }
1263
+ return { year, month, day };
1264
+ }
1265
+ var monthDayBefore = (a, b) => a.month < b.month || a.month === b.month && a.day < b.day;
1266
+ function parsePair(birth, on) {
1267
+ const b = parseDate(birth, "\uC0DD\uC77C");
1268
+ const o = parseDate(on, "\uAE30\uC900\uC77C");
1269
+ if (o.year < b.year || o.year === b.year && (o.month < b.month || o.month === b.month && o.day < b.day)) {
1270
+ throw new RangeError(`\uAE30\uC900\uC77C(${on})\uC774 \uC0DD\uC77C(${birth})\uBCF4\uB2E4 \uC55E\uC124 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4`);
1271
+ }
1272
+ return [b, o];
1273
+ }
1274
+ function ageMan(birth, on) {
1275
+ const [b, o] = parsePair(birth, on);
1276
+ return o.year - b.year - (monthDayBefore(o, b) ? 1 : 0);
1277
+ }
1278
+ function ageByYear(birth, on) {
1279
+ const [b, o] = parsePair(birth, on);
1280
+ return o.year - b.year;
1281
+ }
1282
+ function ageInsurance(birth, on) {
1283
+ const [b, o] = parsePair(birth, on);
1284
+ const months = (o.year - b.year) * 12 + (o.month - b.month) - (o.day < b.day ? 1 : 0);
1285
+ return Math.floor((months + 6) / 12);
1286
+ }
1287
+
1288
+ // src/phone.ts
1289
+ var SEPARATOR_PATTERN3 = /[-\t ().]/g;
1290
+ var AREA_CODES = /* @__PURE__ */ new Set([
1291
+ "031",
1292
+ "032",
1293
+ "033",
1294
+ "041",
1295
+ "042",
1296
+ "043",
1297
+ "044",
1298
+ "051",
1299
+ "052",
1300
+ "053",
1301
+ "054",
1302
+ "055",
1303
+ "061",
1304
+ "062",
1305
+ "063",
1306
+ "064"
1307
+ ]);
1308
+ var MOBILE_PREFIXES = /* @__PURE__ */ new Set(["011", "016", "017", "018", "019"]);
1309
+ var DIGITS_ONLY = /^[0-9]+$/;
1310
+ function normalizePhoneNumber(value) {
1311
+ if (value == null) return value;
1312
+ const stripped = value.replace(SEPARATOR_PATTERN3, "");
1313
+ if (stripped.startsWith("+82")) {
1314
+ const rest = stripped.slice(3);
1315
+ return rest.startsWith("0") ? rest : "0" + rest;
1316
+ }
1317
+ return stripped;
1318
+ }
1319
+ function classifyPhoneNumber(value) {
1320
+ const digits = normalizePhoneNumber(value);
1321
+ if (digits == null || !DIGITS_ONLY.test(digits)) return "unknown";
1322
+ const len = digits.length;
1323
+ const p3 = digits.slice(0, 3);
1324
+ if (p3 === "010") return len === 11 ? "mobile" : "unknown";
1325
+ if (MOBILE_PREFIXES.has(p3)) return len === 10 || len === 11 ? "mobile" : "unknown";
1326
+ if (p3 === "070") return len === 11 ? "voip" : "unknown";
1327
+ if (p3 === "012") return len === 11 || len === 12 ? "m2m" : "unknown";
1328
+ if (/^050[0-9]/.test(digits)) return len === 11 || len === 12 ? "safe" : "unknown";
1329
+ if (digits.startsWith("02")) return len === 9 || len === 10 ? "landline" : "unknown";
1330
+ if (AREA_CODES.has(p3)) return len === 10 || len === 11 ? "landline" : "unknown";
1331
+ return "unknown";
1332
+ }
1333
+ function formatPhoneNumber(value) {
1334
+ const digits = normalizePhoneNumber(value);
1335
+ if (digits == null) return digits;
1336
+ const type = classifyPhoneNumber(digits);
1337
+ if (type === "unknown") return digits;
1338
+ const len = digits.length;
1339
+ if (digits.startsWith("02")) {
1340
+ return `${digits.slice(0, 2)}-${digits.slice(2, len - 4)}-${digits.slice(len - 4)}`;
1341
+ }
1342
+ if (type === "safe") {
1343
+ return `${digits.slice(0, 4)}-${digits.slice(4, len - 4)}-${digits.slice(len - 4)}`;
1344
+ }
1345
+ if (type === "m2m" && len === 12) return digits;
1346
+ return `${digits.slice(0, 3)}-${digits.slice(3, len - 4)}-${digits.slice(len - 4)}`;
1347
+ }
1348
+ function toE164(value) {
1349
+ const digits = normalizePhoneNumber(value);
1350
+ if (digits == null || classifyPhoneNumber(digits) === "unknown") return null;
1351
+ return "+82" + digits.slice(1);
1352
+ }
1353
+
1354
+ // src/money.ts
1355
+ var LIMIT = 10n ** 20n;
1356
+ var TRILLION = 10n ** 12n;
1357
+ var HUNDRED_MILLION = 10n ** 8n;
1358
+ var TEN_THOUSAND = 10n ** 4n;
1359
+ var DIGIT_WORDS = ["", "\uC77C", "\uC774", "\uC0BC", "\uC0AC", "\uC624", "\uC721", "\uCE60", "\uD314", "\uAD6C"];
1360
+ var PLACE_WORDS = ["", "\uC2ED", "\uBC31", "\uCC9C"];
1361
+ var GROUP_WORDS = ["", "\uB9CC", "\uC5B5", "\uC870", "\uACBD"];
1362
+ function toBigInt(amount) {
1363
+ let value;
1364
+ if (typeof amount === "bigint") {
1365
+ value = amount;
1366
+ } else {
1367
+ if (!Number.isSafeInteger(amount)) {
1368
+ throw new RangeError(`\uAE08\uC561\uC740 \uC548\uC804 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${amount}`);
1369
+ }
1370
+ value = BigInt(amount);
1371
+ }
1372
+ if (value >= LIMIT || value <= -LIMIT) {
1373
+ throw new RangeError(`\uC9C0\uC6D0 \uBC94\uC704(|amount| < 10^20)\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4: ${value}`);
1374
+ }
1375
+ return value;
1376
+ }
1377
+ function groupWords(group, explicitOne) {
1378
+ let out = "";
1379
+ for (let place = 3; place >= 0; place--) {
1380
+ const d = Math.floor(group / 10 ** place) % 10;
1381
+ if (d === 0) continue;
1382
+ if (d === 1 && place > 0 && !explicitOne) {
1383
+ out += PLACE_WORDS[place];
1384
+ } else {
1385
+ out += DIGIT_WORDS[d] + PLACE_WORDS[place];
1386
+ }
1387
+ }
1388
+ return out;
1389
+ }
1390
+ function wordsOfPositive(abs, explicitOne) {
1391
+ const groups = [];
1392
+ let rest = abs;
1393
+ while (rest > 0n) {
1394
+ groups.push(Number(rest % 10000n));
1395
+ rest /= 10000n;
1396
+ }
1397
+ let out = "";
1398
+ for (let gi = groups.length - 1; gi >= 0; gi--) {
1399
+ const group = groups[gi];
1400
+ if (group === 0) continue;
1401
+ const body = group === 1 && gi > 0 ? "\uC77C" : groupWords(group, explicitOne);
1402
+ out += body + GROUP_WORDS[gi];
1403
+ }
1404
+ return out;
1405
+ }
1406
+ var withCommas = (digits) => digits.replace(/\B(?=(\d{3})+$)/g, ",");
1407
+ function toKoreanWords(amount) {
1408
+ const value = toBigInt(amount);
1409
+ if (value === 0n) return "\uC601";
1410
+ if (value < 0n) return "\uB9C8\uC774\uB108\uC2A4 " + wordsOfPositive(-value, false);
1411
+ return wordsOfPositive(value, false);
1412
+ }
1413
+ function toFormalNotation(amount) {
1414
+ const value = toBigInt(amount);
1415
+ if (value < 0n) throw new RangeError(`\uACF5\uBB38\uC11C \uD45C\uAE30\uB294 \uC74C\uC218\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: ${value}`);
1416
+ if (value === 0n) return "\uAE08\uC601\uC6D0\u6574";
1417
+ return "\uAE08" + wordsOfPositive(value, true) + "\uC6D0\u6574";
1418
+ }
1419
+ function scaledOneDecimal(abs, unit) {
1420
+ const intPart = abs / unit;
1421
+ const tenth = abs % unit * 10n / unit;
1422
+ const intStr = withCommas(intPart.toString());
1423
+ return tenth > 0n ? `${intStr}.${tenth}` : intStr;
1424
+ }
1425
+ function abbreviateAmount(amount) {
1426
+ const value = toBigInt(amount);
1427
+ const sign = value < 0n ? "-" : "";
1428
+ const abs = value < 0n ? -value : value;
1429
+ if (abs >= TRILLION) return sign + scaledOneDecimal(abs, TRILLION) + "\uC870";
1430
+ if (abs >= HUNDRED_MILLION) return sign + scaledOneDecimal(abs, HUNDRED_MILLION) + "\uC5B5";
1431
+ if (abs >= TEN_THOUSAND) return sign + withCommas((abs / TEN_THOUSAND).toString()) + "\uB9CC";
1432
+ return sign + withCommas(abs.toString());
1433
+ }
1434
+
1435
+ // src/businessDays.ts
1436
+ var DATE_PATTERN2 = /^(\d{4})-(\d{2})-(\d{2})$/;
1437
+ var MS_PER_DAY = 864e5;
1438
+ var isLeapYear3 = (year) => year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
1439
+ var DAYS_IN_MONTH3 = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1440
+ function toEpochDay(value) {
1441
+ const m = typeof value === "string" ? value.match(DATE_PATTERN2) : null;
1442
+ if (!m) throw new RangeError(`\uB0A0\uC9DC\uB294 "YYYY-MM-DD" \uD615\uC2DD\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: ${value}`);
1443
+ const year = Number(m[1]);
1444
+ const month = Number(m[2]);
1445
+ const day = Number(m[3]);
1446
+ const maxDay = month >= 1 && month <= 12 ? month === 2 && isLeapYear3(year) ? 29 : DAYS_IN_MONTH3[month - 1] : 0;
1447
+ if (month < 1 || month > 12 || day < 1 || day > maxDay) {
1448
+ throw new RangeError(`\uC2E4\uC874\uD558\uC9C0 \uC54A\uB294 \uB0A0\uC9DC\uC785\uB2C8\uB2E4: ${value}`);
1449
+ }
1450
+ return Date.UTC(year, month - 1, day) / MS_PER_DAY;
1451
+ }
1452
+ function fromEpochDay(epochDay) {
1453
+ const d = new Date(epochDay * MS_PER_DAY);
1454
+ const pad2 = (n) => String(n).padStart(2, "0");
1455
+ return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
1456
+ }
1457
+ function isWeekend(epochDay) {
1458
+ const dow = ((epochDay + 4) % 7 + 7) % 7;
1459
+ return dow === 0 || dow === 6;
1460
+ }
1461
+ function createBusinessDays(options) {
1462
+ const holidaySet = /* @__PURE__ */ new Set();
1463
+ for (const holiday of options.holidays) holidaySet.add(toEpochDay(holiday));
1464
+ const isBusinessEpochDay = (epochDay) => !isWeekend(epochDay) && !holidaySet.has(epochDay);
1465
+ const stepUntilBusiness = (epochDay, step) => {
1466
+ let day = epochDay + step;
1467
+ while (!isBusinessEpochDay(day)) day += step;
1468
+ return day;
1469
+ };
1470
+ return {
1471
+ isBusinessDay(date) {
1472
+ return isBusinessEpochDay(toEpochDay(date));
1473
+ },
1474
+ addBusinessDays(date, n) {
1475
+ let day = toEpochDay(date);
1476
+ if (!Number.isInteger(n)) throw new RangeError(`n \uC740 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${n}`);
1477
+ const step = n < 0 ? -1 : 1;
1478
+ for (let remaining = Math.abs(n); remaining > 0; remaining--) {
1479
+ day = stepUntilBusiness(day, step);
1480
+ }
1481
+ return fromEpochDay(day);
1482
+ },
1483
+ nextBusinessDay(date) {
1484
+ return fromEpochDay(stepUntilBusiness(toEpochDay(date), 1));
1485
+ },
1486
+ previousBusinessDay(date) {
1487
+ return fromEpochDay(stepUntilBusiness(toEpochDay(date), -1));
1488
+ },
1489
+ countBusinessDays(start, endExclusive) {
1490
+ const startDay = toEpochDay(start);
1491
+ const endDay = toEpochDay(endExclusive);
1492
+ if (startDay > endDay) {
1493
+ throw new RangeError(`start(${start})\uAC00 end(${endExclusive})\uBCF4\uB2E4 \uB4A4\uC77C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4`);
1494
+ }
1495
+ let count = 0;
1496
+ for (let day = startDay; day < endDay; day++) {
1497
+ if (isBusinessEpochDay(day)) count++;
1498
+ }
1499
+ return count;
1500
+ }
1501
+ };
1502
+ }
1503
+
1504
+ // src/jamo.ts
1505
+ var HANGUL_BASE3 = 44032;
1506
+ var HANGUL_END3 = 55203;
1507
+ var CHO_DIV2 = 21 * 28;
1508
+ var COMPAT_CONS_START2 = 12593;
1509
+ var COMPAT_CONS_END2 = 12622;
1510
+ var COMPAT_VOWEL_START2 = 12623;
1511
+ var COMPAT_VOWEL_END2 = 12643;
1512
+ var j = String.fromCharCode;
1513
+ var CHO_CODEPOINTS = [
1514
+ 12593,
1515
+ 12594,
1516
+ 12596,
1517
+ 12599,
1518
+ 12600,
1519
+ 12601,
1520
+ 12609,
1521
+ 12610,
1522
+ 12611,
1523
+ 12613,
1524
+ 12614,
1525
+ 12615,
1526
+ 12616,
1527
+ 12617,
1528
+ 12618,
1529
+ 12619,
1530
+ 12620,
1531
+ 12621,
1532
+ 12622
1533
+ ];
1534
+ var JUNG_DECOMP = [
1535
+ j(12623),
1536
+ j(12624),
1537
+ j(12625),
1538
+ j(12626),
1539
+ j(12627),
1540
+ j(12628),
1541
+ j(12629),
1542
+ j(12630),
1543
+ j(12631),
1544
+ // ㅗ
1545
+ j(12631) + j(12623),
1546
+ // ㅘ→ㅗㅏ
1547
+ j(12631) + j(12624),
1548
+ // ㅙ→ㅗㅐ
1549
+ j(12631) + j(12643),
1550
+ // ㅚ→ㅗㅣ
1551
+ j(12635),
1552
+ // ㅛ
1553
+ j(12636),
1554
+ // ㅜ
1555
+ j(12636) + j(12627),
1556
+ // ㅝ→ㅜㅓ
1557
+ j(12636) + j(12628),
1558
+ // ㅞ→ㅜㅔ
1559
+ j(12636) + j(12643),
1560
+ // ㅟ→ㅜㅣ
1561
+ j(12640),
1562
+ // ㅠ
1563
+ j(12641),
1564
+ // ㅡ
1565
+ j(12641) + j(12643),
1566
+ // ㅢ→ㅡㅣ
1567
+ j(12643)
1568
+ // ㅣ
1569
+ ];
1570
+ var JONG_DECOMP = [
1571
+ "",
1572
+ j(12593),
1573
+ // ㄱ
1574
+ j(12594),
1575
+ // ㄲ (비분해)
1576
+ j(12593) + j(12613),
1577
+ // ㄳ→ㄱㅅ
1578
+ j(12596),
1579
+ // ㄴ
1580
+ j(12596) + j(12616),
1581
+ // ㄵ→ㄴㅈ
1582
+ j(12596) + j(12622),
1583
+ // ㄶ→ㄴㅎ
1584
+ j(12599),
1585
+ // ㄷ
1586
+ j(12601),
1587
+ // ㄹ
1588
+ j(12601) + j(12593),
1589
+ // ㄺ→ㄹㄱ
1590
+ j(12601) + j(12609),
1591
+ // ㄻ→ㄹㅁ
1592
+ j(12601) + j(12610),
1593
+ // ㄼ→ㄹㅂ
1594
+ j(12601) + j(12613),
1595
+ // ㄽ→ㄹㅅ
1596
+ j(12601) + j(12620),
1597
+ // ㄾ→ㄹㅌ
1598
+ j(12601) + j(12621),
1599
+ // ㄿ→ㄹㅍ
1600
+ j(12601) + j(12622),
1601
+ // ㅀ→ㄹㅎ
1602
+ j(12609),
1603
+ // ㅁ
1604
+ j(12610),
1605
+ // ㅂ
1606
+ j(12610) + j(12613),
1607
+ // ㅄ→ㅂㅅ
1608
+ j(12613),
1609
+ // ㅅ
1610
+ j(12614),
1611
+ // ㅆ (비분해)
1612
+ j(12615),
1613
+ // ㅇ
1614
+ j(12616),
1615
+ // ㅈ
1616
+ j(12618),
1617
+ // ㅊ
1618
+ j(12619),
1619
+ // ㅋ
1620
+ j(12620),
1621
+ // ㅌ
1622
+ j(12621),
1623
+ // ㅍ
1624
+ j(12622)
1625
+ // ㅎ
1626
+ ];
1627
+ var CHO_INDEX = new Map(CHO_CODEPOINTS.map((cp, idx) => [cp, idx]));
1628
+ var JONG_INDEX = /* @__PURE__ */ new Map([
1629
+ [12593, 1],
1630
+ [12594, 2],
1631
+ [12595, 3],
1632
+ [12596, 4],
1633
+ [12597, 5],
1634
+ [12598, 6],
1635
+ [12599, 7],
1636
+ [12601, 8],
1637
+ [12602, 9],
1638
+ [12603, 10],
1639
+ [12604, 11],
1640
+ [12605, 12],
1641
+ [12606, 13],
1642
+ [12607, 14],
1643
+ [12608, 15],
1644
+ [12609, 16],
1645
+ [12610, 17],
1646
+ [12612, 18],
1647
+ [12613, 19],
1648
+ [12614, 20],
1649
+ [12615, 21],
1650
+ [12616, 22],
1651
+ [12618, 23],
1652
+ [12619, 24],
1653
+ [12620, 25],
1654
+ [12621, 26],
1655
+ [12622, 27]
1656
+ ]);
1657
+ var VOWEL_COMBINE = /* @__PURE__ */ new Map([
1658
+ [12631 << 16 | 12623, 12632],
1659
+ // ㅗ+ㅏ→ㅘ
1660
+ [12631 << 16 | 12624, 12633],
1661
+ // ㅗ+ㅐ→ㅙ
1662
+ [12631 << 16 | 12643, 12634],
1663
+ // ㅗ+ㅣ→ㅚ
1664
+ [12636 << 16 | 12627, 12637],
1665
+ // ㅜ+ㅓ→ㅝ
1666
+ [12636 << 16 | 12628, 12638],
1667
+ // ㅜ+ㅔ→ㅞ
1668
+ [12636 << 16 | 12643, 12639],
1669
+ // ㅜ+ㅣ→ㅟ
1670
+ [12641 << 16 | 12643, 12642]
1671
+ // ㅡ+ㅣ→ㅢ
1672
+ ]);
1673
+ var JONG_COMBINE = /* @__PURE__ */ new Map([
1674
+ [12593 << 16 | 12613, 12595],
1675
+ // ㄱ+ㅅ→ㄳ
1676
+ [12596 << 16 | 12616, 12597],
1677
+ // ㄴ+ㅈ→ㄵ
1678
+ [12596 << 16 | 12622, 12598],
1679
+ // ㄴ+ㅎ→ㄶ
1680
+ [12601 << 16 | 12593, 12602],
1681
+ // ㄹ+ㄱ→ㄺ
1682
+ [12601 << 16 | 12609, 12603],
1683
+ // ㄹ+ㅁ→ㄻ
1684
+ [12601 << 16 | 12610, 12604],
1685
+ // ㄹ+ㅂ→ㄼ
1686
+ [12601 << 16 | 12613, 12605],
1687
+ // ㄹ+ㅅ→ㄽ
1688
+ [12601 << 16 | 12620, 12606],
1689
+ // ㄹ+ㅌ→ㄾ
1690
+ [12601 << 16 | 12621, 12607],
1691
+ // ㄹ+ㅍ→ㄿ
1692
+ [12601 << 16 | 12622, 12608],
1693
+ // ㄹ+ㅎ→ㅀ
1694
+ [12610 << 16 | 12613, 12612]
1695
+ // ㅂ+ㅅ→ㅄ
1696
+ ]);
1697
+ var isVowelCp = (cp) => cp >= COMPAT_VOWEL_START2 && cp <= COMPAT_VOWEL_END2;
1698
+ function decomposeHangul(s) {
1699
+ if (!s) return "";
1700
+ let out = "";
1701
+ for (const ch of s) {
1702
+ const cp = ch.codePointAt(0);
1703
+ if (cp >= HANGUL_BASE3 && cp <= HANGUL_END3) {
1704
+ const idx = cp - HANGUL_BASE3;
1705
+ out += j(CHO_CODEPOINTS[Math.floor(idx / CHO_DIV2)]);
1706
+ out += JUNG_DECOMP[Math.floor(idx / 28) % 21];
1707
+ out += JONG_DECOMP[idx % 28];
1708
+ } else {
1709
+ out += ch;
1710
+ }
1711
+ }
1712
+ return out;
1713
+ }
1714
+ function composeHangul(s) {
1715
+ const chars = Array.from(s);
1716
+ const n = chars.length;
1717
+ const cpAt = (idx) => idx >= 0 && idx < n ? chars[idx].codePointAt(0) : -1;
1718
+ let out = "";
1719
+ let i = 0;
1720
+ while (i < n) {
1721
+ const choIdx = CHO_INDEX.get(cpAt(i));
1722
+ if (choIdx === void 0 || !isVowelCp(cpAt(i + 1))) {
1723
+ out += chars[i];
1724
+ i++;
1725
+ continue;
1726
+ }
1727
+ i++;
1728
+ let jungCp = cpAt(i);
1729
+ i++;
1730
+ const combinedVowel = VOWEL_COMBINE.get(jungCp << 16 | cpAt(i));
1731
+ if (combinedVowel !== void 0) {
1732
+ jungCp = combinedVowel;
1733
+ i++;
1734
+ }
1735
+ let jongIdx = 0;
1736
+ const jong1Cp = cpAt(i);
1737
+ const jong1Idx = JONG_INDEX.get(jong1Cp);
1738
+ if (jong1Idx !== void 0 && !isVowelCp(cpAt(i + 1))) {
1739
+ jongIdx = jong1Idx;
1740
+ i++;
1741
+ const combinedJong = JONG_COMBINE.get(jong1Cp << 16 | cpAt(i));
1742
+ if (combinedJong !== void 0 && !isVowelCp(cpAt(i + 1))) {
1743
+ jongIdx = JONG_INDEX.get(combinedJong);
1744
+ i++;
1745
+ }
1746
+ }
1747
+ out += j(HANGUL_BASE3 + choIdx * CHO_DIV2 + (jungCp - COMPAT_VOWEL_START2) * 28 + jongIdx);
1748
+ }
1749
+ return out;
1750
+ }
1751
+ function matchesHangul(query, target) {
1752
+ const q = Array.from(query);
1753
+ const t = Array.from(target);
1754
+ if (q.length === 0) return true;
1755
+ if (q.length > t.length) return false;
1756
+ for (let i = 0; i < q.length; i++) {
1757
+ const qc = q[i];
1758
+ const tc = t[i];
1759
+ if (i === q.length - 1) {
1760
+ const qd = decomposeHangul(qc.toLowerCase());
1761
+ const td = decomposeHangul(tc.toLowerCase());
1762
+ if (!td.startsWith(qd)) return false;
1763
+ continue;
1764
+ }
1765
+ if (qc === tc) continue;
1766
+ const qcp = qc.codePointAt(0);
1767
+ const tcp = tc.codePointAt(0);
1768
+ if (qcp >= COMPAT_CONS_START2 && qcp <= COMPAT_CONS_END2 && tcp >= HANGUL_BASE3 && tcp <= HANGUL_END3) {
1769
+ if (qcp === CHO_CODEPOINTS[Math.floor((tcp - HANGUL_BASE3) / CHO_DIV2)]) continue;
1770
+ return false;
1771
+ }
1772
+ if (qc.toLowerCase() === tc.toLowerCase()) continue;
1773
+ return false;
1774
+ }
1775
+ return true;
1776
+ }
828
1777
  // Annotate the CommonJS export names for ESM import in node:
829
1778
  0 && (module.exports = {
830
1779
  ApiError,
831
1780
  BulkheadFullError,
1781
+ CircuitOpenError,
832
1782
  ResultCode,
1783
+ WEBHOOK_SIGNATURE_HEADER,
1784
+ abbreviateAmount,
1785
+ ageByYear,
1786
+ ageInsurance,
1787
+ ageMan,
1788
+ attachJosa,
1789
+ buildListQuery,
1790
+ bulkFailures,
1791
+ classifyPhoneNumber,
1792
+ composeHangul,
833
1793
  createApiClient,
1794
+ createBulkResultBuilder,
834
1795
  createBulkhead,
1796
+ createBusinessDays,
835
1797
  createCircuitBreaker,
1798
+ createFeatureFlags,
836
1799
  createTokenBucket,
837
1800
  createTtlCache,
838
1801
  decodeJwtPayload,
1802
+ decomposeHangul,
1803
+ formatPhoneNumber,
1804
+ generateIdempotencyKey,
839
1805
  getTokenExpiry,
1806
+ isBulkResult,
840
1807
  isChosungQuery,
1808
+ isForeignerRrn,
841
1809
  isRetryableStatus,
842
1810
  isTokenExpired,
843
1811
  isValidBusinessNumber,
844
1812
  isValidCorporateNumber,
1813
+ isValidRrn,
845
1814
  isValidationErrorData,
1815
+ kindsForExtension,
846
1816
  maskCardNumber,
847
1817
  maskEmail,
848
1818
  maskName,
849
1819
  maskPhone,
850
1820
  maskSecret,
1821
+ matchesHangul,
851
1822
  normalizeBusinessNumber,
1823
+ normalizePhoneNumber,
1824
+ normalizeRrn,
1825
+ parseFlag,
852
1826
  parseRetryAfterMs,
853
1827
  parseSseFrame,
854
1828
  parseWireDateTime,
1829
+ pickJosa,
855
1830
  readSseStream,
856
1831
  retry,
1832
+ rrnBirthDate,
1833
+ rrnChecksumOkLegacy,
857
1834
  sanitizeLogValue,
1835
+ signWebhook,
1836
+ sniffFile,
858
1837
  stripZone,
859
1838
  toChosung,
1839
+ toE164,
1840
+ toFormalNotation,
1841
+ toKoreanWords,
860
1842
  toWireDate,
861
- toWireDateTime
1843
+ toWireDateTime,
1844
+ validateUpload,
1845
+ verifyWebhook
862
1846
  });