@gzl10/ts-helpers 4.2.1
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/CHANGELOG.md +320 -0
- package/README.md +233 -0
- package/USAGE-GUIDE.md +800 -0
- package/dist/browser/async.js +15 -0
- package/dist/browser/async.js.map +1 -0
- package/dist/browser/chunk-4O7ZPIJN.js +383 -0
- package/dist/browser/chunk-4O7ZPIJN.js.map +1 -0
- package/dist/browser/chunk-75XNTC34.js +60 -0
- package/dist/browser/chunk-75XNTC34.js.map +1 -0
- package/dist/browser/chunk-C3D7YZVE.js +299 -0
- package/dist/browser/chunk-C3D7YZVE.js.map +1 -0
- package/dist/browser/chunk-CZL6C2EI.js +452 -0
- package/dist/browser/chunk-CZL6C2EI.js.map +1 -0
- package/dist/browser/chunk-D4FZFIVA.js +240 -0
- package/dist/browser/chunk-D4FZFIVA.js.map +1 -0
- package/dist/browser/chunk-IL7NG7IC.js +72 -0
- package/dist/browser/chunk-IL7NG7IC.js.map +1 -0
- package/dist/browser/chunk-NSBPE2FW.js +17 -0
- package/dist/browser/chunk-NSBPE2FW.js.map +1 -0
- package/dist/browser/chunk-SLQVNPTH.js +27 -0
- package/dist/browser/chunk-SLQVNPTH.js.map +1 -0
- package/dist/browser/chunk-WG7ILCUB.js +195 -0
- package/dist/browser/chunk-WG7ILCUB.js.map +1 -0
- package/dist/browser/chunk-WJA4JDMZ.js +278 -0
- package/dist/browser/chunk-WJA4JDMZ.js.map +1 -0
- package/dist/browser/chunk-ZFVYLUTT.js +65 -0
- package/dist/browser/chunk-ZFVYLUTT.js.map +1 -0
- package/dist/browser/chunk-ZYTSVMTI.js +263 -0
- package/dist/browser/chunk-ZYTSVMTI.js.map +1 -0
- package/dist/browser/dates.js +78 -0
- package/dist/browser/dates.js.map +1 -0
- package/dist/browser/environment-detection.js +21 -0
- package/dist/browser/environment-detection.js.map +1 -0
- package/dist/browser/environment.js +34 -0
- package/dist/browser/environment.js.map +1 -0
- package/dist/browser/errors.js +18 -0
- package/dist/browser/errors.js.map +1 -0
- package/dist/browser/index.js +412 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/math.js +51 -0
- package/dist/browser/math.js.map +1 -0
- package/dist/browser/number.js +10 -0
- package/dist/browser/number.js.map +1 -0
- package/dist/browser/objects.js +31 -0
- package/dist/browser/objects.js.map +1 -0
- package/dist/browser/strings.js +80 -0
- package/dist/browser/strings.js.map +1 -0
- package/dist/browser/validation-core.js +54 -0
- package/dist/browser/validation-core.js.map +1 -0
- package/dist/browser/validation-crypto.js +28 -0
- package/dist/browser/validation-crypto.js.map +1 -0
- package/dist/browser/validators.js +98 -0
- package/dist/browser/validators.js.map +1 -0
- package/dist/cjs/async.js +86 -0
- package/dist/cjs/async.js.map +1 -0
- package/dist/cjs/dates.js +285 -0
- package/dist/cjs/dates.js.map +1 -0
- package/dist/cjs/environment-detection.js +84 -0
- package/dist/cjs/environment-detection.js.map +1 -0
- package/dist/cjs/environment.js +261 -0
- package/dist/cjs/environment.js.map +1 -0
- package/dist/cjs/errors.js +80 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.js +2035 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/math.js +388 -0
- package/dist/cjs/math.js.map +1 -0
- package/dist/cjs/number.js +37 -0
- package/dist/cjs/number.js.map +1 -0
- package/dist/cjs/objects.js +249 -0
- package/dist/cjs/objects.js.map +1 -0
- package/dist/cjs/strings.js +253 -0
- package/dist/cjs/strings.js.map +1 -0
- package/dist/cjs/validation.js +450 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/esm/async.js +15 -0
- package/dist/esm/async.js.map +1 -0
- package/dist/esm/chunk-4O7ZPIJN.js +383 -0
- package/dist/esm/chunk-4O7ZPIJN.js.map +1 -0
- package/dist/esm/chunk-75XNTC34.js +60 -0
- package/dist/esm/chunk-75XNTC34.js.map +1 -0
- package/dist/esm/chunk-BDOBKBKA.js +72 -0
- package/dist/esm/chunk-BDOBKBKA.js.map +1 -0
- package/dist/esm/chunk-C3D7YZVE.js +299 -0
- package/dist/esm/chunk-C3D7YZVE.js.map +1 -0
- package/dist/esm/chunk-CZL6C2EI.js +452 -0
- package/dist/esm/chunk-CZL6C2EI.js.map +1 -0
- package/dist/esm/chunk-EBLSTOEC.js +263 -0
- package/dist/esm/chunk-EBLSTOEC.js.map +1 -0
- package/dist/esm/chunk-NSBPE2FW.js +17 -0
- package/dist/esm/chunk-NSBPE2FW.js.map +1 -0
- package/dist/esm/chunk-SLQVNPTH.js +27 -0
- package/dist/esm/chunk-SLQVNPTH.js.map +1 -0
- package/dist/esm/chunk-WG7ILCUB.js +195 -0
- package/dist/esm/chunk-WG7ILCUB.js.map +1 -0
- package/dist/esm/chunk-WJA4JDMZ.js +278 -0
- package/dist/esm/chunk-WJA4JDMZ.js.map +1 -0
- package/dist/esm/chunk-ZFVYLUTT.js +65 -0
- package/dist/esm/chunk-ZFVYLUTT.js.map +1 -0
- package/dist/esm/dates.js +78 -0
- package/dist/esm/dates.js.map +1 -0
- package/dist/esm/environment-detection.js +21 -0
- package/dist/esm/environment-detection.js.map +1 -0
- package/dist/esm/environment.js +34 -0
- package/dist/esm/environment.js.map +1 -0
- package/dist/esm/errors.js +18 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.js +380 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/math.js +51 -0
- package/dist/esm/math.js.map +1 -0
- package/dist/esm/number.js +10 -0
- package/dist/esm/number.js.map +1 -0
- package/dist/esm/objects.js +31 -0
- package/dist/esm/objects.js.map +1 -0
- package/dist/esm/strings.js +80 -0
- package/dist/esm/strings.js.map +1 -0
- package/dist/esm/validation.js +54 -0
- package/dist/esm/validation.js.map +1 -0
- package/dist/node/async.js +93 -0
- package/dist/node/async.js.map +1 -0
- package/dist/node/csv.js +102 -0
- package/dist/node/csv.js.map +1 -0
- package/dist/node/data.js +880 -0
- package/dist/node/data.js.map +1 -0
- package/dist/node/dates.js +324 -0
- package/dist/node/dates.js.map +1 -0
- package/dist/node/environment.js +278 -0
- package/dist/node/environment.js.map +1 -0
- package/dist/node/errors.js +89 -0
- package/dist/node/errors.js.map +1 -0
- package/dist/node/index.js +3151 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/json.js +107 -0
- package/dist/node/json.js.map +1 -0
- package/dist/node/math.js +413 -0
- package/dist/node/math.js.map +1 -0
- package/dist/node/number.js +42 -0
- package/dist/node/number.js.map +1 -0
- package/dist/node/objects.js +264 -0
- package/dist/node/objects.js.map +1 -0
- package/dist/node/strings.js +293 -0
- package/dist/node/strings.js.map +1 -0
- package/dist/node/tree.js +89 -0
- package/dist/node/tree.js.map +1 -0
- package/dist/node/validation-core.js +477 -0
- package/dist/node/validation-core.js.map +1 -0
- package/dist/node/validation-crypto.js +179 -0
- package/dist/node/validation-crypto.js.map +1 -0
- package/dist/node/validation.js +677 -0
- package/dist/node/validation.js.map +1 -0
- package/dist/node/validators.js +123 -0
- package/dist/node/validators.js.map +1 -0
- package/dist/node-esm/async.js +15 -0
- package/dist/node-esm/async.js.map +1 -0
- package/dist/node-esm/chunk-3YOF7NPT.js +299 -0
- package/dist/node-esm/chunk-3YOF7NPT.js.map +1 -0
- package/dist/node-esm/chunk-64TBXJQS.js +263 -0
- package/dist/node-esm/chunk-64TBXJQS.js.map +1 -0
- package/dist/node-esm/chunk-75XNTC34.js +60 -0
- package/dist/node-esm/chunk-75XNTC34.js.map +1 -0
- package/dist/node-esm/chunk-C4PKXIPB.js +278 -0
- package/dist/node-esm/chunk-C4PKXIPB.js.map +1 -0
- package/dist/node-esm/chunk-CMDFZME3.js +452 -0
- package/dist/node-esm/chunk-CMDFZME3.js.map +1 -0
- package/dist/node-esm/chunk-DZZPUYMP.js +74 -0
- package/dist/node-esm/chunk-DZZPUYMP.js.map +1 -0
- package/dist/node-esm/chunk-HTSEHRHI.js +195 -0
- package/dist/node-esm/chunk-HTSEHRHI.js.map +1 -0
- package/dist/node-esm/chunk-JCAUVOPH.js +27 -0
- package/dist/node-esm/chunk-JCAUVOPH.js.map +1 -0
- package/dist/node-esm/chunk-KBHE3K2F.js +505 -0
- package/dist/node-esm/chunk-KBHE3K2F.js.map +1 -0
- package/dist/node-esm/chunk-LYTET5NX.js +65 -0
- package/dist/node-esm/chunk-LYTET5NX.js.map +1 -0
- package/dist/node-esm/chunk-PZ5AY32C.js +10 -0
- package/dist/node-esm/chunk-PZ5AY32C.js.map +1 -0
- package/dist/node-esm/chunk-UKGXL2QO.js +383 -0
- package/dist/node-esm/chunk-UKGXL2QO.js.map +1 -0
- package/dist/node-esm/chunk-XAEYT23H.js +164 -0
- package/dist/node-esm/chunk-XAEYT23H.js.map +1 -0
- package/dist/node-esm/csv.js +63 -0
- package/dist/node-esm/csv.js.map +1 -0
- package/dist/node-esm/data.js +32 -0
- package/dist/node-esm/data.js.map +1 -0
- package/dist/node-esm/dates.js +78 -0
- package/dist/node-esm/dates.js.map +1 -0
- package/dist/node-esm/environment.js +34 -0
- package/dist/node-esm/environment.js.map +1 -0
- package/dist/node-esm/errors.js +18 -0
- package/dist/node-esm/errors.js.map +1 -0
- package/dist/node-esm/index.js +426 -0
- package/dist/node-esm/index.js.map +1 -0
- package/dist/node-esm/json.js +68 -0
- package/dist/node-esm/json.js.map +1 -0
- package/dist/node-esm/math.js +51 -0
- package/dist/node-esm/math.js.map +1 -0
- package/dist/node-esm/number.js +10 -0
- package/dist/node-esm/number.js.map +1 -0
- package/dist/node-esm/objects.js +31 -0
- package/dist/node-esm/objects.js.map +1 -0
- package/dist/node-esm/strings.js +80 -0
- package/dist/node-esm/strings.js.map +1 -0
- package/dist/node-esm/tree.js +8 -0
- package/dist/node-esm/tree.js.map +1 -0
- package/dist/node-esm/validation-core.js +54 -0
- package/dist/node-esm/validation-core.js.map +1 -0
- package/dist/node-esm/validation-crypto.js +26 -0
- package/dist/node-esm/validation-crypto.js.map +1 -0
- package/dist/node-esm/validation.js +606 -0
- package/dist/node-esm/validation.js.map +1 -0
- package/dist/node-esm/validators.js +98 -0
- package/dist/node-esm/validators.js.map +1 -0
- package/dist/types/async-C8gvbSG-.d.ts +453 -0
- package/dist/types/async.d.ts +1 -0
- package/dist/types/csv.d.ts +226 -0
- package/dist/types/data.d.ts +1561 -0
- package/dist/types/dates-hTiE0Z11.d.ts +298 -0
- package/dist/types/dates.d.ts +1 -0
- package/dist/types/environment-B8eLS7KT.d.ts +420 -0
- package/dist/types/environment-detection.d.ts +102 -0
- package/dist/types/environment.d.ts +1 -0
- package/dist/types/errors.d.ts +147 -0
- package/dist/types/index.d.ts +211 -0
- package/dist/types/json.d.ts +284 -0
- package/dist/types/math-BQ9Lwdp7.d.ts +2060 -0
- package/dist/types/math.d.ts +1 -0
- package/dist/types/number-CYnQfLWj.d.ts +44 -0
- package/dist/types/number.d.ts +1 -0
- package/dist/types/objects-BohS8GCS.d.ts +1185 -0
- package/dist/types/objects.d.ts +1 -0
- package/dist/types/strings-CiqRPYLL.d.ts +1349 -0
- package/dist/types/strings.d.ts +1 -0
- package/dist/types/tree.d.ts +284 -0
- package/dist/types/validation-core-DfHF8rCG.d.ts +238 -0
- package/dist/types/validation-crypto-browser.d.ts +56 -0
- package/dist/types/validation-crypto-node.d.ts +31 -0
- package/dist/types/validation.d.ts +1 -0
- package/dist/types/validators.d.ts +216 -0
- package/package.json +253 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/strings.ts
|
|
21
|
+
var strings_exports = {};
|
|
22
|
+
__export(strings_exports, {
|
|
23
|
+
capitalizeEachWord: () => capitalizeEachWord,
|
|
24
|
+
capitalizeFirst: () => capitalizeFirst,
|
|
25
|
+
cleanJsonChars: () => cleanJsonChars,
|
|
26
|
+
contains: () => contains,
|
|
27
|
+
countOccurrences: () => countOccurrences,
|
|
28
|
+
endsWith: () => endsWith,
|
|
29
|
+
ensureEndsWith: () => ensureEndsWith,
|
|
30
|
+
ensureStartsWith: () => ensureStartsWith,
|
|
31
|
+
envKeyToPath: () => envKeyToPath,
|
|
32
|
+
escapeHtmlChars: () => escapeHtmlChars,
|
|
33
|
+
isEmail: () => isEmail,
|
|
34
|
+
isEmpty: () => isEmpty,
|
|
35
|
+
matchPathPattern: () => matchPathPattern,
|
|
36
|
+
padEnd: () => padEnd,
|
|
37
|
+
padStart: () => padStart,
|
|
38
|
+
pathToEnvKey: () => pathToEnvKey,
|
|
39
|
+
removeAccents: () => removeAccents,
|
|
40
|
+
repeatString: () => repeatString,
|
|
41
|
+
replaceAllOccurrences: () => replaceAllOccurrences,
|
|
42
|
+
reverseString: () => reverseString,
|
|
43
|
+
sanitizeString: () => sanitizeString,
|
|
44
|
+
startsWith: () => startsWith,
|
|
45
|
+
stripFromEnd: () => stripFromEnd,
|
|
46
|
+
stripFromStart: () => stripFromStart,
|
|
47
|
+
toCamelCase: () => toCamelCase,
|
|
48
|
+
toKebabCase: () => toKebabCase,
|
|
49
|
+
toLowerCase: () => toLowerCase,
|
|
50
|
+
toPascalCase: () => toPascalCase,
|
|
51
|
+
toSnakeCase: () => toSnakeCase,
|
|
52
|
+
toUpperCase: () => toUpperCase,
|
|
53
|
+
toUrlSlug: () => toUrlSlug,
|
|
54
|
+
trim: () => trim,
|
|
55
|
+
trimEnd: () => trimEnd,
|
|
56
|
+
trimStart: () => trimStart,
|
|
57
|
+
truncateString: () => truncateString,
|
|
58
|
+
unescapeHtmlChars: () => unescapeHtmlChars,
|
|
59
|
+
unescapeUnicode: () => unescapeUnicode
|
|
60
|
+
});
|
|
61
|
+
module.exports = __toCommonJS(strings_exports);
|
|
62
|
+
var sanitizeString = (instr) => {
|
|
63
|
+
if (!instr) return "";
|
|
64
|
+
return addDash(instr.replace(/[^a-zA-Z0-9 ]/g, "")).toLowerCase();
|
|
65
|
+
};
|
|
66
|
+
function addDash(str) {
|
|
67
|
+
const str2 = str.replace(/[`~!@#$%^&*()_|+\-=?;:'",.<>{}[\]\\/]/gi, "");
|
|
68
|
+
return str2.replace(/ /g, "-");
|
|
69
|
+
}
|
|
70
|
+
var cleanJsonChars = (texto) => {
|
|
71
|
+
if (!texto) return "";
|
|
72
|
+
return texto.replace(/({|}|&|amp;|gt;)/g, "");
|
|
73
|
+
};
|
|
74
|
+
var truncateString = (str, maxlength = 80, suffix = "...") => {
|
|
75
|
+
if (!str) return "";
|
|
76
|
+
return str.length > maxlength ? str.substring(0, maxlength) + suffix : str;
|
|
77
|
+
};
|
|
78
|
+
var unescapeUnicode = (input) => {
|
|
79
|
+
return JSON.stringify(JSON.parse(`"${input}"`)).replace(/^"(.*)"$/, "$1");
|
|
80
|
+
};
|
|
81
|
+
var ensureEndsWith = (str, trailing) => {
|
|
82
|
+
return str.endsWith(trailing) ? str : `${str}${trailing}`;
|
|
83
|
+
};
|
|
84
|
+
var stripFromEnd = (str, trailing) => {
|
|
85
|
+
return str.endsWith(trailing) ? str.slice(0, -1 * trailing.length) : str;
|
|
86
|
+
};
|
|
87
|
+
var ensureStartsWith = (str, leading) => {
|
|
88
|
+
return str.startsWith(leading) ? str : `${leading}${str}`;
|
|
89
|
+
};
|
|
90
|
+
var stripFromStart = (str, leading) => {
|
|
91
|
+
return str.startsWith(leading) ? str.slice(leading.length) : str;
|
|
92
|
+
};
|
|
93
|
+
var toLowerCase = (str) => {
|
|
94
|
+
return str.toLowerCase();
|
|
95
|
+
};
|
|
96
|
+
var toUpperCase = (str) => {
|
|
97
|
+
return str.toUpperCase();
|
|
98
|
+
};
|
|
99
|
+
var capitalizeFirst = (str) => {
|
|
100
|
+
if (!str) return "";
|
|
101
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
102
|
+
};
|
|
103
|
+
var capitalizeEachWord = (str) => {
|
|
104
|
+
if (!str) return "";
|
|
105
|
+
return str.split(" ").map((word) => capitalizeFirst(word)).join(" ");
|
|
106
|
+
};
|
|
107
|
+
var toCamelCase = (str) => {
|
|
108
|
+
if (!str) return "";
|
|
109
|
+
const words = str.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[-_\s]+/).filter((word) => word.length > 0).map((word) => word.toLowerCase());
|
|
110
|
+
if (words.length === 0) return "";
|
|
111
|
+
return words[0] + words.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
112
|
+
};
|
|
113
|
+
var toSnakeCase = (str) => {
|
|
114
|
+
if (!str) return "";
|
|
115
|
+
return str.replace(/\W+/g, " ").split(/ |\B(?=[A-Z])/).map((word) => word.toLowerCase()).join("_");
|
|
116
|
+
};
|
|
117
|
+
var toKebabCase = (str) => {
|
|
118
|
+
if (!str) return "";
|
|
119
|
+
return str.replace(/[_\s]+/g, "-").replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
120
|
+
};
|
|
121
|
+
var toPascalCase = (str) => {
|
|
122
|
+
if (!str) return "";
|
|
123
|
+
return str.replace(/[-_\s]+(.)?/g, (_, char) => char ? char.toUpperCase() : "").replace(/^./, (char) => char.toUpperCase());
|
|
124
|
+
};
|
|
125
|
+
var contains = (str, searchStr, caseSensitive = false) => {
|
|
126
|
+
if (caseSensitive) {
|
|
127
|
+
return str.includes(searchStr);
|
|
128
|
+
}
|
|
129
|
+
return str.toLowerCase().includes(searchStr.toLowerCase());
|
|
130
|
+
};
|
|
131
|
+
var startsWith = (str, searchStr, caseSensitive = false) => {
|
|
132
|
+
if (caseSensitive) {
|
|
133
|
+
return str.startsWith(searchStr);
|
|
134
|
+
}
|
|
135
|
+
return str.toLowerCase().startsWith(searchStr.toLowerCase());
|
|
136
|
+
};
|
|
137
|
+
var endsWith = (str, searchStr, caseSensitive = false) => {
|
|
138
|
+
if (caseSensitive) {
|
|
139
|
+
return str.endsWith(searchStr);
|
|
140
|
+
}
|
|
141
|
+
return str.toLowerCase().endsWith(searchStr.toLowerCase());
|
|
142
|
+
};
|
|
143
|
+
var padStart = (str, length, padChar = " ") => {
|
|
144
|
+
return str.padStart(length, padChar);
|
|
145
|
+
};
|
|
146
|
+
var padEnd = (str, length, padChar = " ") => {
|
|
147
|
+
if (str.length >= length) return str;
|
|
148
|
+
return str + padChar.repeat(length - str.length);
|
|
149
|
+
};
|
|
150
|
+
var trim = (str) => {
|
|
151
|
+
return str.trim();
|
|
152
|
+
};
|
|
153
|
+
var trimStart = (str) => {
|
|
154
|
+
return str.trimStart();
|
|
155
|
+
};
|
|
156
|
+
var trimEnd = (str) => {
|
|
157
|
+
return str.trimEnd();
|
|
158
|
+
};
|
|
159
|
+
var reverseString = (str) => {
|
|
160
|
+
return str.split("").reverse().join("");
|
|
161
|
+
};
|
|
162
|
+
var repeatString = (str, times) => {
|
|
163
|
+
return str.repeat(times);
|
|
164
|
+
};
|
|
165
|
+
var replaceAllOccurrences = (str, searchStr, replaceStr) => {
|
|
166
|
+
return str.split(searchStr).join(replaceStr);
|
|
167
|
+
};
|
|
168
|
+
var countOccurrences = (str, searchStr) => {
|
|
169
|
+
if (!searchStr) return 0;
|
|
170
|
+
let count = 0;
|
|
171
|
+
let position = 0;
|
|
172
|
+
while ((position = str.indexOf(searchStr, position)) !== -1) {
|
|
173
|
+
count++;
|
|
174
|
+
position += 1;
|
|
175
|
+
}
|
|
176
|
+
return count;
|
|
177
|
+
};
|
|
178
|
+
var isEmpty = (str) => {
|
|
179
|
+
return !str || str.trim().length === 0;
|
|
180
|
+
};
|
|
181
|
+
var isEmail = (str) => {
|
|
182
|
+
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
183
|
+
return re.test(str.toLowerCase());
|
|
184
|
+
};
|
|
185
|
+
var toUrlSlug = (str) => {
|
|
186
|
+
return str.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
187
|
+
};
|
|
188
|
+
var removeAccents = (str) => {
|
|
189
|
+
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
190
|
+
};
|
|
191
|
+
var escapeHtmlChars = (str) => {
|
|
192
|
+
const htmlEscapes = {
|
|
193
|
+
"&": "&",
|
|
194
|
+
"<": "<",
|
|
195
|
+
">": ">",
|
|
196
|
+
'"': """,
|
|
197
|
+
"'": "'"
|
|
198
|
+
};
|
|
199
|
+
return str.replace(/[&<>"']/g, (match) => htmlEscapes[match]);
|
|
200
|
+
};
|
|
201
|
+
var unescapeHtmlChars = (str) => {
|
|
202
|
+
const htmlUnescapes = {
|
|
203
|
+
"&": "&",
|
|
204
|
+
"<": "<",
|
|
205
|
+
">": ">",
|
|
206
|
+
""": '"',
|
|
207
|
+
"'": "'"
|
|
208
|
+
};
|
|
209
|
+
return str.replace(/&|<|>|"|'/g, (match) => htmlUnescapes[match]);
|
|
210
|
+
};
|
|
211
|
+
function matchPathPattern(path, pattern) {
|
|
212
|
+
if (!path || typeof path !== "string" || !pattern || typeof pattern !== "string") {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
if (path === pattern) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
if (!pattern.includes("*")) {
|
|
219
|
+
return path === pattern;
|
|
220
|
+
}
|
|
221
|
+
const regexPattern = pattern.split(".").map((segment) => {
|
|
222
|
+
if (segment === "*") {
|
|
223
|
+
return "[^.]+";
|
|
224
|
+
}
|
|
225
|
+
return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
226
|
+
}).join("\\.");
|
|
227
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
228
|
+
return regex.test(path);
|
|
229
|
+
}
|
|
230
|
+
function envKeyToPath(envKey, prefix = "NX") {
|
|
231
|
+
if (!envKey || typeof envKey !== "string") {
|
|
232
|
+
return "";
|
|
233
|
+
}
|
|
234
|
+
let key = envKey.trim();
|
|
235
|
+
if (prefix && key.startsWith(`${prefix}_`)) {
|
|
236
|
+
key = key.substring(prefix.length + 1);
|
|
237
|
+
}
|
|
238
|
+
return key.toLowerCase().replace(/_/g, ".");
|
|
239
|
+
}
|
|
240
|
+
function pathToEnvKey(path, prefix = "NX") {
|
|
241
|
+
if (!path || typeof path !== "string") {
|
|
242
|
+
return "";
|
|
243
|
+
}
|
|
244
|
+
const key = path.trim().toUpperCase().replace(/\./g, "_");
|
|
245
|
+
if (key.length === 0) {
|
|
246
|
+
return "";
|
|
247
|
+
}
|
|
248
|
+
if (prefix && prefix.length > 0) {
|
|
249
|
+
return `${prefix.toUpperCase()}_${key}`;
|
|
250
|
+
}
|
|
251
|
+
return key;
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=strings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/strings.ts"],"sourcesContent":["/**\n * String manipulation utilities\n * Consolidated from primitives/string module\n */\n\n/**\n * Sanitizes a string by removing special characters and converting to lowercase with dashes\n *\n * Produces clean, URL-friendly strings by removing special characters and normalizing whitespace.\n * Useful for slugs, IDs, CSS class names, and filename generation.\n *\n * Transformation rules:\n * 1. Remove all non-alphanumeric characters except spaces\n * 2. Remove additional punctuation (`~!@#$%^&*()_|+-=?;:'\",.<>{}[]\\\\/`)\n * 3. Replace spaces with dashes\n * 4. Convert to lowercase\n *\n * @param instr - Input string to sanitize (null returns empty string)\n * @returns Sanitized string in lowercase with dashes replacing spaces\n *\n * @example\n * ```typescript\n * // Basic sanitization\n * sanitizeString('Hello World!') // 'hello-world'\n * sanitizeString('user@example.com') // 'userexamplecom'\n * sanitizeString('Price: $19.99') // 'price-1999'\n *\n * // Remove accents and special chars\n * sanitizeString('Café Münchën') // 'caf-mnchen'\n * sanitizeString('北京 2024') // '2024'\n *\n * // Multiple spaces/dashes\n * sanitizeString('hello world') // 'hello---world'\n * sanitizeString('foo-bar_baz') // 'foo-barbaz'\n *\n * // Edge cases\n * sanitizeString(null) // ''\n * sanitizeString('') // ''\n * sanitizeString(' ') // '---'\n * sanitizeString('123') // '123'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Generate CSS class names from user input\n * function generateClassName(userInput: string): string {\n * return `custom-${sanitizeString(userInput)}`\n * }\n *\n * generateClassName('My Component!') // 'custom-my-component'\n * generateClassName('Button (Primary)') // 'custom-button-primary'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Clean product names for file export\n * const products = [\n * { name: 'T-Shirt (Blue)', sku: 'TS-001' },\n * { name: 'Shoes & Accessories', sku: 'SH-042' }\n * ]\n *\n * products.forEach(p => {\n * const filename = `${sanitizeString(p.name)}-${p.sku}.json`\n * console.log(filename)\n * // 't-shirt-blue-TS-001.json'\n * // 'shoes-accessories-SH-042.json'\n * })\n * ```\n *\n * @see {@link toUrlSlug} for more sophisticated URL slug generation with accent removal\n * @see {@link toKebabCase} for case conversion to kebab-case\n */\nexport const sanitizeString = (instr: string | null): string => {\n if (!instr) return ''\n return addDash(instr.replace(/[^a-zA-Z0-9 ]/g, '')).toLowerCase()\n}\n\nfunction addDash(str: string): string {\n const str2 = str.replace(/[`~!@#$%^&*()_|+\\-=?;:'\",.<>{}[\\]\\\\/]/gi, '')\n return str2.replace(/ /g, '-')\n}\n\n/**\n * Removes JSON-like characters and HTML entities from a string\n * @param texto Input string to clean\n * @returns String with JSON characters and HTML entities removed\n */\nexport const cleanJsonChars = (texto: string | null): string => {\n if (!texto) return ''\n return texto.replace(/({|}|&|amp;|gt;)/g, '')\n}\n\n/**\n * Truncates a string to a specified maximum length with optional suffix\n *\n * Shortens long strings for display in UI components, tables, previews, and tooltips.\n * Preserves readability while fitting space constraints.\n *\n * Behavior:\n * - If string length ≤ maxlength: returns original string unchanged\n * - If string length > maxlength: returns `str.substring(0, maxlength) + suffix`\n * - Suffix is included in addition to maxlength (not counted within limit)\n * - Null/empty strings return empty string\n *\n * @param str - String to truncate (null/empty returns '')\n * @param maxlength - Maximum length before truncation (default: 80 characters)\n * @param suffix - Suffix to append when truncated (default: '...')\n * @returns Truncated string with suffix if needed, original string if shorter\n *\n * @example\n * ```typescript\n * // Basic truncation (default 80 chars)\n * truncateString('Short text') // 'Short text' (no truncation)\n *\n * const long = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit'\n * truncateString(long, 20) // 'Lorem ipsum dolor si...' (20 + 3 = 23 chars total)\n *\n * // Custom max length\n * truncateString('Hello World', 5) // 'Hello...'\n * truncateString('Hello World', 11) // 'Hello World' (no truncation)\n *\n * // Custom suffix\n * truncateString('Long text here', 8, '…') // 'Long tex…'\n * truncateString('Long text here', 8, ' [more]') // 'Long tex [more]'\n * truncateString('Long text here', 8, '') // 'Long tex' (no suffix)\n *\n * // Edge cases\n * truncateString('', 10) // ''\n * truncateString(null as any, 10) // '' (graceful handling)\n * truncateString('Hi', 0) // '...' (edge case: 0 maxlength)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Truncate product descriptions in table\n * const products = [\n * { name: 'Laptop', description: 'High-performance laptop with 16GB RAM and 512GB SSD' },\n * { name: 'Mouse', description: 'Wireless ergonomic mouse' }\n * ]\n *\n * products.forEach(p => {\n * console.log(`${p.name}: ${truncateString(p.description, 30)}`)\n * })\n * // Output:\n * // Laptop: High-performance laptop with 1...\n * // Mouse: Wireless ergonomic mouse\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Truncate user comments for preview\n * function renderCommentPreview(comment: string): string {\n * return `<div class=\"preview\">${truncateString(comment, 100)}</div>`\n * }\n *\n * renderCommentPreview('This is a very long comment that needs to be shortened for display...')\n * // '<div class=\"preview\">This is a very long comment that needs to be shortened for display in the UI without taking too m...</div>'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Email subject line truncation\n * function formatEmailSubject(subject: string): string {\n * // Gmail displays ~60 chars on desktop\n * return truncateString(subject, 60, '…')\n * }\n *\n * formatEmailSubject('Re: Important meeting about Q4 planning and budget review')\n * // 'Re: Important meeting about Q4 planning and budget review' (fits)\n *\n * formatEmailSubject('Re: [URGENT] Critical system outage affecting all production servers and databases')\n * // 'Re: [URGENT] Critical system outage affecting all production…'\n * ```\n *\n * @see {@link isEmpty} for checking empty strings\n */\nexport const truncateString = (str: string, maxlength = 80, suffix = '...'): string => {\n if (!str) return ''\n return str.length > maxlength ? str.substring(0, maxlength) + suffix : str\n}\n\n/**\n * Converts Unicode escape sequences to their actual characters\n * @param input String containing Unicode escape sequences\n * @returns String with escape sequences converted to actual characters\n */\nexport const unescapeUnicode = (input: string): string => {\n return JSON.stringify(JSON.parse(`\"${input}\"`)).replace(/^\"(.*)\"$/, '$1')\n}\n\n/**\n * Ensures a string ends with a specific suffix, adding it if not present\n * @param str Input string to check\n * @param trailing Suffix string to ensure is present at the end\n * @returns String guaranteed to end with the trailing string\n */\nexport const ensureEndsWith = (str: string, trailing: string): string => {\n return str.endsWith(trailing) ? str : `${str}${trailing}`\n}\n\n/**\n * Removes a specific suffix from the end of a string if present\n * @param str Input string to process\n * @param trailing Suffix string to remove from the end\n * @returns String with trailing suffix removed, original if suffix not found\n */\nexport const stripFromEnd = (str: string, trailing: string): string => {\n return str.endsWith(trailing) ? str.slice(0, -1 * trailing.length) : str\n}\n\n/**\n * Ensures a string starts with a specific prefix, adding it if not present\n * @param str Input string to check\n * @param leading Prefix string to ensure is present at the start\n * @returns String guaranteed to start with the leading string\n */\nexport const ensureStartsWith = (str: string, leading: string): string => {\n return str.startsWith(leading) ? str : `${leading}${str}`\n}\n\n/**\n * Removes a specific prefix from the start of a string if present\n * @param str Input string to process\n * @param leading Prefix string to remove from the start\n * @returns String with leading prefix removed, original if prefix not found\n */\nexport const stripFromStart = (str: string, leading: string): string => {\n return str.startsWith(leading) ? str.slice(leading.length) : str\n}\n\n/**\n * Converts all characters in a string to lowercase\n * @param str Input string to convert\n * @returns String with all characters in lowercase\n */\nexport const toLowerCase = (str: string): string => {\n return str.toLowerCase()\n}\n\n/**\n * Converts all characters in a string to uppercase\n * @param str Input string to convert\n * @returns String with all characters in uppercase\n */\nexport const toUpperCase = (str: string): string => {\n return str.toUpperCase()\n}\n\n/**\n * Capitalizes the first letter of a string and lowercases the rest\n * @param str Input string to capitalize\n * @returns String with first letter capitalized and rest in lowercase\n */\nexport const capitalizeFirst = (str: string): string => {\n if (!str) return ''\n return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()\n}\n\n/**\n * Capitalizes the first letter of each word in a string\n * @param str Input string with words separated by spaces\n * @returns String with each word's first letter capitalized\n */\nexport const capitalizeEachWord = (str: string): string => {\n if (!str) return ''\n return str\n .split(' ')\n .map(word => capitalizeFirst(word))\n .join(' ')\n}\n\n/**\n * Converts a string to camelCase format\n *\n * Transforms strings from any case convention (kebab-case, snake_case, PascalCase, spaces)\n * to camelCase following JavaScript/TypeScript naming conventions.\n *\n * Algorithm:\n * 1. Detect word boundaries (spaces, hyphens, underscores, case transitions)\n * 2. Split into words and normalize to lowercase\n * 3. First word: keep lowercase\n * 4. Remaining words: capitalize first letter\n * 5. Join without separators\n *\n * Format: `firstWordSecondWordThirdWord`\n *\n * @param str - Input string in any case format (empty returns '')\n * @returns String in camelCase format\n *\n * @example\n * ```typescript\n * // From kebab-case\n * toCamelCase('hello-world') // 'helloWorld'\n * toCamelCase('user-profile-page') // 'userProfilePage'\n *\n * // From snake_case\n * toCamelCase('hello_world') // 'helloWorld'\n * toCamelCase('user_first_name') // 'userFirstName'\n *\n * // From PascalCase\n * toCamelCase('HelloWorld') // 'helloWorld'\n * toCamelCase('UserProfile') // 'userProfile'\n *\n * // From space-separated\n * toCamelCase('hello world') // 'helloWorld'\n * toCamelCase('First Name') // 'firstName'\n *\n * // Mixed formats\n * toCamelCase('hello-World_test') // 'helloWorldTest'\n * toCamelCase('API_Response-data') // 'apiResponseData'\n *\n * // Edge cases\n * toCamelCase('') // ''\n * toCamelCase('a') // 'a'\n * toCamelCase('UPPERCASE') // 'uppercase'\n * toCamelCase('123-test') // '123Test'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Convert database column names to JS properties\n * const dbColumns = ['user_id', 'first_name', 'last_name', 'created_at']\n *\n * const jsProperties = dbColumns.map(toCamelCase)\n * console.log(jsProperties)\n * // ['userId', 'firstName', 'lastName', 'createdAt']\n *\n * // Transform database row to JS object\n * function transformRow(row: Record<string, any>): Record<string, any> {\n * return Object.fromEntries(\n * Object.entries(row).map(([key, value]) => [toCamelCase(key), value])\n * )\n * }\n *\n * transformRow({ user_id: 123, first_name: 'Alice' })\n * // { userId: 123, firstName: 'Alice' }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Convert CSS property names to JS style properties\n * const cssProperties = [\n * 'background-color',\n * 'font-size',\n * 'margin-top',\n * 'border-bottom-width'\n * ]\n *\n * cssProperties.forEach(prop => {\n * const jsProp = toCamelCase(prop)\n * console.log(`${prop} → ${jsProp}`)\n * })\n * // background-color → backgroundColor\n * // font-size → fontSize\n * // margin-top → marginTop\n * // border-bottom-width → borderBottomWidth\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: API response transformation\n * interface ApiUser {\n * user_id: number\n * first_name: string\n * last_name: string\n * created_at: string\n * }\n *\n * function transformApiResponse(apiData: ApiUser) {\n * return {\n * userId: apiData.user_id,\n * firstName: apiData.first_name,\n * lastName: apiData.last_name,\n * createdAt: new Date(apiData.created_at)\n * }\n * }\n *\n * // Or generically:\n * function autoTransformKeys<T extends Record<string, any>>(obj: T) {\n * return Object.fromEntries(\n * Object.entries(obj).map(([k, v]) => [toCamelCase(k), v])\n * )\n * }\n * ```\n *\n * @see {@link toPascalCase} for PascalCase conversion (first letter uppercase)\n * @see {@link toSnakeCase} for snake_case conversion\n * @see {@link toKebabCase} for kebab-case conversion\n */\nexport const toCamelCase = (str: string): string => {\n if (!str) return ''\n\n // Split by word boundaries (spaces, hyphens, underscores, camelCase boundaries)\n const words = str\n .replace(/([a-z])([A-Z])/g, '$1 $2') // Split camelCase\n .split(/[-_\\s]+/) // Split by separators\n .filter(word => word.length > 0)\n .map(word => word.toLowerCase())\n\n if (words.length === 0) return ''\n\n // First word lowercase, rest capitalize first letter\n return (\n words[0] +\n words\n .slice(1)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n .join('')\n )\n}\n\n/**\n * Converts a string to snake_case format\n *\n * Transforms strings from any case convention to snake_case, commonly used in\n * Python, Ruby, database column names, and environment variables.\n *\n * Algorithm:\n * 1. Replace non-word characters with spaces\n * 2. Split on spaces and camelCase boundaries\n * 3. Convert all words to lowercase\n * 4. Join with underscores\n *\n * Format: `first_word_second_word_third_word`\n *\n * @param str - Input string in any case format (empty returns '')\n * @returns String in snake_case format\n *\n * @example\n * ```typescript\n * // From camelCase\n * toSnakeCase('helloWorld') // 'hello_world'\n * toSnakeCase('userFirstName') // 'user_first_name'\n *\n * // From PascalCase\n * toSnakeCase('HelloWorld') // 'hello_world'\n * toSnakeCase('UserProfile') // 'user_profile'\n *\n * // From kebab-case\n * toSnakeCase('hello-world') // 'hello_world'\n * toSnakeCase('user-profile-page') // 'user_profile_page'\n *\n * // From space-separated\n * toSnakeCase('hello world') // 'hello_world'\n * toSnakeCase('First Name') // 'first_name'\n *\n * // Mixed formats\n * toSnakeCase('helloWorld-test') // 'hello_world_test'\n * toSnakeCase('APIResponse') // 'a_p_i_response'\n *\n * // Edge cases\n * toSnakeCase('') // ''\n * toSnakeCase('a') // 'a'\n * toSnakeCase('UPPERCASE') // 'u_p_p_e_r_c_a_s_e'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Convert JS properties to database column names\n * const jsObject = {\n * userId: 123,\n * firstName: 'Alice',\n * lastName: 'Smith',\n * createdAt: new Date()\n * }\n *\n * const dbColumns = Object.keys(jsObject).map(toSnakeCase)\n * console.log(dbColumns)\n * // ['user_id', 'first_name', 'last_name', 'created_at']\n *\n * // Generate SQL INSERT statement\n * const columns = Object.keys(jsObject).map(toSnakeCase).join(', ')\n * const sql = `INSERT INTO users (${columns}) VALUES (?)`\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Environment variable generation\n * const configKeys = ['databaseHost', 'databasePort', 'apiBaseUrl']\n *\n * configKeys.forEach(key => {\n * const envVar = toSnakeCase(key).toUpperCase()\n * console.log(`${envVar}=value`)\n * })\n * // DATABASE_HOST=value\n * // DATABASE_PORT=value\n * // API_BASE_URL=value\n * ```\n *\n * @see {@link toCamelCase} for camelCase conversion\n * @see {@link toKebabCase} for kebab-case conversion\n * @see {@link toPascalCase} for PascalCase conversion\n */\nexport const toSnakeCase = (str: string): string => {\n if (!str) return ''\n return str\n .replace(/\\W+/g, ' ')\n .split(/ |\\B(?=[A-Z])/)\n .map(word => word.toLowerCase())\n .join('_')\n}\n\n/**\n * Converts a string to kebab-case format\n *\n * Transforms strings from any case convention to kebab-case (also called dash-case or hyphen-case).\n * Widely used in URLs, HTML attributes, CSS classes, and file names.\n *\n * Algorithm:\n * 1. Replace underscores and spaces with hyphens\n * 2. Insert hyphens before uppercase letters (handle camelCase)\n * 3. Convert all to lowercase\n * 4. Remove non-alphanumeric characters (except hyphens)\n * 5. Collapse multiple consecutive hyphens\n * 6. Remove leading/trailing hyphens\n *\n * Format: `first-word-second-word-third-word`\n *\n * @param str - Input string in any case format (empty returns '')\n * @returns String in kebab-case format\n *\n * @example\n * ```typescript\n * // From camelCase\n * toKebabCase('helloWorld') // 'hello-world'\n * toKebabCase('userFirstName') // 'user-first-name'\n *\n * // From PascalCase\n * toKebabCase('HelloWorld') // 'hello-world'\n * toKebabCase('UserProfile') // 'user-profile'\n *\n * // From snake_case\n * toKebabCase('hello_world') // 'hello-world'\n * toKebabCase('user_first_name') // 'user-first-name'\n *\n * // From space-separated\n * toKebabCase('hello world') // 'hello-world'\n * toKebabCase('First Name') // 'first-name'\n *\n * // Mixed formats\n * toKebabCase('helloWorld_test') // 'hello-world-test'\n * toKebabCase('API-Response') // 'api-response'\n *\n * // Edge cases\n * toKebabCase('') // ''\n * toKebabCase('a') // 'a'\n * toKebabCase('UPPERCASE') // 'uppercase'\n * toKebabCase('123Test') // '123-test'\n * toKebabCase('--multiple--dashes--') // 'multiple-dashes'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Generate URL slugs from page titles\n * const pageTitle = 'My Awesome Blog Post'\n * const urlSlug = toKebabCase(pageTitle)\n * const url = `https://example.com/blog/${urlSlug}`\n * // https://example.com/blog/my-awesome-blog-post\n *\n * // Multiple pages\n * const pages = [\n * { title: 'Getting Started', component: 'GettingStarted' },\n * { title: 'API Reference', component: 'ApiReference' },\n * { title: 'Best Practices', component: 'BestPractices' }\n * ]\n *\n * const routes = pages.map(p => ({\n * path: `/${toKebabCase(p.title)}`,\n * component: p.component\n * }))\n * // [\n * // { path: '/getting-started', component: 'GettingStarted' },\n * // { path: '/api-reference', component: 'ApiReference' },\n * // { path: '/best-practices', component: 'BestPractices' }\n * // ]\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: CSS class name generation\n * function generateClassName(componentName: string, modifier?: string): string {\n * const base = `component-${toKebabCase(componentName)}`\n * return modifier ? `${base}--${toKebabCase(modifier)}` : base\n * }\n *\n * generateClassName('UserProfile') // 'component-user-profile'\n * generateClassName('UserProfile', 'isActive') // 'component-user-profile--is-active'\n * generateClassName('ButtonPrimary', 'largeSize') // 'component-button-primary--large-size'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: HTML attribute generation\n * function generateDataAttribute(key: string, value: string): string {\n * return `data-${toKebabCase(key)}=\"${value}\"`\n * }\n *\n * generateDataAttribute('userId', '123') // 'data-user-id=\"123\"'\n * generateDataAttribute('testEnvironment', 'staging') // 'data-test-environment=\"staging\"'\n * ```\n *\n * @see {@link toCamelCase} for camelCase conversion\n * @see {@link toSnakeCase} for snake_case conversion\n * @see {@link toPascalCase} for PascalCase conversion\n * @see {@link toUrlSlug} for URL-safe slug generation with accent removal\n */\nexport const toKebabCase = (str: string): string => {\n if (!str) return ''\n return str\n .replace(/[_\\s]+/g, '-')\n .replace(/([a-z0-9])([A-Z])/g, '$1-$2') // Handle numbers before capitals too\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n}\n\n/**\n * Converts a string to PascalCase format\n *\n * Transforms strings from any case convention to PascalCase (also called UpperCamelCase).\n * Commonly used for class names, component names, type names, and constructor functions\n * in JavaScript/TypeScript.\n *\n * Algorithm:\n * 1. Split on word boundaries (hyphens, underscores, spaces)\n * 2. Capitalize first letter of each word\n * 3. Join without separators\n * 4. Ensure first character is uppercase\n *\n * Format: `FirstWordSecondWordThirdWord`\n *\n * @param str - Input string in any case format (empty returns '')\n * @returns String in PascalCase format\n *\n * @example\n * ```typescript\n * // From camelCase\n * toPascalCase('helloWorld') // 'HelloWorld'\n * toPascalCase('userFirstName') // 'UserFirstName'\n *\n * // From kebab-case\n * toPascalCase('hello-world') // 'HelloWorld'\n * toPascalCase('user-profile-page') // 'UserProfilePage'\n *\n * // From snake_case\n * toPascalCase('hello_world') // 'HelloWorld'\n * toPascalCase('user_first_name') // 'UserFirstName'\n *\n * // From space-separated\n * toPascalCase('hello world') // 'HelloWorld'\n * toPascalCase('first name') // 'FirstName'\n *\n * // Mixed formats\n * toPascalCase('hello-World_test') // 'HelloWorldTest'\n * toPascalCase('api_response-data') // 'ApiResponseData'\n *\n * // Edge cases\n * toPascalCase('') // ''\n * toPascalCase('a') // 'A'\n * toPascalCase('UPPERCASE') // 'UPPERCASE'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Generate React component names\n * const componentNames = ['user-profile', 'navigation-bar', 'footer-links']\n *\n * componentNames.forEach(name => {\n * const pascalName = toPascalCase(name)\n * console.log(`export function ${pascalName}() { ... }`)\n * })\n * // export function UserProfile() { ... }\n * // export function NavigationBar() { ... }\n * // export function FooterLinks() { ... }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: TypeScript type name generation\n * interface GenerateTypeOptions {\n * name: string\n * fields: Array<{ name: string; type: string }>\n * }\n *\n * function generateTypeDefinition(options: GenerateTypeOptions): string {\n * const typeName = toPascalCase(options.name)\n * const fields = options.fields\n * .map(f => ` ${f.name}: ${f.type}`)\n * .join('\\n')\n *\n * return `interface ${typeName} {\\n${fields}\\n}`\n * }\n *\n * generateTypeDefinition({\n * name: 'user-profile',\n * fields: [\n * { name: 'userId', type: 'number' },\n * { name: 'name', type: 'string' }\n * ]\n * })\n * // interface UserProfile {\n * // userId: number\n * // name: string\n * // }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Class name generation for dynamic imports\n * async function loadService(serviceName: string) {\n * const className = toPascalCase(serviceName)\n * const module = await import(`./services/${serviceName}`)\n * return new module[className]()\n * }\n *\n * await loadService('user-service') // new UserService()\n * await loadService('payment-gateway') // new PaymentGateway()\n * ```\n *\n * @see {@link toCamelCase} for camelCase conversion (first letter lowercase)\n * @see {@link toSnakeCase} for snake_case conversion\n * @see {@link toKebabCase} for kebab-case conversion\n */\nexport const toPascalCase = (str: string): string => {\n if (!str) return ''\n return str\n .replace(/[-_\\s]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))\n .replace(/^./, char => char.toUpperCase())\n}\n\n/**\n * Checks if a string contains another string with optional case sensitivity\n * @param str String to search within\n * @param searchStr Substring to search for\n * @param caseSensitive Whether to perform case-sensitive search (default: false)\n * @returns True if the string contains the search string, false otherwise\n */\nexport const contains = (str: string, searchStr: string, caseSensitive = false): boolean => {\n if (caseSensitive) {\n return str.includes(searchStr)\n }\n return str.toLowerCase().includes(searchStr.toLowerCase())\n}\n\n/**\n * Checks if a string starts with another string with optional case sensitivity\n * @param str String to check\n * @param searchStr Prefix to search for\n * @param caseSensitive Whether to perform case-sensitive search (default: false)\n * @returns True if the string starts with the search string, false otherwise\n */\nexport const startsWith = (str: string, searchStr: string, caseSensitive = false): boolean => {\n if (caseSensitive) {\n return str.startsWith(searchStr)\n }\n return str.toLowerCase().startsWith(searchStr.toLowerCase())\n}\n\n/**\n * Checks if a string ends with another string with optional case sensitivity\n * @param str String to check\n * @param searchStr Suffix to search for\n * @param caseSensitive Whether to perform case-sensitive search (default: false)\n * @returns True if the string ends with the search string, false otherwise\n */\nexport const endsWith = (str: string, searchStr: string, caseSensitive = false): boolean => {\n if (caseSensitive) {\n return str.endsWith(searchStr)\n }\n return str.toLowerCase().endsWith(searchStr.toLowerCase())\n}\n\n/**\n * Pads a string at the start (left) with a specific character to reach target length\n * @param str String to pad\n * @param length Target length for the resulting string\n * @param padChar Character to use for padding (default: space)\n * @returns String padded to target length, original if already longer\n */\nexport const padStart = (str: string, length: number, padChar = ' '): string => {\n return str.padStart(length, padChar)\n}\n\n/**\n * Pads a string at the end (right) with a specific character to reach target length\n * @param str String to pad\n * @param length Target length for the resulting string\n * @param padChar Character to use for padding (default: space)\n * @returns String padded to target length, original if already longer\n */\nexport const padEnd = (str: string, length: number, padChar = ' '): string => {\n if (str.length >= length) return str\n return str + padChar.repeat(length - str.length)\n}\n\n/**\n * Removes leading and trailing whitespace from a string\n * @param str String to trim\n * @returns String with leading and trailing whitespace removed\n */\nexport const trim = (str: string): string => {\n return str.trim()\n}\n\n/**\n * Removes leading (start) whitespace from a string\n * @param str String to trim\n * @returns String with leading whitespace removed\n */\nexport const trimStart = (str: string): string => {\n return str.trimStart()\n}\n\n/**\n * Removes trailing (end) whitespace from a string\n * @param str String to trim\n * @returns String with trailing whitespace removed\n */\nexport const trimEnd = (str: string): string => {\n return str.trimEnd()\n}\n\n/**\n * Reverses the character order in a string\n * @param str String to reverse\n * @returns String with characters in reverse order\n */\nexport const reverseString = (str: string): string => {\n return str.split('').reverse().join('')\n}\n\n/**\n * Repeats a string a specified number of times\n * @param str String to repeat\n * @param times Number of times to repeat the string\n * @returns String repeated the specified number of times\n */\nexport const repeatString = (str: string, times: number): string => {\n return str.repeat(times)\n}\n\n/**\n * Replaces all occurrences of a substring with a replacement string\n * @param str String to search in\n * @param searchStr Substring to find and replace\n * @param replaceStr String to replace each occurrence with\n * @returns String with all occurrences replaced\n */\nexport const replaceAllOccurrences = (\n str: string,\n searchStr: string,\n replaceStr: string\n): string => {\n return str.split(searchStr).join(replaceStr)\n}\n\n/**\n * Counts the number of occurrences of a substring within a string\n * @param str String to search within\n * @param searchStr Substring to count occurrences of\n * @returns Number of times the substring appears in the string\n */\nexport const countOccurrences = (str: string, searchStr: string): number => {\n if (!searchStr) return 0\n let count = 0\n let position = 0\n\n while ((position = str.indexOf(searchStr, position)) !== -1) {\n count++\n position += 1 // Move only 1 position to allow overlapping matches\n }\n\n return count\n}\n\n/**\n * Checks if a string is empty, null, undefined, or contains only whitespace\n * @param str String to check (can be null or undefined)\n * @returns True if string is empty/null/undefined or only whitespace, false otherwise\n */\nexport const isEmpty = (str: string | null | undefined): boolean => {\n return !str || str.trim().length === 0\n}\n\n/**\n * Validates if a string is a properly formatted email address\n * @param str String to validate as email\n * @returns True if string matches email format, false otherwise\n */\nexport const isEmail = (str: string): boolean => {\n const re =\n /^(([^<>()[\\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/\n return re.test(str.toLowerCase())\n}\n\n/**\n * Generates a URL-friendly slug from a string\n *\n * Creates clean, SEO-friendly URL slugs by normalizing strings for use in web addresses.\n * Removes special characters, normalizes whitespace, and converts to lowercase with hyphens.\n *\n * Algorithm:\n * 1. Convert to lowercase\n * 2. Trim leading/trailing whitespace\n * 3. Remove non-word characters (except spaces, hyphens, underscores)\n * 4. Replace spaces/underscores/multiple-hyphens with single hyphen\n * 5. Remove leading/trailing hyphens\n *\n * Format: `url-friendly-slug-text`\n *\n * ⚠️ NOTE: Does NOT remove accents. For accent removal, use {@link removeAccents} first.\n *\n * @param str - String to convert to URL slug (special chars removed)\n * @returns URL-friendly slug string in lowercase with hyphens\n *\n * @example\n * ```typescript\n * // Basic slug generation\n * toUrlSlug('Hello World') // 'hello-world'\n * toUrlSlug('My Blog Post Title') // 'my-blog-post-title'\n *\n * // Remove special characters\n * toUrlSlug('User: John Doe!') // 'user-john-doe'\n * toUrlSlug('Price: $19.99') // 'price-1999'\n * toUrlSlug('Hello @ World #2024') // 'hello-world-2024'\n *\n * // Normalize whitespace and separators\n * toUrlSlug('hello world') // 'hello-world'\n * toUrlSlug('hello_world_test') // 'hello-world-test'\n * toUrlSlug('---multiple---dashes---') // 'multiple-dashes'\n *\n * // Preserve numbers and hyphens\n * toUrlSlug('Article 123') // 'article-123'\n * toUrlSlug('ES6-Features') // 'es6-features'\n *\n * // Edge cases\n * toUrlSlug('') // ''\n * toUrlSlug(' ') // ''\n * toUrlSlug('123') // '123'\n * toUrlSlug('a-b-c') // 'a-b-c'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Generate blog post URL from title\n * function createBlogPostUrl(title: string, id: number): string {\n * const slug = toUrlSlug(title)\n * return `/blog/${id}/${slug}`\n * }\n *\n * createBlogPostUrl('10 Tips for TypeScript', 42)\n * // '/blog/42/10-tips-for-typescript'\n *\n * createBlogPostUrl('Getting Started with React!', 1)\n * // '/blog/1/getting-started-with-react'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Generate product URLs for e-commerce\n * interface Product {\n * id: string\n * name: string\n * category: string\n * }\n *\n * function getProductUrl(product: Product): string {\n * const categorySlug = toUrlSlug(product.category)\n * const nameSlug = toUrlSlug(product.name)\n * return `/products/${categorySlug}/${product.id}/${nameSlug}`\n * }\n *\n * getProductUrl({\n * id: 'SKU-123',\n * name: 'Wireless Mouse (Ergonomic)',\n * category: 'Computer Accessories'\n * })\n * // '/products/computer-accessories/SKU-123/wireless-mouse-ergonomic'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: SEO-friendly URLs with accent handling\n * function createSeoUrl(title: string): string {\n * // Remove accents BEFORE slug generation for better URL compatibility\n * return toUrlSlug(removeAccents(title))\n * }\n *\n * createSeoUrl('Café Münchën 2024')\n * // 'cafe-munchen-2024' (accents removed)\n *\n * // Without accent removal:\n * toUrlSlug('Café Münchën 2024')\n * // 'caf-mnchen-2024' (accents become invalid chars, get removed)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Generate unique slugs for duplicate titles\n * const existingSlugs = new Set(['hello-world', 'hello-world-1'])\n *\n * function generateUniqueSlug(title: string): string {\n * let slug = toUrlSlug(title)\n * let counter = 1\n *\n * while (existingSlugs.has(slug)) {\n * slug = `${toUrlSlug(title)}-${counter}`\n * counter++\n * }\n *\n * existingSlugs.add(slug)\n * return slug\n * }\n *\n * generateUniqueSlug('Hello World') // 'hello-world-2' (avoiding existing)\n * generateUniqueSlug('New Article') // 'new-article'\n * ```\n *\n * @see {@link toKebabCase} for case conversion to kebab-case\n * @see {@link removeAccents} for removing accents before slug generation\n * @see {@link sanitizeString} for basic string sanitization\n */\nexport const toUrlSlug = (str: string): string => {\n return str\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_-]+/g, '-')\n .replace(/^-+|-+$/g, '')\n}\n\n/**\n * Removes accents and diacritical marks from characters in a string\n *\n * Uses Unicode normalization (NFD) to decompose accented characters, then removes\n * combining diacritical marks. Essential for search, sorting, URL slugs, and ASCII compatibility.\n *\n * Algorithm:\n * 1. Normalize string to NFD (Normalization Form Decomposed)\n * 2. Remove Unicode combining diacritical marks (U+0300–U+036F)\n * 3. Return ASCII-compatible base characters\n *\n * Supported diacritics: acute (´), grave (`), circumflex (^), tilde (~), umlaut (¨),\n * cedilla (¸), and many more.\n *\n * @param str - String containing accented characters (e.g., 'café', 'Münchën')\n * @returns String with accents removed to base ASCII characters (e.g., 'cafe', 'Munchen')\n *\n * @example\n * ```typescript\n * // Basic accent removal - Romance languages\n * removeAccents('café') // 'cafe'\n * removeAccents('naïve') // 'naive'\n * removeAccents('résumé') // 'resume'\n * removeAccents('à côté') // 'a cote'\n *\n * // Spanish accents\n * removeAccents('Español') // 'Espanol'\n * removeAccents('niño') // 'nino'\n * removeAccents('José María') // 'Jose Maria'\n *\n * // German umlauts\n * removeAccents('Münchën') // 'Munchen'\n * removeAccents('Köln') // 'Koln'\n * removeAccents('Zürich') // 'Zurich'\n *\n * // Portuguese\n * removeAccents('São Paulo') // 'Sao Paulo'\n * removeAccents('Brasília') // 'Brasilia'\n *\n * // French\n * removeAccents('Côte d'Ivoire') // 'Cote d'Ivoire'\n * removeAccents('Françoise') // 'Francoise'\n *\n * // Mixed\n * removeAccents('Crème brûlée') // 'Creme brulee'\n * removeAccents('Åre, Malmö') // 'Are, Malmo'\n *\n * // Edge cases\n * removeAccents('') // ''\n * removeAccents('ASCII text') // 'ASCII text' (unchanged)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Search normalization (case + accent insensitive)\n * function normalizeForSearch(text: string): string {\n * return removeAccents(text.toLowerCase().trim())\n * }\n *\n * const searchQuery = normalizeForSearch('Café') // 'cafe'\n * const productName = normalizeForSearch('CAFÉ PREMIUM') // 'cafe premium'\n *\n * if (productName.includes(searchQuery)) {\n * console.log('Match found!')\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Generate SEO-friendly URLs\n * function createSeoUrl(title: string): string {\n * return toUrlSlug(removeAccents(title))\n * }\n *\n * createSeoUrl('Guía de Español')\n * // 'guia-de-espanol'\n *\n * createSeoUrl('Café Münchën 2024')\n * // 'cafe-munchen-2024'\n *\n * createSeoUrl('São Paulo: Best Restaurants')\n * // 'sao-paulo-best-restaurants'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Sort names alphabetically (accent-insensitive)\n * const names = ['Álvarez', 'Andersen', 'Ängel', 'Adams']\n *\n * const sorted = names.sort((a, b) =>\n * removeAccents(a).localeCompare(removeAccents(b))\n * )\n * // ['Adams', 'Álvarez', 'Andersen', 'Ängel']\n * // (sorted by: Adams, Alvarez, Andersen, Angel)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Filename sanitization\n * function sanitizeFilename(filename: string): string {\n * // Remove accents, then sanitize\n * const withoutAccents = removeAccents(filename)\n * return sanitizeString(withoutAccents)\n * }\n *\n * sanitizeFilename('Presentación_2024.pdf')\n * // 'presentacion-2024pdf'\n *\n * sanitizeFilename('Föräldrar & Barn.docx')\n * // 'foraldrar-barn-docx'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Email address generation\n * function generateEmail(firstName: string, lastName: string): string {\n * const first = removeAccents(firstName.toLowerCase())\n * const last = removeAccents(lastName.toLowerCase())\n * return `${first}.${last}@company.com`\n * }\n *\n * generateEmail('José', 'García')\n * // 'jose.garcia@company.com'\n *\n * generateEmail('François', 'Müller')\n * // 'francois.muller@company.com'\n * ```\n *\n * @see {@link toUrlSlug} for URL slug generation (combine with removeAccents for best results)\n * @see {@link sanitizeString} for string sanitization\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize String.normalize() Documentation}\n */\nexport const removeAccents = (str: string): string => {\n return str.normalize('NFD').replace(/[\\u0300-\\u036f]/g, '')\n}\n\n/**\n * Escapes HTML special characters to prevent XSS and display issues\n *\n * Converts dangerous HTML characters to their HTML entity equivalents, preventing\n * script injection (XSS) and ensuring proper text display in HTML contexts.\n *\n * Escaped characters:\n * - `&` → `&` (must be first to avoid double-escaping)\n * - `<` → `<` (prevents opening tags)\n * - `>` → `>` (prevents closing tags)\n * - `\"` → `"` (prevents attribute injection)\n * - `'` → `'` (prevents attribute injection)\n *\n * ⚠️ SECURITY WARNING: This provides basic XSS protection but is NOT a complete solution.\n * For production HTML sanitization, use DOMPurify or similar dedicated libraries.\n * This function is safe for:\n * - Displaying user input as plain text in HTML\n * - Escaping attribute values in HTML\n * - Simple content rendering\n *\n * NOT sufficient for:\n * - Rich HTML content (use DOMPurify)\n * - JavaScript context (use different escaping)\n * - URL context (use encodeURIComponent)\n * - CSS context (use CSS-specific escaping)\n *\n * @param str - String containing HTML characters to escape\n * @returns String with HTML characters safely escaped as HTML entities\n *\n * @example\n * ```typescript\n * // Basic escaping\n * escapeHtmlChars('<script>alert(\"XSS\")</script>')\n * // '<script>alert("XSS")</script>'\n *\n * escapeHtmlChars('5 < 10 & 10 > 5')\n * // '5 < 10 & 10 > 5'\n *\n * escapeHtmlChars('Say \"Hello\" & \\'Goodbye\\'')\n * // 'Say "Hello" & 'Goodbye''\n *\n * // Edge cases\n * escapeHtmlChars('') // ''\n * escapeHtmlChars('No special chars') // 'No special chars'\n * escapeHtmlChars('<') // '&lt;' (escapes already-escaped)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Safely display user comments in HTML\n * interface Comment {\n * author: string\n * content: string\n * }\n *\n * function renderComment(comment: Comment): string {\n * const safeAuthor = escapeHtmlChars(comment.author)\n * const safeContent = escapeHtmlChars(comment.content)\n *\n * return `\n * <div class=\"comment\">\n * <strong>${safeAuthor}</strong>\n * <p>${safeContent}</p>\n * </div>\n * `\n * }\n *\n * renderComment({\n * author: '<script>alert(\"XSS\")</script>',\n * content: 'Great post! <3'\n * })\n * // Safe HTML output with escaped tags\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Generate safe HTML attributes\n * function generateDataAttribute(key: string, value: string): string {\n * const safeKey = toKebabCase(key)\n * const safeValue = escapeHtmlChars(value)\n * return `data-${safeKey}=\"${safeValue}\"`\n * }\n *\n * generateDataAttribute('userInput', '<script>alert(1)</script>')\n * // 'data-user-input=\"<script>alert(1)</script>\"'\n *\n * generateDataAttribute('description', 'Product \"Premium\" & more')\n * // 'data-description=\"Product "Premium" & more\"'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Escape user search query for display\n * function displaySearchResults(query: string, results: any[]): string {\n * const safeQuery = escapeHtmlChars(query)\n *\n * return `\n * <div class=\"search-results\">\n * <h2>Results for: ${safeQuery}</h2>\n * <p>${results.length} results found</p>\n * </div>\n * `\n * }\n *\n * displaySearchResults('<img src=x onerror=alert(1)>', [])\n * // Safe display: \"Results for: <img src=x onerror=alert(1)>\"\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: CSV to HTML table with safe content\n * function csvRowToHtmlRow(cells: string[]): string {\n * const safeCells = cells.map(cell => escapeHtmlChars(cell))\n * const tdElements = safeCells.map(cell => `<td>${cell}</td>`).join('')\n * return `<tr>${tdElements}</tr>`\n * }\n *\n * csvRowToHtmlRow(['Alice', '<script>evil</script>', '25'])\n * // '<tr><td>Alice</td><td><script>evil</script></td><td>25</td></tr>'\n * ```\n *\n * @example\n * ```typescript\n * // Edge case: Preventing double-escaping\n * const userInput = 'Hello & Goodbye'\n * const escaped = escapeHtmlChars(userInput)\n * // 'Hello & Goodbye'\n *\n * const doubleEscaped = escapeHtmlChars(escaped)\n * // 'Hello &amp; Goodbye' ⚠️ Over-escaped!\n *\n * // Solution: Only escape once, track escaped state\n * interface SafeString {\n * value: string\n * isEscaped: boolean\n * }\n *\n * function safeEscape(str: string | SafeString): SafeString {\n * if (typeof str === 'object' && str.isEscaped) {\n * return str\n * }\n * const value = typeof str === 'string' ? str : str.value\n * return { value: escapeHtmlChars(value), isEscaped: true }\n * }\n * ```\n *\n * @see {@link unescapeHtmlChars} for reversing HTML entity escaping\n * @see {@link sanitizeString} for basic string sanitization\n * @see {@link https://owasp.org/www-community/attacks/xss/ OWASP XSS Prevention Cheat Sheet}\n * @see {@link https://github.com/cure53/DOMPurify DOMPurify for production HTML sanitization}\n */\nexport const escapeHtmlChars = (str: string): string => {\n const htmlEscapes: { [key: string]: string } = {\n '&': '&',\n '<': '<',\n '>': '>',\n '\"': '"',\n \"'\": ''',\n }\n return str.replace(/[&<>\"']/g, match => htmlEscapes[match])\n}\n\n/**\n * Converts HTML entities back to their original characters\n * @param str String containing HTML entities to unescape\n * @returns String with HTML entities converted back to original characters\n */\nexport const unescapeHtmlChars = (str: string): string => {\n const htmlUnescapes: { [key: string]: string } = {\n '&': '&',\n '<': '<',\n '>': '>',\n '"': '\"',\n ''': \"'\",\n }\n return str.replace(/&|<|>|"|'/g, match => htmlUnescapes[match])\n}\n\n/**\n * Checks if a path matches a wildcard pattern using dot notation\n *\n * Supports wildcards (*) to match any segment at that position.\n * Useful for configuration paths, routing, permissions, and feature flags.\n *\n * @param path - Dot-notation path to test (e.g., 'features.auth.enabled')\n * @param pattern - Pattern with optional wildcards (e.g., 'features.*', 'features.*.enabled')\n * @returns True if path matches pattern, false otherwise\n *\n * @example\n * ```typescript\n * // Exact match\n * matchPathPattern('features.auth', 'features.auth') // true\n *\n * // Wildcard at end\n * matchPathPattern('features.auth', 'features.*') // true\n * matchPathPattern('features.payments', 'features.*') // true\n * matchPathPattern('other.value', 'features.*') // false\n *\n * // Wildcard in middle\n * matchPathPattern('features.auth.enabled', 'features.*.enabled') // true\n * matchPathPattern('features.payments.enabled', 'features.*.enabled') // true\n * matchPathPattern('features.auth.disabled', 'features.*.enabled') // false\n *\n * // Multiple wildcards\n * matchPathPattern('app.features.auth.oauth', 'app.*.*.oauth') // true\n * matchPathPattern('app.features.auth.saml', 'app.*.*.oauth') // false\n *\n * // No wildcard\n * matchPathPattern('exact.path.match', 'exact.path.match') // true\n * matchPathPattern('exact.path.nomatch', 'exact.path.match') // false\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Feature flag matching\n * const enabledFeatures = [\n * 'features.auth.oauth',\n * 'features.auth.saml',\n * 'features.payments.stripe',\n * ]\n *\n * const hasAuth = enabledFeatures.some(f =>\n * matchPathPattern(f, 'features.auth.*')\n * ) // true\n *\n * const hasPayments = enabledFeatures.some(f =>\n * matchPathPattern(f, 'features.payments.*')\n * ) // true\n *\n * // Permission matching\n * const userPermissions = ['admin.users.read', 'admin.users.write']\n * const canManageUsers = userPermissions.some(p =>\n * matchPathPattern(p, 'admin.users.*')\n * ) // true\n * ```\n */\nexport function matchPathPattern(path: string, pattern: string): boolean {\n if (!path || typeof path !== 'string' || !pattern || typeof pattern !== 'string') {\n return false\n }\n\n // Exact match\n if (path === pattern) {\n return true\n }\n\n // No wildcard in pattern\n if (!pattern.includes('*')) {\n return path === pattern\n }\n\n // Convert pattern to regex\n // Escape special regex characters except *\n const regexPattern = pattern\n .split('.')\n .map(segment => {\n if (segment === '*') {\n return '[^.]+'\n }\n // Escape special regex chars\n return segment.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n })\n .join('\\\\.')\n\n const regex = new RegExp(`^${regexPattern}$`)\n return regex.test(path)\n}\n\n/**\n * Converts an environment variable key to dot-notation path\n *\n * Transforms uppercase underscore-separated env var names to lowercase\n * dot-notation paths. Optionally removes a prefix.\n *\n * Common convention: ENV_VAR_NAME → env.var.name\n *\n * @param envKey - Environment variable key (e.g., 'NX_FEATURES_AUTH', 'APP_DATABASE_HOST')\n * @param prefix - Optional prefix to remove (e.g., 'NX', 'APP'). Default: 'NX'\n * @returns Dot-notation path in lowercase\n *\n * @example\n * ```typescript\n * // Default prefix (NX)\n * envKeyToPath('NX_FEATURES_AUTH') // 'features.auth'\n * envKeyToPath('NX_FEATURES_PAYMENTS') // 'features.payments'\n * envKeyToPath('NX_DATABASE_HOST') // 'database.host'\n *\n * // Custom prefix\n * envKeyToPath('APP_DATABASE_HOST', 'APP') // 'database.host'\n * envKeyToPath('MY_CONFIG_VALUE', 'MY') // 'config.value'\n *\n * // No prefix\n * envKeyToPath('DATABASE_HOST', '') // 'database.host'\n * envKeyToPath('FEATURES_AUTH', '') // 'features.auth'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Parse environment variables to config object\n * const envVars = {\n * 'APP_DATABASE_HOST': 'localhost',\n * 'APP_DATABASE_PORT': '5432',\n * 'APP_CACHE_TTL': '3600',\n * }\n *\n * const config = {}\n * Object.entries(envVars).forEach(([key, value]) => {\n * const path = envKeyToPath(key, 'APP')\n * // Use with setDeepValue: setDeepValue(config, path, value)\n * })\n * // Paths: 'database.host', 'database.port', 'cache.ttl'\n * ```\n */\nexport function envKeyToPath(envKey: string, prefix: string = 'NX'): string {\n if (!envKey || typeof envKey !== 'string') {\n return ''\n }\n\n let key = envKey.trim()\n\n // Remove prefix if provided and matches\n if (prefix && key.startsWith(`${prefix}_`)) {\n key = key.substring(prefix.length + 1)\n }\n\n // Convert to lowercase and replace underscores with dots\n return key.toLowerCase().replace(/_/g, '.')\n}\n\n/**\n * Converts a dot-notation path to environment variable key format\n *\n * Transforms lowercase dot-notation paths to uppercase underscore-separated\n * env var names. Optionally adds a prefix.\n *\n * Common convention: env.var.name → ENV_VAR_NAME\n *\n * @param path - Dot-notation path (e.g., 'features.auth', 'database.host')\n * @param prefix - Optional prefix to add (e.g., 'NX', 'APP'). Default: 'NX'\n * @returns Environment variable key in uppercase\n *\n * @example\n * ```typescript\n * // Default prefix (NX)\n * pathToEnvKey('features.auth') // 'NX_FEATURES_AUTH'\n * pathToEnvKey('features.payments') // 'NX_FEATURES_PAYMENTS'\n * pathToEnvKey('database.host') // 'NX_DATABASE_HOST'\n *\n * // Custom prefix\n * pathToEnvKey('database.host', 'APP') // 'APP_DATABASE_HOST'\n * pathToEnvKey('config.value', 'MY') // 'MY_CONFIG_VALUE'\n *\n * // No prefix\n * pathToEnvKey('database.host', '') // 'DATABASE_HOST'\n * pathToEnvKey('features.auth', '') // 'FEATURES_AUTH'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Generate .env file from config object\n * const config = {\n * database: { host: 'localhost', port: 5432 },\n * cache: { ttl: 3600 }\n * }\n *\n * const envVars = [\n * `${pathToEnvKey('database.host', 'APP')}=localhost`,\n * `${pathToEnvKey('database.port', 'APP')}=5432`,\n * `${pathToEnvKey('cache.ttl', 'APP')}=3600`,\n * ]\n * // Output:\n * // APP_DATABASE_HOST=localhost\n * // APP_DATABASE_PORT=5432\n * // APP_CACHE_TTL=3600\n * ```\n */\nexport function pathToEnvKey(path: string, prefix: string = 'NX'): string {\n if (!path || typeof path !== 'string') {\n return ''\n }\n\n // Convert dots to underscores and uppercase\n const key = path.trim().toUpperCase().replace(/\\./g, '_')\n\n // Return empty if path was just whitespace\n if (key.length === 0) {\n return ''\n }\n\n // Add prefix if provided\n if (prefix && prefix.length > 0) {\n return `${prefix.toUpperCase()}_${key}`\n }\n\n return key\n}\n"],"mappings":"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","names":[]}
|