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