@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/README.md +105 -23
- package/dist/crypto.cjs +116 -0
- package/dist/crypto.d.cts +26 -0
- package/dist/crypto.d.ts +26 -0
- package/dist/crypto.js +89 -0
- package/dist/index.cjs +997 -13
- package/dist/index.d.cts +620 -29
- package/dist/index.d.ts +620 -29
- package/dist/index.js +961 -12
- package/package.json +13 -3
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
|
-
|
|
157
|
-
|
|
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
|
|
223
|
-
const message = typeof
|
|
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
|
-
|
|
599
|
-
|
|
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
|
-
|
|
603
|
-
probeInFlight = true;
|
|
604
|
-
return true;
|
|
629
|
+
inFlightGrants = 0;
|
|
605
630
|
}
|
|
606
|
-
|
|
631
|
+
state = "HALF_OPEN";
|
|
632
|
+
probeInFlight = true;
|
|
633
|
+
probeStartedAtMs = nowMs2;
|
|
634
|
+
return true;
|
|
607
635
|
}
|
|
608
|
-
|
|
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
|
};
|