@jthong/util 0.0.0-beta-20260522002050
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 +71 -0
- package/dist/array/index.cjs +46 -0
- package/dist/array/index.cjs.map +1 -0
- package/dist/array/index.d.ts +91 -0
- package/dist/array/index.d.ts.map +1 -0
- package/dist/array/index.js +40 -0
- package/dist/array/index.js.map +1 -0
- package/dist/fn/index.cjs +81 -0
- package/dist/fn/index.cjs.map +1 -0
- package/dist/fn/index.d.ts +149 -0
- package/dist/fn/index.d.ts.map +1 -0
- package/dist/fn/index.js +73 -0
- package/dist/fn/index.js.map +1 -0
- package/dist/index.cjs +300 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +266 -0
- package/dist/index.js.map +1 -0
- package/dist/object/index.cjs +28 -0
- package/dist/object/index.cjs.map +1 -0
- package/dist/object/index.d.ts +44 -0
- package/dist/object/index.d.ts.map +1 -0
- package/dist/object/index.js +24 -0
- package/dist/object/index.js.map +1 -0
- package/dist/regex/index.cjs +82 -0
- package/dist/regex/index.cjs.map +1 -0
- package/dist/regex/index.d.ts +209 -0
- package/dist/regex/index.d.ts.map +1 -0
- package/dist/regex/index.js +74 -0
- package/dist/regex/index.js.map +1 -0
- package/dist/string/index.cjs +25 -0
- package/dist/string/index.cjs.map +1 -0
- package/dist/string/index.d.ts +94 -0
- package/dist/string/index.d.ts.map +1 -0
- package/dist/string/index.js +18 -0
- package/dist/string/index.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/regex/index.ts"],"names":[],"mappings":";AAgEO,IAAM,MAAA,GAAS;AAAA;AAAA,EAEpB,KAAA,EAAO,4BAAA;AAAA;AAAA,EAEP,GAAA,EAAK,gCAAA;AAAA;AAAA,EAEL,IAAA,EAAM,4EAAA;AAAA;AAAA,EAEN,YAAA,EAAc;AAChB;AAQO,IAAM,EAAA,GAAK;AAAA;AAAA,EAEhB,IAAA,EAAM,eAAA;AAAA;AAAA,EAEN,KAAA,EAAO,iCAAA;AAAA;AAAA,EAEP,YAAA,EAAc,+BAAA;AAAA;AAAA,EAEd,UAAA,EAAY,0DAAA;AAAA;AAAA,EAEZ,iBAAA,EAAmB,wDAAA;AAAA;AAAA,EAEnB,YAAA,EAAc,2BAAA;AAAA;AAAA,EAEd,mBAAA,EAAqB,yBAAA;AAAA;AAAA,EAErB,UAAA,EAAY,gCAAA;AAAA;AAAA,EAEZ,iBAAA,EAAmB;AACrB;AAaO,IAAM,KAAA,GAAQ;AAAA,EACnB,MAAA;AAAA,EACA;AACF;AAoBO,IAAM,QAAA,GAAW,CACtB,QAAA,EACA,IAAA,KACW;AACX,EAAA,MAAM,QAAA,GAAY,IAAA,CAAgB,KAAA,CAAM,GAAG,CAAA;AAC3C,EAAA,IAAI,OAAA,GAAmB,QAAA;AACvB,EAAA,KAAA,MAAW,WAAW,QAAA,EAAU;AAC9B,IAAA,IAAI,YAAY,IAAA,IAAQ,OAAO,OAAA,KAAY,QAAA,IAAY,mBAAmB,MAAA,EAAQ;AAChF,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,gCAAA,EAAmC,IAAc,CAAA,UAAA,EAAQ,OAAO,CAAA,2BAAA;AAAA,OAClE;AAAA,IACF;AACA,IAAA,OAAA,GAAW,QAAoC,OAAO,CAAA;AACtD,IAAA,IAAI,YAAY,MAAA,EAAW;AACzB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,gBAAA,EAAmB,IAAc,CAAA,oCAAA,EAAkC,OAAO,CAAA,CAAA;AAAA,OAC5E;AAAA,IACF;AAAA,EACF;AACA,EAAA,IAAI,EAAE,mBAAmB,MAAA,CAAA,EAAS;AAChC,IAAA,MAAM,IAAI,KAAA;AAAA,MACR,CAAA,gBAAA,EAAmB,IAAc,CAAA,oCAAA,EAAuC,OAAO,OAAO,CAAA,CAAA;AAAA,KACxF;AAAA,EACF;AACA,EAAA,OAAO,OAAA;AACT;AAeO,IAAM,OAAA,GAAU,CACrB,QAAA,EACA,IAAA,EACA,KAAA,KACY,SAAS,QAAA,EAAU,IAAI,CAAA,CAAE,IAAA,CAAK,KAAK;AAqB1C,IAAM,MAAA,GAAS,CACpB,QAAA,EACA,IAAA,EACA,OACA,WAAA,KACkB;AAClB,EAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,QAAA,EAAU,IAAI,CAAA;AACrC,EAAA,IAAI,CAAC,KAAA,CAAM,IAAA,CAAK,KAAK,GAAG,OAAO,IAAA;AAC/B,EAAA,OAAO,OAAO,WAAA,KAAgB,QAAA,GAC1B,KAAA,CAAM,OAAA,CAAQ,KAAA,EAAO,WAAW,CAAA,GAChC,KAAA,CAAM,OAAA,CAAQ,KAAA,EAAO,WAAW,CAAA;AACtC;AA2CO,IAAM,iBAAA,GAAoB,CAC/B,QAAA,MACoB;AAAA,EACpB,QAAA;AAAA,EACA,QAAA,EAAU,CAAC,IAAA,KAAS,QAAA,CAAS,UAAU,IAAI,CAAA;AAAA,EAC3C,SAAS,CAAC,IAAA,EAAM,UAAU,OAAA,CAAQ,QAAA,EAAU,MAAM,KAAK,CAAA;AAAA,EACvD,MAAA,EAAQ,CAAC,IAAA,EAAM,KAAA,EAAO,gBAAgB,MAAA,CAAO,QAAA,EAAU,IAAA,EAAM,KAAA,EAAO,WAAW;AACjF,CAAA","file":"index.js","sourcesContent":["/**\n * 사용자 정의 정규식 맵 타입. 키 K들의 union으로 `{ [key]: RegExp }` 형태를 만듭니다.\n *\n * @example\n * ```ts\n * type MyKeys = \"POSTAL_CODE\" | \"BUSINESS_NUMBER\";\n * const myRegex: CustomRegexMap<MyKeys> = {\n * POSTAL_CODE: /^\\d{5}$/,\n * BUSINESS_NUMBER: /^\\d{3}-\\d{2}-\\d{5}$/,\n * };\n * ```\n */\nexport type CustomRegexMap<K extends string> = {\n [key in K]: RegExp;\n};\n\n/**\n * 정규식 registry의 모양. 임의 깊이로 중첩 가능합니다.\n * 각 leaf는 `RegExp`, 중간 노드는 동일한 형태의 객체입니다.\n */\nexport type RegexRegistry = {\n readonly [key: string]: RegExp | RegexRegistry;\n};\n\n/**\n * registry에서 RegExp까지의 경로를 dot-notation 문자열 union으로 만듭니다.\n *\n * 모던 TS는 재귀 깊이를 자체적으로 안전하게 다루므로, 깊이 제한 튜플 트릭 없이도 동작합니다.\n *\n * @example\n * ```ts\n * const R = { KR: { PHONE: /.../ }, COMMON: { EMAIL: /.../ } } as const;\n * type P = RegexPath<typeof R>; // \"KR.PHONE\" | \"COMMON.EMAIL\"\n * ```\n */\nexport type RegexPath<T> = T extends RegExp\n ? never\n : T extends object\n ? {\n [K in keyof T & string]: T[K] extends RegExp\n ? K\n : T[K] extends object\n ? `${K}.${RegexPath<T[K]> & string}`\n : never;\n }[keyof T & string]\n : never;\n\n/**\n * `String.replace`의 두 번째 인자 타입. 문자열 패턴 또는 함수 replacer.\n *\n * 함수 replacer는 전체 매치 문자열과 각 capture group을 인자로 받습니다\n * (이어서 offset, 원본 문자열, named groups 객체가 옵니다).\n * 네이티브 `String.prototype.replace`의 lib 시그니처와 동일하게 맞춰 두었습니다.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type RegexReplacer = string | ((substring: string, ...args: any[]) => string);\n\n// ─────────────────────────────────────────────\n// Built-in registry\n// ─────────────────────────────────────────────\n\n/**\n * 모든 서비스/지역 공통 정규식.\n */\nexport const COMMON = {\n /** 단순한 이메일 검증 (공백 없는 local@domain.tld). */\n EMAIL: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/,\n /** http/https URL. */\n URL: /^https?:\\/\\/[^\\s/$.?#][^\\s]*$/i,\n /** UUID v1~v5. */\n UUID: /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,\n /** 영문 + 숫자만 (특수문자/공백 불가). */\n ALPHANUMERIC: /^[a-zA-Z0-9]+$/,\n} as const;\n\n/**\n * 한국 환경에서 자주 쓰는 정규식.\n *\n * - 휴대폰 접두사는 010/011/016/017/018/019 만 유효.\n * - `_DIGITS` 접미 패턴은 하이픈이 없는 숫자 11자리 형식.\n */\nexport const KR = {\n /** 한글 이름 2~17자. */\n NAME: /^[가-힣]{2,17}$/,\n /** 휴대폰번호 (하이픈 포함). */\n PHONE: /^(01[016-9])-(\\d{3,4})-(\\d{4})$/,\n /** 휴대폰번호 (하이픈 없음, 숫자만). */\n PHONE_DIGITS: /^(01[016-9])(\\d{3,4})(\\d{4})$/,\n /** 지역 유선번호 (하이픈 포함). */\n REGION_TEL: /^(0(?:2|3[1-3]|4[1-4]|5[1-5]|6[1-4]))-(\\d{3,4})-(\\d{4})$/,\n /** 지역 유선번호 (하이픈 없음). */\n REGION_TEL_DIGITS: /^(0(?:2|3[1-3]|4[1-4]|5[1-5]|6[1-4]))(\\d{3,4})(\\d{4})$/,\n /** 인터넷 전화 070 (하이픈 포함). */\n INTERNET_TEL: /^(070)-(\\d{3,4})-(\\d{4})$/,\n /** 인터넷 전화 070 (하이픈 없음). */\n INTERNET_TEL_DIGITS: /^(070)(\\d{3,4})(\\d{4})$/,\n /** 안심번호 050X (하이픈 포함). */\n SAFETY_TEL: /^(050[1-9])-(\\d{3,4})-(\\d{4})$/,\n /** 안심번호 050X (하이픈 없음). */\n SAFETY_TEL_DIGITS: /^(050[1-9])(\\d{3,4})(\\d{4})$/,\n} as const;\n\n/**\n * 기본 제공 정규식 registry. `RegexRegistry`를 만족합니다.\n *\n * 사용자가 자신의 패턴을 추가하려면 스프레드로 확장하세요:\n * ```ts\n * const MY_REGEX = {\n * ...REGEX,\n * MY_DOMAIN: { POSTAL: /^\\d{5}$/ },\n * } as const;\n * ```\n */\nexport const REGEX = {\n COMMON,\n KR,\n} as const;\n\n// ─────────────────────────────────────────────\n// Low-level helpers\n// ─────────────────────────────────────────────\n\n/**\n * registry에서 dot-notation 경로로 `RegExp`를 꺼냅니다.\n *\n * 경로가 존재하지 않거나 RegExp가 아닌 곳을 가리키면 에러를 throw합니다.\n *\n * @param registry - 검색할 정규식 registry\n * @param path - 점으로 구분된 경로 (예: `\"KR.PHONE\"`)\n * @returns 경로가 가리키는 `RegExp`\n * @throws 경로가 잘못되었거나 RegExp가 아닌 경우\n * @example\n * ```ts\n * regexFor(REGEX, \"KR.PHONE\"); // /^(01[016-9])-(\\d{3,4})-(\\d{4})$/\n * ```\n */\nexport const regexFor = <T extends RegexRegistry>(\n registry: T,\n path: RegexPath<T>,\n): RegExp => {\n const segments = (path as string).split(\".\");\n let current: unknown = registry;\n for (const segment of segments) {\n if (current === null || typeof current !== \"object\" || current instanceof RegExp) {\n throw new Error(\n `regexFor: cannot traverse path \"${path as string}\" — \"${segment}\" is not a navigable object`,\n );\n }\n current = (current as Record<string, unknown>)[segment];\n if (current === undefined) {\n throw new Error(\n `regexFor: path \"${path as string}\" not found — missing segment \"${segment}\"`,\n );\n }\n }\n if (!(current instanceof RegExp)) {\n throw new Error(\n `regexFor: path \"${path as string}\" does not resolve to a RegExp (got ${typeof current})`,\n );\n }\n return current;\n};\n\n/**\n * 입력 문자열이 경로의 정규식과 매치되는지 검사합니다.\n *\n * @param registry - 정규식 registry\n * @param path - 경로\n * @param input - 검사할 문자열\n * @returns 매치 여부\n * @example\n * ```ts\n * matches(REGEX, \"KR.PHONE\", \"010-1234-5678\"); // true\n * matches(REGEX, \"KR.PHONE\", \"010-12-5678\"); // false\n * ```\n */\nexport const matches = <T extends RegexRegistry>(\n registry: T,\n path: RegexPath<T>,\n input: string,\n): boolean => regexFor(registry, path).test(input);\n\n/**\n * 입력 문자열을 경로의 정규식으로 치환합니다. 매치되지 않으면 `null`을 반환합니다.\n *\n * 원본 코드와 달리 매치 실패 시 입력 그대로 반환하지 않고 `null`을 돌려주므로, 호출 측이 실패를 명시적으로 처리할 수 있습니다.\n *\n * @param registry - 정규식 registry\n * @param path - 경로\n * @param input - 변환할 문자열\n * @param replacement - 치환 패턴 (문자열 또는 함수, `String.replace`와 동일)\n * @returns 치환된 문자열, 매치 안 되면 `null`\n * @example\n * ```ts\n * format(REGEX, \"KR.PHONE_DIGITS\", \"01012345678\", \"$1-$2-$3\");\n * // \"010-1234-5678\"\n *\n * format(REGEX, \"KR.PHONE_DIGITS\", \"abc\", \"$1-$2-$3\");\n * // null\n * ```\n */\nexport const format = <T extends RegexRegistry>(\n registry: T,\n path: RegexPath<T>,\n input: string,\n replacement: RegexReplacer,\n): string | null => {\n const regex = regexFor(registry, path);\n if (!regex.test(input)) return null;\n return typeof replacement === \"string\"\n ? input.replace(regex, replacement)\n : input.replace(regex, replacement);\n};\n\n// ─────────────────────────────────────────────\n// Ergonomic factory\n// ─────────────────────────────────────────────\n\n/**\n * {@link createRegexHelper}로 만들어지는 헬퍼의 메서드 모음.\n */\nexport interface RegexHelper<T extends RegexRegistry> {\n /** 경로로 RegExp 조회. {@link regexFor}와 동일. */\n regexFor: (path: RegexPath<T>) => RegExp;\n /** 입력이 경로의 정규식과 매치되는지. {@link matches}와 동일. */\n matches: (path: RegexPath<T>, input: string) => boolean;\n /** 정규식으로 치환. {@link format}와 동일, 매치 안 되면 `null`. */\n format: (\n path: RegexPath<T>,\n input: string,\n replacement: RegexReplacer,\n ) => string | null;\n /** 원본 registry 참조 (불변). */\n readonly registry: T;\n}\n\n/**\n * registry에 묶인 헬퍼 객체를 만듭니다. 매번 registry를 인자로 넘기는 번거로움을 없애는 용도.\n *\n * @param registry - 정규식 registry (`REGEX` 또는 커스텀)\n * @returns registry에 바인딩된 헬퍼\n * @example\n * ```ts\n * import { REGEX, createRegexHelper } from \"@jthong/util/regex\";\n *\n * const r = createRegexHelper(REGEX);\n * r.matches(\"KR.PHONE\", \"010-1234-5678\"); // true\n * r.format(\"KR.PHONE_DIGITS\", \"01012345678\", \"$1-$2-$3\"); // \"010-1234-5678\"\n *\n * // 커스텀 registry\n * const MY = { CUSTOM: { ZIP: /^\\d{5}$/ } } as const;\n * const my = createRegexHelper(MY);\n * my.matches(\"CUSTOM.ZIP\", \"06234\"); // true\n * ```\n */\nexport const createRegexHelper = <T extends RegexRegistry>(\n registry: T,\n): RegexHelper<T> => ({\n registry,\n regexFor: (path) => regexFor(registry, path),\n matches: (path, input) => matches(registry, path, input),\n format: (path, input, replacement) => format(registry, path, input, replacement),\n});\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/string/index.ts
|
|
4
|
+
var capitalize = (str) => str.length === 0 ? str : str[0].toUpperCase() + str.slice(1);
|
|
5
|
+
var camelToKebab = (str) => str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`).replace(/^-/, "");
|
|
6
|
+
var kebabToCamel = (str) => str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
7
|
+
var truncate = (str, max, suffix = "...") => str.length <= max ? str : str.slice(0, Math.max(0, max - suffix.length)) + suffix;
|
|
8
|
+
var formatNumber = (n, locale = "en-US") => new Intl.NumberFormat(locale).format(n);
|
|
9
|
+
var mask = (str, opts = {}) => {
|
|
10
|
+
const start = Math.max(0, opts.start ?? 1);
|
|
11
|
+
const end = Math.max(0, opts.end ?? 0);
|
|
12
|
+
const char = opts.char ?? "*";
|
|
13
|
+
if (str.length <= start + end) return str;
|
|
14
|
+
const masked = char.repeat(str.length - start - end);
|
|
15
|
+
return str.slice(0, start) + masked + str.slice(str.length - end);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
exports.camelToKebab = camelToKebab;
|
|
19
|
+
exports.capitalize = capitalize;
|
|
20
|
+
exports.formatNumber = formatNumber;
|
|
21
|
+
exports.kebabToCamel = kebabToCamel;
|
|
22
|
+
exports.mask = mask;
|
|
23
|
+
exports.truncate = truncate;
|
|
24
|
+
//# sourceMappingURL=index.cjs.map
|
|
25
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/string/index.ts"],"names":[],"mappings":";;;AAWO,IAAM,UAAA,GAAa,CAAC,GAAA,KACzB,GAAA,CAAI,WAAW,CAAA,GAAI,GAAA,GAAM,GAAA,CAAI,CAAC,CAAA,CAAG,WAAA,EAAY,GAAI,GAAA,CAAI,MAAM,CAAC;AAavD,IAAM,eAAe,CAAC,GAAA,KAC3B,GAAA,CAAI,OAAA,CAAQ,UAAU,CAAC,CAAA,KAAM,CAAA,CAAA,EAAI,CAAA,CAAE,aAAa,CAAA,CAAE,CAAA,CAAE,OAAA,CAAQ,MAAM,EAAE;AAa/D,IAAM,YAAA,GAAe,CAAC,GAAA,KAC3B,GAAA,CAAI,OAAA,CAAQ,WAAA,EAAa,CAAC,CAAA,EAAG,CAAA,KAAc,CAAA,CAAE,WAAA,EAAa;AAgBrD,IAAM,QAAA,GAAW,CAAC,GAAA,EAAa,GAAA,EAAa,SAAS,KAAA,KAC1D,GAAA,CAAI,UAAU,GAAA,GAAM,GAAA,GAAM,IAAI,KAAA,CAAM,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,MAAM,MAAA,CAAO,MAAM,CAAC,CAAA,GAAI;AAetE,IAAM,YAAA,GAAe,CAAC,CAAA,EAAW,MAAA,GAAS,OAAA,KAC/C,IAAI,IAAA,CAAK,YAAA,CAAa,MAAM,CAAA,CAAE,MAAA,CAAO,CAAC;AA8BjC,IAAM,IAAA,GAAO,CAAC,GAAA,EAAa,IAAA,GAAoB,EAAC,KAAc;AACnE,EAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,SAAS,CAAC,CAAA;AACzC,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,OAAO,CAAC,CAAA;AACrC,EAAA,MAAM,IAAA,GAAO,KAAK,IAAA,IAAQ,GAAA;AAC1B,EAAA,IAAI,GAAA,CAAI,MAAA,IAAU,KAAA,GAAQ,GAAA,EAAK,OAAO,GAAA;AACtC,EAAA,MAAM,SAAS,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,MAAA,GAAS,QAAQ,GAAG,CAAA;AACnD,EAAA,OAAO,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,GAAI,SAAS,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,MAAA,GAAS,GAAG,CAAA;AAClE","file":"index.cjs","sourcesContent":["/**\n * 문자열의 첫 글자를 대문자로 변환합니다.\n *\n * @param str - 변환할 문자열\n * @returns 첫 글자가 대문자가 된 문자열. 빈 문자열은 그대로 반환됩니다.\n * @example\n * ```ts\n * capitalize(\"hello\"); // \"Hello\"\n * capitalize(\"\"); // \"\"\n * ```\n */\nexport const capitalize = (str: string): string =>\n str.length === 0 ? str : str[0]!.toUpperCase() + str.slice(1);\n\n/**\n * camelCase 문자열을 kebab-case로 변환합니다.\n *\n * @param str - camelCase 문자열\n * @returns kebab-case로 변환된 문자열\n * @example\n * ```ts\n * camelToKebab(\"backgroundColor\"); // \"background-color\"\n * camelToKebab(\"HTMLParser\"); // \"h-t-m-l-parser\"\n * ```\n */\nexport const camelToKebab = (str: string): string =>\n str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`).replace(/^-/, \"\");\n\n/**\n * kebab-case 문자열을 camelCase로 변환합니다.\n *\n * @param str - kebab-case 문자열\n * @returns camelCase로 변환된 문자열\n * @example\n * ```ts\n * kebabToCamel(\"background-color\"); // \"backgroundColor\"\n * kebabToCamel(\"data-test-id\"); // \"dataTestId\"\n * ```\n */\nexport const kebabToCamel = (str: string): string =>\n str.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n\n/**\n * 문자열이 최대 길이를 초과하면 잘라내고 접미사를 붙입니다.\n *\n * @param str - 원본 문자열\n * @param max - 결과의 최대 길이 (접미사 포함)\n * @param suffix - 잘렸을 때 붙일 접미사 (기본값 `\"...\"`)\n * @returns 최대 길이 이하면 원본, 초과하면 잘린 문자열 + 접미사\n * @example\n * ```ts\n * truncate(\"hello world\", 8); // \"hello...\"\n * truncate(\"hi\", 5); // \"hi\" (변경 없음)\n * truncate(\"hello world\", 8, \"…\"); // \"hello w…\"\n * ```\n */\nexport const truncate = (str: string, max: number, suffix = \"...\"): string =>\n str.length <= max ? str : str.slice(0, Math.max(0, max - suffix.length)) + suffix;\n\n/**\n * 숫자에 천 단위 구분자를 추가합니다. `Intl.NumberFormat` 기반이라 음수/소수/로케일을 모두 지원합니다.\n *\n * @param n - 포맷할 숫자\n * @param locale - BCP 47 로케일 태그 (기본값 `\"en-US\"`)\n * @returns 천 단위 구분자가 들어간 문자열\n * @example\n * ```ts\n * formatNumber(1234567); // \"1,234,567\"\n * formatNumber(-1234.56); // \"-1,234.56\"\n * formatNumber(1234567, \"de-DE\"); // \"1.234.567\" (독일은 점이 구분자)\n * ```\n */\nexport const formatNumber = (n: number, locale = \"en-US\"): string =>\n new Intl.NumberFormat(locale).format(n);\n\n/**\n * {@link mask} 함수의 옵션.\n */\nexport interface MaskOptions {\n /** 앞에서 보존할 문자 수. 기본값 `1`. */\n start?: number;\n /** 뒤에서 보존할 문자 수. 기본값 `0`. */\n end?: number;\n /** 가릴 때 사용할 문자. 기본값 `\"*\"`. */\n char?: string;\n}\n\n/**\n * 문자열의 중간 일부를 마스킹 문자로 가립니다. 이메일/전화번호 등 민감 정보 표시에 유용합니다.\n *\n * 문자열이 `start + end`보다 짧으면 원본을 그대로 반환합니다.\n *\n * @param str - 원본 문자열\n * @param opts - 마스킹 옵션 ({@link MaskOptions} 참고)\n * @returns 마스킹된 문자열\n * @example\n * ```ts\n * mask(\"01012345678\", { start: 3, end: 4 }); // \"010****5678\"\n * mask(\"jthong@gmail.com\", { start: 1, end: 10 }); // \"j*****@gmail.com\"\n * mask(\"secret\", { start: 0, end: 0, char: \"#\" }); // \"######\"\n * mask(\"hi\", { start: 1, end: 1 }); // \"hi\" (너무 짧아 변경 없음)\n * ```\n */\nexport const mask = (str: string, opts: MaskOptions = {}): string => {\n const start = Math.max(0, opts.start ?? 1);\n const end = Math.max(0, opts.end ?? 0);\n const char = opts.char ?? \"*\";\n if (str.length <= start + end) return str;\n const masked = char.repeat(str.length - start - end);\n return str.slice(0, start) + masked + str.slice(str.length - end);\n};\n"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 문자열의 첫 글자를 대문자로 변환합니다.
|
|
3
|
+
*
|
|
4
|
+
* @param str - 변환할 문자열
|
|
5
|
+
* @returns 첫 글자가 대문자가 된 문자열. 빈 문자열은 그대로 반환됩니다.
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* capitalize("hello"); // "Hello"
|
|
9
|
+
* capitalize(""); // ""
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare const capitalize: (str: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* camelCase 문자열을 kebab-case로 변환합니다.
|
|
15
|
+
*
|
|
16
|
+
* @param str - camelCase 문자열
|
|
17
|
+
* @returns kebab-case로 변환된 문자열
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* camelToKebab("backgroundColor"); // "background-color"
|
|
21
|
+
* camelToKebab("HTMLParser"); // "h-t-m-l-parser"
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export declare const camelToKebab: (str: string) => string;
|
|
25
|
+
/**
|
|
26
|
+
* kebab-case 문자열을 camelCase로 변환합니다.
|
|
27
|
+
*
|
|
28
|
+
* @param str - kebab-case 문자열
|
|
29
|
+
* @returns camelCase로 변환된 문자열
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* kebabToCamel("background-color"); // "backgroundColor"
|
|
33
|
+
* kebabToCamel("data-test-id"); // "dataTestId"
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare const kebabToCamel: (str: string) => string;
|
|
37
|
+
/**
|
|
38
|
+
* 문자열이 최대 길이를 초과하면 잘라내고 접미사를 붙입니다.
|
|
39
|
+
*
|
|
40
|
+
* @param str - 원본 문자열
|
|
41
|
+
* @param max - 결과의 최대 길이 (접미사 포함)
|
|
42
|
+
* @param suffix - 잘렸을 때 붙일 접미사 (기본값 `"..."`)
|
|
43
|
+
* @returns 최대 길이 이하면 원본, 초과하면 잘린 문자열 + 접미사
|
|
44
|
+
* @example
|
|
45
|
+
* ```ts
|
|
46
|
+
* truncate("hello world", 8); // "hello..."
|
|
47
|
+
* truncate("hi", 5); // "hi" (변경 없음)
|
|
48
|
+
* truncate("hello world", 8, "…"); // "hello w…"
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare const truncate: (str: string, max: number, suffix?: string) => string;
|
|
52
|
+
/**
|
|
53
|
+
* 숫자에 천 단위 구분자를 추가합니다. `Intl.NumberFormat` 기반이라 음수/소수/로케일을 모두 지원합니다.
|
|
54
|
+
*
|
|
55
|
+
* @param n - 포맷할 숫자
|
|
56
|
+
* @param locale - BCP 47 로케일 태그 (기본값 `"en-US"`)
|
|
57
|
+
* @returns 천 단위 구분자가 들어간 문자열
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* formatNumber(1234567); // "1,234,567"
|
|
61
|
+
* formatNumber(-1234.56); // "-1,234.56"
|
|
62
|
+
* formatNumber(1234567, "de-DE"); // "1.234.567" (독일은 점이 구분자)
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare const formatNumber: (n: number, locale?: string) => string;
|
|
66
|
+
/**
|
|
67
|
+
* {@link mask} 함수의 옵션.
|
|
68
|
+
*/
|
|
69
|
+
export interface MaskOptions {
|
|
70
|
+
/** 앞에서 보존할 문자 수. 기본값 `1`. */
|
|
71
|
+
start?: number;
|
|
72
|
+
/** 뒤에서 보존할 문자 수. 기본값 `0`. */
|
|
73
|
+
end?: number;
|
|
74
|
+
/** 가릴 때 사용할 문자. 기본값 `"*"`. */
|
|
75
|
+
char?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 문자열의 중간 일부를 마스킹 문자로 가립니다. 이메일/전화번호 등 민감 정보 표시에 유용합니다.
|
|
79
|
+
*
|
|
80
|
+
* 문자열이 `start + end`보다 짧으면 원본을 그대로 반환합니다.
|
|
81
|
+
*
|
|
82
|
+
* @param str - 원본 문자열
|
|
83
|
+
* @param opts - 마스킹 옵션 ({@link MaskOptions} 참고)
|
|
84
|
+
* @returns 마스킹된 문자열
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* mask("01012345678", { start: 3, end: 4 }); // "010****5678"
|
|
88
|
+
* mask("jthong@gmail.com", { start: 1, end: 10 }); // "j*****@gmail.com"
|
|
89
|
+
* mask("secret", { start: 0, end: 0, char: "#" }); // "######"
|
|
90
|
+
* mask("hi", { start: 1, end: 1 }); // "hi" (너무 짧아 변경 없음)
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare const mask: (str: string, opts?: MaskOptions) => string;
|
|
94
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/string/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MACsB,CAAC;AAEhE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAC4B,CAAC;AAExE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MACkB,CAAC;AAE9D;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,EAAE,eAAc,KAAG,MACe,CAAC;AAEpF;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,EAAE,eAAgB,KAAG,MAClB,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,MAAM,EAAE,OAAM,WAAgB,KAAG,MAO1D,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/string/index.ts
|
|
2
|
+
var capitalize = (str) => str.length === 0 ? str : str[0].toUpperCase() + str.slice(1);
|
|
3
|
+
var camelToKebab = (str) => str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`).replace(/^-/, "");
|
|
4
|
+
var kebabToCamel = (str) => str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
5
|
+
var truncate = (str, max, suffix = "...") => str.length <= max ? str : str.slice(0, Math.max(0, max - suffix.length)) + suffix;
|
|
6
|
+
var formatNumber = (n, locale = "en-US") => new Intl.NumberFormat(locale).format(n);
|
|
7
|
+
var mask = (str, opts = {}) => {
|
|
8
|
+
const start = Math.max(0, opts.start ?? 1);
|
|
9
|
+
const end = Math.max(0, opts.end ?? 0);
|
|
10
|
+
const char = opts.char ?? "*";
|
|
11
|
+
if (str.length <= start + end) return str;
|
|
12
|
+
const masked = char.repeat(str.length - start - end);
|
|
13
|
+
return str.slice(0, start) + masked + str.slice(str.length - end);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export { camelToKebab, capitalize, formatNumber, kebabToCamel, mask, truncate };
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/string/index.ts"],"names":[],"mappings":";AAWO,IAAM,UAAA,GAAa,CAAC,GAAA,KACzB,GAAA,CAAI,WAAW,CAAA,GAAI,GAAA,GAAM,GAAA,CAAI,CAAC,CAAA,CAAG,WAAA,EAAY,GAAI,GAAA,CAAI,MAAM,CAAC;AAavD,IAAM,eAAe,CAAC,GAAA,KAC3B,GAAA,CAAI,OAAA,CAAQ,UAAU,CAAC,CAAA,KAAM,CAAA,CAAA,EAAI,CAAA,CAAE,aAAa,CAAA,CAAE,CAAA,CAAE,OAAA,CAAQ,MAAM,EAAE;AAa/D,IAAM,YAAA,GAAe,CAAC,GAAA,KAC3B,GAAA,CAAI,OAAA,CAAQ,WAAA,EAAa,CAAC,CAAA,EAAG,CAAA,KAAc,CAAA,CAAE,WAAA,EAAa;AAgBrD,IAAM,QAAA,GAAW,CAAC,GAAA,EAAa,GAAA,EAAa,SAAS,KAAA,KAC1D,GAAA,CAAI,UAAU,GAAA,GAAM,GAAA,GAAM,IAAI,KAAA,CAAM,CAAA,EAAG,KAAK,GAAA,CAAI,CAAA,EAAG,MAAM,MAAA,CAAO,MAAM,CAAC,CAAA,GAAI;AAetE,IAAM,YAAA,GAAe,CAAC,CAAA,EAAW,MAAA,GAAS,OAAA,KAC/C,IAAI,IAAA,CAAK,YAAA,CAAa,MAAM,CAAA,CAAE,MAAA,CAAO,CAAC;AA8BjC,IAAM,IAAA,GAAO,CAAC,GAAA,EAAa,IAAA,GAAoB,EAAC,KAAc;AACnE,EAAA,MAAM,QAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,SAAS,CAAC,CAAA;AACzC,EAAA,MAAM,MAAM,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,OAAO,CAAC,CAAA;AACrC,EAAA,MAAM,IAAA,GAAO,KAAK,IAAA,IAAQ,GAAA;AAC1B,EAAA,IAAI,GAAA,CAAI,MAAA,IAAU,KAAA,GAAQ,GAAA,EAAK,OAAO,GAAA;AACtC,EAAA,MAAM,SAAS,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,MAAA,GAAS,QAAQ,GAAG,CAAA;AACnD,EAAA,OAAO,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,GAAI,SAAS,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,MAAA,GAAS,GAAG,CAAA;AAClE","file":"index.js","sourcesContent":["/**\n * 문자열의 첫 글자를 대문자로 변환합니다.\n *\n * @param str - 변환할 문자열\n * @returns 첫 글자가 대문자가 된 문자열. 빈 문자열은 그대로 반환됩니다.\n * @example\n * ```ts\n * capitalize(\"hello\"); // \"Hello\"\n * capitalize(\"\"); // \"\"\n * ```\n */\nexport const capitalize = (str: string): string =>\n str.length === 0 ? str : str[0]!.toUpperCase() + str.slice(1);\n\n/**\n * camelCase 문자열을 kebab-case로 변환합니다.\n *\n * @param str - camelCase 문자열\n * @returns kebab-case로 변환된 문자열\n * @example\n * ```ts\n * camelToKebab(\"backgroundColor\"); // \"background-color\"\n * camelToKebab(\"HTMLParser\"); // \"h-t-m-l-parser\"\n * ```\n */\nexport const camelToKebab = (str: string): string =>\n str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`).replace(/^-/, \"\");\n\n/**\n * kebab-case 문자열을 camelCase로 변환합니다.\n *\n * @param str - kebab-case 문자열\n * @returns camelCase로 변환된 문자열\n * @example\n * ```ts\n * kebabToCamel(\"background-color\"); // \"backgroundColor\"\n * kebabToCamel(\"data-test-id\"); // \"dataTestId\"\n * ```\n */\nexport const kebabToCamel = (str: string): string =>\n str.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n\n/**\n * 문자열이 최대 길이를 초과하면 잘라내고 접미사를 붙입니다.\n *\n * @param str - 원본 문자열\n * @param max - 결과의 최대 길이 (접미사 포함)\n * @param suffix - 잘렸을 때 붙일 접미사 (기본값 `\"...\"`)\n * @returns 최대 길이 이하면 원본, 초과하면 잘린 문자열 + 접미사\n * @example\n * ```ts\n * truncate(\"hello world\", 8); // \"hello...\"\n * truncate(\"hi\", 5); // \"hi\" (변경 없음)\n * truncate(\"hello world\", 8, \"…\"); // \"hello w…\"\n * ```\n */\nexport const truncate = (str: string, max: number, suffix = \"...\"): string =>\n str.length <= max ? str : str.slice(0, Math.max(0, max - suffix.length)) + suffix;\n\n/**\n * 숫자에 천 단위 구분자를 추가합니다. `Intl.NumberFormat` 기반이라 음수/소수/로케일을 모두 지원합니다.\n *\n * @param n - 포맷할 숫자\n * @param locale - BCP 47 로케일 태그 (기본값 `\"en-US\"`)\n * @returns 천 단위 구분자가 들어간 문자열\n * @example\n * ```ts\n * formatNumber(1234567); // \"1,234,567\"\n * formatNumber(-1234.56); // \"-1,234.56\"\n * formatNumber(1234567, \"de-DE\"); // \"1.234.567\" (독일은 점이 구분자)\n * ```\n */\nexport const formatNumber = (n: number, locale = \"en-US\"): string =>\n new Intl.NumberFormat(locale).format(n);\n\n/**\n * {@link mask} 함수의 옵션.\n */\nexport interface MaskOptions {\n /** 앞에서 보존할 문자 수. 기본값 `1`. */\n start?: number;\n /** 뒤에서 보존할 문자 수. 기본값 `0`. */\n end?: number;\n /** 가릴 때 사용할 문자. 기본값 `\"*\"`. */\n char?: string;\n}\n\n/**\n * 문자열의 중간 일부를 마스킹 문자로 가립니다. 이메일/전화번호 등 민감 정보 표시에 유용합니다.\n *\n * 문자열이 `start + end`보다 짧으면 원본을 그대로 반환합니다.\n *\n * @param str - 원본 문자열\n * @param opts - 마스킹 옵션 ({@link MaskOptions} 참고)\n * @returns 마스킹된 문자열\n * @example\n * ```ts\n * mask(\"01012345678\", { start: 3, end: 4 }); // \"010****5678\"\n * mask(\"jthong@gmail.com\", { start: 1, end: 10 }); // \"j*****@gmail.com\"\n * mask(\"secret\", { start: 0, end: 0, char: \"#\" }); // \"######\"\n * mask(\"hi\", { start: 1, end: 1 }); // \"hi\" (너무 짧아 변경 없음)\n * ```\n */\nexport const mask = (str: string, opts: MaskOptions = {}): string => {\n const start = Math.max(0, opts.start ?? 1);\n const end = Math.max(0, opts.end ?? 0);\n const char = opts.char ?? \"*\";\n if (str.length <= start + end) return str;\n const masked = char.repeat(str.length - start - end);\n return str.slice(0, start) + masked + str.slice(str.length - end);\n};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jthong/util",
|
|
3
|
+
"version": "0.0.0-beta-20260522002050",
|
|
4
|
+
"description": "Framework-agnostic TypeScript utility functions",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
},
|
|
17
|
+
"./string": {
|
|
18
|
+
"types": "./dist/string/index.d.ts",
|
|
19
|
+
"import": "./dist/string/index.js",
|
|
20
|
+
"require": "./dist/string/index.cjs"
|
|
21
|
+
},
|
|
22
|
+
"./array": {
|
|
23
|
+
"types": "./dist/array/index.d.ts",
|
|
24
|
+
"import": "./dist/array/index.js",
|
|
25
|
+
"require": "./dist/array/index.cjs"
|
|
26
|
+
},
|
|
27
|
+
"./object": {
|
|
28
|
+
"types": "./dist/object/index.d.ts",
|
|
29
|
+
"import": "./dist/object/index.js",
|
|
30
|
+
"require": "./dist/object/index.cjs"
|
|
31
|
+
},
|
|
32
|
+
"./fn": {
|
|
33
|
+
"types": "./dist/fn/index.d.ts",
|
|
34
|
+
"import": "./dist/fn/index.js",
|
|
35
|
+
"require": "./dist/fn/index.cjs"
|
|
36
|
+
},
|
|
37
|
+
"./regex": {
|
|
38
|
+
"types": "./dist/regex/index.d.ts",
|
|
39
|
+
"import": "./dist/regex/index.js",
|
|
40
|
+
"require": "./dist/regex/index.cjs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"tsup": "^8.3.5",
|
|
49
|
+
"typescript": "^5.7.2",
|
|
50
|
+
"vitest": "^2.1.8"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsup && tsc --emitDeclarationOnly --declaration --declarationMap --outDir dist",
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"typecheck": "tsc --noEmit",
|
|
61
|
+
"clean": "rm -rf dist .turbo"
|
|
62
|
+
}
|
|
63
|
+
}
|