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