@hichchi/utils 0.0.1-beta.2 → 0.0.2

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.
Files changed (90) hide show
  1. package/index.cjs.default.js +1 -0
  2. package/index.cjs.js +3645 -0
  3. package/index.cjs.mjs +2 -0
  4. package/index.d.ts +1 -5
  5. package/index.esm.js +3575 -0
  6. package/package.json +13 -8
  7. package/src/index.d.ts +5 -0
  8. package/{interfaces → src/interfaces}/infinite-object.interface.d.ts +3 -3
  9. package/{interfaces → src/interfaces}/path-value-set.interface.d.ts +12 -12
  10. package/{types → src/types}/deep-partial.type.d.ts +6 -0
  11. package/{types → src/types}/literal-object.type.d.ts +2 -2
  12. package/{utils → src/utils}/index.d.ts +1 -0
  13. package/{utils → src/utils}/object.utils.d.ts +23 -19
  14. package/CHANGELOG.md +0 -19
  15. package/README.md +0 -7944
  16. package/constants/constants.js +0 -227
  17. package/constants/constants.js.map +0 -1
  18. package/constants/english-inflection-rules.js +0 -360
  19. package/constants/english-inflection-rules.js.map +0 -1
  20. package/constants/index.js +0 -6
  21. package/constants/index.js.map +0 -1
  22. package/enums/index.js +0 -5
  23. package/enums/index.js.map +0 -1
  24. package/enums/template-tag.enum.js +0 -39
  25. package/enums/template-tag.enum.js.map +0 -1
  26. package/index.js +0 -9
  27. package/index.js.map +0 -1
  28. package/interfaces/index.js +0 -7
  29. package/interfaces/index.js.map +0 -1
  30. package/interfaces/infinite-object.interface.js +0 -3
  31. package/interfaces/infinite-object.interface.js.map +0 -1
  32. package/interfaces/inflection-rule.interfaces.js +0 -3
  33. package/interfaces/inflection-rule.interfaces.js.map +0 -1
  34. package/interfaces/path-value-set.interface.js +0 -3
  35. package/interfaces/path-value-set.interface.js.map +0 -1
  36. package/readme-top.md +0 -187
  37. package/types/deep-partial.type.js +0 -3
  38. package/types/deep-partial.type.js.map +0 -1
  39. package/types/index.js +0 -38
  40. package/types/index.js.map +0 -1
  41. package/types/is-already-in-path.type.js +0 -3
  42. package/types/is-already-in-path.type.js.map +0 -1
  43. package/types/is-empty.type.js +0 -3
  44. package/types/is-empty.type.js.map +0 -1
  45. package/types/is-primitive.type.js +0 -4
  46. package/types/is-primitive.type.js.map +0 -1
  47. package/types/literal-object.type.js +0 -3
  48. package/types/literal-object.type.js.map +0 -1
  49. package/types/loose-autocomplete.type.js +0 -3
  50. package/types/loose-autocomplete.type.js.map +0 -1
  51. package/types/partial-with-null.type.js +0 -3
  52. package/types/partial-with-null.type.js.map +0 -1
  53. package/types/prettify.type.js +0 -3
  54. package/types/prettify.type.js.map +0 -1
  55. package/types/type.type.js +0 -3
  56. package/types/type.type.js.map +0 -1
  57. package/utils/assertions.utils.js +0 -163
  58. package/utils/assertions.utils.js.map +0 -1
  59. package/utils/file.utils.js +0 -1315
  60. package/utils/file.utils.js.map +0 -1
  61. package/utils/index.js +0 -9
  62. package/utils/index.js.map +0 -1
  63. package/utils/object.utils.js +0 -1069
  64. package/utils/object.utils.js.map +0 -1
  65. package/utils/string-template.utils.js +0 -269
  66. package/utils/string-template.utils.js.map +0 -1
  67. package/utils/string.utils.js +0 -1255
  68. package/utils/string.utils.js.map +0 -1
  69. package/utils/url.utils.js +0 -112
  70. package/utils/url.utils.js.map +0 -1
  71. /package/{constants → src/constants}/constants.d.ts +0 -0
  72. /package/{constants → src/constants}/english-inflection-rules.d.ts +0 -0
  73. /package/{constants → src/constants}/index.d.ts +0 -0
  74. /package/{enums → src/enums}/index.d.ts +0 -0
  75. /package/{enums → src/enums}/template-tag.enum.d.ts +0 -0
  76. /package/{interfaces → src/interfaces}/index.d.ts +0 -0
  77. /package/{interfaces → src/interfaces}/inflection-rule.interfaces.d.ts +0 -0
  78. /package/{types → src/types}/index.d.ts +0 -0
  79. /package/{types → src/types}/is-already-in-path.type.d.ts +0 -0
  80. /package/{types → src/types}/is-empty.type.d.ts +0 -0
  81. /package/{types → src/types}/is-primitive.type.d.ts +0 -0
  82. /package/{types → src/types}/loose-autocomplete.type.d.ts +0 -0
  83. /package/{types → src/types}/partial-with-null.type.d.ts +0 -0
  84. /package/{types → src/types}/prettify.type.d.ts +0 -0
  85. /package/{types → src/types}/type.type.d.ts +0 -0
  86. /package/{utils → src/utils}/assertions.utils.d.ts +0 -0
  87. /package/{utils → src/utils}/file.utils.d.ts +0 -0
  88. /package/{utils → src/utils}/string-template.utils.d.ts +0 -0
  89. /package/{utils → src/utils}/string.utils.d.ts +0 -0
  90. /package/{utils → src/utils}/url.utils.d.ts +0 -0
package/index.esm.js ADDED
@@ -0,0 +1,3575 @@
1
+ // noinspection JSUnusedGlobalSymbols
2
+ /**
3
+ * Validates if a redirect URL is allowed based on a whitelist of permitted domains.
4
+ *
5
+ * This security utility function checks whether a given URL's domain is included in a list
6
+ * of allowed domains, helping prevent open redirect vulnerabilities. It supports both exact
7
+ * domain matching and subdomain matching, where a URL's hostname must either exactly match
8
+ * an allowed domain or be a subdomain of an allowed domain.
9
+ *
10
+ * This is essential for secure redirect functionality in web applications, preventing
11
+ * attackers from redirecting users to malicious external sites. It's commonly used in
12
+ * authentication flows, logout redirects, and any feature that accepts user-provided
13
+ * redirect URLs.
14
+ *
15
+ * @param {string} url - The URL to validate. Must be a valid URL string that can be parsed
16
+ * by the URL constructor. Can include protocol, path, query parameters, etc.
17
+ * @param {string[]} allowedDomains - Array of domain names that are permitted for redirects.
18
+ * Domains should be specified without protocol (e.g., "example.com").
19
+ * Subdomains of these domains will also be allowed.
20
+ * @returns {boolean} True if the URL's hostname matches or is a subdomain of any allowed domain,
21
+ * false if the domain is not allowed or if the URL is invalid
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * // Basic domain validation
26
+ * const allowedDomains = ["example.com", "myapp.com"];
27
+ *
28
+ * const isValid1 = isValidRedirectUrl("https://example.com/dashboard", allowedDomains);
29
+ * // Returns: true (exact domain match)
30
+ *
31
+ * const isValid2 = isValidRedirectUrl("https://api.example.com/callback", allowedDomains);
32
+ * // Returns: true (subdomain of allowed domain)
33
+ *
34
+ * const isValid3 = isValidRedirectUrl("https://malicious.com/phishing", allowedDomains);
35
+ * // Returns: false (domain not in allowed list)
36
+ * ```
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * // Authentication redirect validation
41
+ * function handleAuthRedirect(redirectUrl: string): void {
42
+ * const trustedDomains = [
43
+ * "myapp.com",
44
+ * "admin.myapp.com",
45
+ * "localhost" // for development
46
+ * ];
47
+ *
48
+ * if (isValidRedirectUrl(redirectUrl, trustedDomains)) {
49
+ * window.location.href = redirectUrl;
50
+ * } else {
51
+ * // Redirect to safe default instead
52
+ * window.location.href = "/dashboard";
53
+ * console.warn("Attempted redirect to untrusted domain:", redirectUrl);
54
+ * }
55
+ * }
56
+ * ```
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // API endpoint validation
61
+ * app.post('/auth/login', (req, res) => {
62
+ * const { username, password, redirectUrl } = req.body;
63
+ *
64
+ * if (authenticateUser(username, password)) {
65
+ * const allowedDomains = ["myapp.com", "partner.com"];
66
+ *
67
+ * if (redirectUrl && isValidRedirectUrl(redirectUrl, allowedDomains)) {
68
+ * res.json({ success: true, redirectUrl });
69
+ * } else {
70
+ * res.json({ success: true, redirectUrl: "/dashboard" });
71
+ * }
72
+ * } else {
73
+ * res.status(401).json({ error: "Invalid credentials" });
74
+ * }
75
+ * });
76
+ * ```
77
+ *
78
+ * @example
79
+ * ```typescript
80
+ * // Complex subdomain scenarios
81
+ * const domains = ["company.com"];
82
+ *
83
+ * isValidRedirectUrl("https://app.company.com", domains); // true
84
+ * isValidRedirectUrl("https://api.app.company.com", domains); // true
85
+ * isValidRedirectUrl("https://company.com.evil.com", domains); // false
86
+ * isValidRedirectUrl("https://fakecompany.com", domains); // false
87
+ * ```
88
+ *
89
+ * @remarks
90
+ * - Uses the native URL constructor for reliable URL parsing
91
+ * - Supports both exact domain matches and subdomain matches
92
+ * - Returns false for invalid URLs that cannot be parsed
93
+ * - Domain matching is case-insensitive (handled by URL constructor)
94
+ * - Does not validate the protocol - focuses only on hostname validation
95
+ * - Subdomain matching uses endsWith() to ensure proper domain boundaries
96
+ * - Empty or null URL strings will cause the function to return false
97
+ *
98
+ * @throws {never} This function catches all URL parsing errors and returns false instead of throwing
99
+ */
100
+ function isValidRedirectUrl(url, allowedDomains) {
101
+ try {
102
+ const parsedUrl = new URL(url);
103
+ return allowedDomains.some(domain => parsedUrl.hostname === domain || parsedUrl.hostname.endsWith(`.${domain}`));
104
+ } catch {
105
+ return false;
106
+ }
107
+ }
108
+
109
+ /* eslint-disable @typescript-eslint/no-explicit-any */
110
+ // noinspection JSUnusedGlobalSymbols
111
+ /**
112
+ * Deep copy an object.
113
+ * @template T Type of the object.
114
+ * @param {T} obj Object to copy.
115
+ * @returns {T} Copied object.
116
+ *
117
+ * @example
118
+ * ```TypeScript
119
+ * // Example usage
120
+ * const object = {
121
+ * name: "John Doe"
122
+ * }
123
+ *
124
+ * const copiedObject = deepCopy(object);
125
+ * ```
126
+ */
127
+ function deepCopy(obj) {
128
+ if (Array.isArray(obj)) {
129
+ return obj.map(deepCopy);
130
+ } else if (typeof obj === "object" && obj !== null) {
131
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, deepCopy(value)]));
132
+ }
133
+ return obj;
134
+ }
135
+ /**
136
+ * Get the key of a map by value.
137
+ * @param {Map<string, unknown>} map Map to get key from.
138
+ * @param {unknown} value Value to get key for.
139
+ * @returns {string | undefined} Key of the map.
140
+ *
141
+ * @example
142
+ * ```TypeScript
143
+ * // Example usage
144
+ * const user = new Map<string, string>([
145
+ * ["firstName", "John"],
146
+ * ["lastName", "Doe"],
147
+ * ["preferredName", "John"],
148
+ * ["age", 30],
149
+ * ]);
150
+ *
151
+ * const key = getMapKey(user, "value2");
152
+ *
153
+ * // Example output: "firstName"
154
+ * ```
155
+ */
156
+ function getMapKey(map, value) {
157
+ return [...Array.from(map.entries())].find(([, v]) => v === value)?.[0];
158
+ }
159
+ /**
160
+ * Retrieves all keys from a Map where the corresponding values contain a specified substring.
161
+ *
162
+ * This utility function searches through a Map's values and returns an array of keys
163
+ * whose associated values include the provided partial string. The search is case-sensitive
164
+ * and uses JavaScript's native string.includes() method for matching.
165
+ *
166
+ * This is particularly useful for implementing search functionality, filtering operations,
167
+ * or finding related entries in configuration maps, user preference stores, or any
168
+ * key-value data structure where you need to locate entries by partial value matching.
169
+ *
170
+ * @param {Map<string, string>} map - The Map to search through for matching values
171
+ * @param {string} partialValue - The substring to search for within the Map's values
172
+ * @returns {string[]} An array of keys whose corresponding values contain the partial value
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * // Search user preferences for entries containing "John"
177
+ * const userPreferences = new Map<string, string>([
178
+ * ["displayName", "John Doe"],
179
+ * ["firstName", "John"],
180
+ * ["lastName", "Doe"],
181
+ * ["preferredName", "Johnny"],
182
+ * ["email", "john.doe@example.com"]
183
+ * ]);
184
+ *
185
+ * const johnKeys = getMapKeys(userPreferences, "John");
186
+ * // Returns: ["displayName", "firstName", "email"]
187
+ * ```
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * // Find configuration keys related to database settings
192
+ * const config = new Map<string, string>([
193
+ * ["db_host", "localhost"],
194
+ * ["db_port", "5432"],
195
+ * ["cache_host", "redis-server"],
196
+ * ["db_name", "myapp_database"],
197
+ * ["log_level", "debug"]
198
+ * ]);
199
+ *
200
+ * const dbKeys = getMapKeys(config, "db");
201
+ * // Returns: ["db_name"] (only values containing "db", not keys)
202
+ * ```
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * // Search in a translations map
207
+ * const translations = new Map<string, string>([
208
+ * ["welcome_message", "Welcome to our application"],
209
+ * ["error_message", "An error occurred"],
210
+ * ["success_message", "Operation completed successfully"],
211
+ * ["info_message", "Please check your information"]
212
+ * ]);
213
+ *
214
+ * const messageKeys = getMapKeys(translations, "message");
215
+ * // Returns: ["welcome_message", "error_message", "success_message", "info_message"]
216
+ * ```
217
+ *
218
+ * @remarks
219
+ * - The search is case-sensitive; "John" will not match "john"
220
+ * - Returns an empty array if no matches are found
221
+ * - The function iterates through all Map entries, so performance scales with Map size
222
+ * - Only works with Maps that have string values; other value types will cause runtime errors
223
+ */
224
+ const getMapKeys = (map, partialValue) => {
225
+ const keys = [];
226
+ for (const [key, value] of Array.from(map.entries())) {
227
+ if (value.includes(partialValue)) {
228
+ keys.push(key);
229
+ }
230
+ }
231
+ return keys;
232
+ };
233
+ /**
234
+ * Groups an array of objects into a Map based on a key extraction function.
235
+ *
236
+ * This utility function takes an array of objects and organizes them into groups
237
+ * based on a common key or property. The grouping is performed using a key extraction
238
+ * function that you provide, which determines how each object should be categorized.
239
+ * The result is a Map where each key represents a group and the value is an array
240
+ * of objects belonging to that group.
241
+ *
242
+ * This is particularly useful for data analysis, reporting, organizing collections
243
+ * for display purposes, or preparing data for further processing where items need
244
+ * to be categorized by shared characteristics.
245
+ *
246
+ * @template K - The type of the grouping key (can be string, number, boolean, etc.)
247
+ * @template V - The type of the objects being grouped
248
+ * @param {Array<V>} list - The array of objects to group
249
+ * @param {(input: V) => K} keyGetter - A function that extracts the grouping key from each object.
250
+ * This function receives an object and should return the value
251
+ * to group by. If the function returns null or undefined,
252
+ * the object will be grouped under a null key.
253
+ * @returns {Map<K | null, Array<V>>} A Map where keys are the grouping values and values are arrays
254
+ * of objects that share that grouping key
255
+ *
256
+ * @example
257
+ * ```typescript
258
+ * // Group users by age
259
+ * interface User {
260
+ * name: string;
261
+ * age: number;
262
+ * department: string;
263
+ * }
264
+ *
265
+ * const users: User[] = [
266
+ * { name: "John", age: 30, department: "Engineering" },
267
+ * { name: "Jane", age: 25, department: "Marketing" },
268
+ * { name: "Bob", age: 30, department: "Engineering" },
269
+ * { name: "Alice", age: 25, department: "Sales" },
270
+ * { name: "Charlie", age: 35, department: "Engineering" }
271
+ * ];
272
+ *
273
+ * const usersByAge = groupBy(users, user => user.age);
274
+ * // Returns:
275
+ * // Map {
276
+ * // 30 => [
277
+ * // { name: "John", age: 30, department: "Engineering" },
278
+ * // { name: "Bob", age: 30, department: "Engineering" }
279
+ * // ],
280
+ * // 25 => [
281
+ * // { name: "Jane", age: 25, department: "Marketing" },
282
+ * // { name: "Alice", age: 25, department: "Sales" }
283
+ * // ],
284
+ * // 35 => [
285
+ * // { name: "Charlie", age: 35, department: "Engineering" }
286
+ * // ]
287
+ * // }
288
+ * ```
289
+ *
290
+ * @example
291
+ * ```typescript
292
+ * // Group products by category
293
+ * interface Product {
294
+ * id: number;
295
+ * name: string;
296
+ * category: string;
297
+ * price: number;
298
+ * }
299
+ *
300
+ * const products: Product[] = [
301
+ * { id: 1, name: "Laptop", category: "Electronics", price: 999 },
302
+ * { id: 2, name: "Book", category: "Education", price: 29 },
303
+ * { id: 3, name: "Phone", category: "Electronics", price: 699 },
304
+ * { id: 4, name: "Pen", category: "Office", price: 5 }
305
+ * ];
306
+ *
307
+ * const productsByCategory = groupBy(products, product => product.category);
308
+ * // Useful for creating category-based product listings
309
+ * ```
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * // Group tasks by completion status with boolean keys
314
+ * interface Task {
315
+ * id: number;
316
+ * title: string;
317
+ * completed: boolean;
318
+ * }
319
+ *
320
+ * const tasks: Task[] = [
321
+ * { id: 1, title: "Review code", completed: true },
322
+ * { id: 2, title: "Write tests", completed: false },
323
+ * { id: 3, title: "Deploy app", completed: true },
324
+ * { id: 4, title: "Update docs", completed: false }
325
+ * ];
326
+ *
327
+ * const tasksByStatus = groupBy(tasks, task => task.completed);
328
+ * // Returns Map with boolean keys: true and false
329
+ * ```
330
+ *
331
+ * @remarks
332
+ * - The function preserves the original order of objects within each group
333
+ * - If the keyGetter function returns null or undefined, those objects will be grouped under a null key
334
+ * - The Map structure allows for efficient lookups and iteration over groups
335
+ * - Works with any type of grouping key (string, number, boolean, objects, etc.)
336
+ * - Empty arrays will return an empty Map
337
+ */
338
+ const groupBy = (list, keyGetter) => {
339
+ const map = new Map();
340
+ list.forEach(item => {
341
+ const key = keyGetter(item);
342
+ const collection = map.get(key);
343
+ if (!collection) {
344
+ map.set(key, [item]);
345
+ } else {
346
+ collection.push(item);
347
+ }
348
+ });
349
+ return map;
350
+ };
351
+ /**
352
+ * Retrieves all values from a Map that contain a specified substring.
353
+ *
354
+ * This utility function searches through a Map's values and returns an array of all values
355
+ * that include the provided partial string. The search is case-sensitive and uses JavaScript's
356
+ * native string.includes() method for matching. This is the counterpart to getMapKeys(),
357
+ * but instead of returning the keys, it returns the actual values that match.
358
+ *
359
+ * This is particularly useful for implementing search functionality where you need to find
360
+ * and display matching content, filtering data for autocomplete features, or extracting
361
+ * related values from configuration maps, user data, or any key-value store where you
362
+ * need to locate entries by partial content matching.
363
+ *
364
+ * @param {Map<string, string>} map - The Map to search through for matching values
365
+ * @param {string} partialValue - The substring to search for within the Map's values
366
+ * @returns {string[]} An array of values that contain the specified partial value
367
+ *
368
+ * @example
369
+ * ```typescript
370
+ * // Search user information for values containing "John"
371
+ * const userInfo = new Map<string, string>([
372
+ * ["fullName", "John Doe"],
373
+ * ["firstName", "John"],
374
+ * ["lastName", "Doe"],
375
+ * ["nickname", "Johnny"],
376
+ * ["email", "john.doe@example.com"],
377
+ * ["phone", "555-0123"]
378
+ * ]);
379
+ *
380
+ * const johnValues = searchMapValues(userInfo, "John");
381
+ * // Returns: ["John Doe", "John", "Johnny", "john.doe@example.com"]
382
+ * ```
383
+ *
384
+ * @example
385
+ * ```typescript
386
+ * // Find error messages containing specific keywords
387
+ * const errorMessages = new Map<string, string>([
388
+ * ["auth_failed", "Authentication failed. Please check your credentials."],
389
+ * ["network_error", "Network connection failed. Please try again."],
390
+ * ["validation_error", "Validation failed for the provided data."],
391
+ * ["timeout_error", "Request timeout. The server took too long to respond."],
392
+ * ["permission_denied", "Access denied. You don't have permission."]
393
+ * ]);
394
+ *
395
+ * const failedMessages = searchMapValues(errorMessages, "failed");
396
+ * // Returns: ["Authentication failed. Please check your credentials.",
397
+ * // "Network connection failed. Please try again.",
398
+ * // "Validation failed for the provided data."]
399
+ * ```
400
+ *
401
+ * @example
402
+ * ```typescript
403
+ * // Search configuration values for environment-specific settings
404
+ * const config = new Map<string, string>([
405
+ * ["api_url", "https://api.production.example.com"],
406
+ * ["db_host", "production-db.example.com"],
407
+ * ["cache_url", "redis://production-cache.example.com"],
408
+ * ["log_level", "error"],
409
+ * ["debug_mode", "false"]
410
+ * ]);
411
+ *
412
+ * const productionValues = searchMapValues(config, "production");
413
+ * // Returns: ["https://api.production.example.com",
414
+ * // "production-db.example.com",
415
+ * // "redis://production-cache.example.com"]
416
+ * ```
417
+ *
418
+ * @remarks
419
+ * - The search is case-sensitive; "John" will not match "john"
420
+ * - Returns an empty array if no matches are found
421
+ * - The function iterates through all Map entries, so performance scales with Map size
422
+ * - Only works with Maps that have string values; other value types will cause runtime errors
423
+ * - Values are returned in the order they appear in the Map iteration
424
+ *
425
+ * @see {@link getMapKeys} Function to get keys whose values contain a substring
426
+ */
427
+ const searchMapValues = (map, partialValue) => {
428
+ const values = [];
429
+ for (const [, value] of Array.from(map.entries())) {
430
+ if (value.includes(partialValue)) {
431
+ values.push(value);
432
+ }
433
+ }
434
+ return values;
435
+ };
436
+ /**
437
+ * Gets a value from a nested object using a dot-notation path string.
438
+ *
439
+ * This function safely traverses a deeply nested object structure using a string path
440
+ * with dot notation. It handles both object properties and array indices within the path.
441
+ *
442
+ * @template T - Type of the value to be returned.
443
+ * @param {InfiniteObject} obj - The object to retrieve the value from.
444
+ * @param {string} path - The dot-notation path to the desired value.
445
+ * - Use dots to navigate through nested objects: 'user.profile.address'
446
+ * - Use array notation for accessing array elements: 'items[0]' or 'users[2].name'
447
+ * @returns {T | undefined} - The value at the specified path, or undefined if:
448
+ * - Any part of the path doesn't exist
449
+ * - An array index is out of bounds
450
+ * - The path format is invalid
451
+ *
452
+ * @example
453
+ * ```typescript
454
+ * // Simple nested object property
455
+ * const user = {
456
+ * profile: {
457
+ * name: "John Doe",
458
+ * contact: { email: "john@example.com" }
459
+ * }
460
+ * };
461
+ * const email = getValueByPath<string>(user, "profile.contact.email");
462
+ * // Returns: "john@example.com"
463
+ * ```
464
+ *
465
+ * @example
466
+ * ```typescript
467
+ * // Accessing array elements
468
+ * const data = {
469
+ * users: [
470
+ * { id: 1, name: "Alice" },
471
+ * { id: 2, name: "Bob" }
472
+ * ]
473
+ * };
474
+ * const name = getValueByPath<string>(data, "users[1].name");
475
+ * // Returns: "Bob"
476
+ * ```
477
+ */
478
+ const getValueByPath = (obj, path) => {
479
+ const keys = path.split("."); // Split the path into an array of keys
480
+ let value = obj;
481
+ for (const key of keys) {
482
+ // noinspection RegExpRedundantEscape
483
+ const regExp = /^(\w+)\[(\d+)\]$/;
484
+ const isArrayIndex = regExp.exec(key); // Check if the key is an array index
485
+ if (isArrayIndex) {
486
+ const arrayKey = isArrayIndex[1];
487
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
488
+ const index = Number(isArrayIndex[2]);
489
+ if (arrayKey && Array.isArray(value[arrayKey]) && index >= 0 && index < value[arrayKey].length) {
490
+ value = value[arrayKey][index]; // Update the value to the array element
491
+ } else {
492
+ return undefined; // Return undefined if the array index is invalid
493
+ }
494
+ } else if (value && typeof value === "object" && key in value) {
495
+ value = value[key]; // Update the value to the nested property
496
+ } else {
497
+ return undefined; // Return undefined if any key is not found
498
+ }
499
+ }
500
+ return value;
501
+ };
502
+ /**
503
+ * Converts a nested object into a flattened DottedPathValueObject representation.
504
+ *
505
+ * This function transforms a hierarchical object structure into a flat key-value map
506
+ * where keys represent paths to values in the original object using dot notation.
507
+ *
508
+ * The function recursively traverses the object and flattens nested properties,
509
+ * converting object hierarchies like `{ user: { name: 'John' } }` into
510
+ * path-based entries like `{ 'user.name': 'John' }`.
511
+ *
512
+ * @param {LiteralObject} obj - The nested object to flatten
513
+ * @returns {DottedPathValueObject} - A flattened representation where:
514
+ * - Keys are dot-notation paths to values in the original object
515
+ * - Values are primitive values (strings, numbers, booleans) from the original object
516
+ *
517
+ * @remarks
518
+ * - Array values will be preserved as-is (not flattened into separate paths)
519
+ * - Only primitive values (string, number, boolean) are supported as leaf values
520
+ * - Circular references are not handled and will cause a stack overflow
521
+ *
522
+ * @see {@link dottedPathObjectToNested} The inverse operation to convert a DottedPathValueObject back to a nested object
523
+ * @see {@link DottedPathValueObject} The interface for the returned flattened object
524
+ *
525
+ * @example
526
+ * ```typescript
527
+ * // Flatten a nested user object
528
+ * const user = {
529
+ * id: 123,
530
+ * name: "John Doe",
531
+ * isActive: true,
532
+ * profile: {
533
+ * age: 30,
534
+ * address: {
535
+ * city: "New York",
536
+ * zip: "10001"
537
+ * }
538
+ * }
539
+ * };
540
+ *
541
+ * const flattened = objectToDottedPathValueObject(user);
542
+ *
543
+ * // Result:
544
+ * // {
545
+ * // "id": 123,
546
+ * // "name": "John Doe",
547
+ * // "isActive": true,
548
+ * // "profile.age": 30,
549
+ * // "profile.address.city": "New York",
550
+ * // "profile.address.zip": "10001"
551
+ * // }
552
+ * ```
553
+ */
554
+ function objectToDottedPathValueObject(obj) {
555
+ const result = {};
556
+ function traverse(obj, path = []) {
557
+ for (const key in obj) {
558
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
559
+ const value = obj[key];
560
+ if (typeof value === "object" && !Array.isArray(value)) {
561
+ traverse(value, [...path, key]);
562
+ } else {
563
+ result[[...path, key].join(".")] = value;
564
+ }
565
+ }
566
+ }
567
+ }
568
+ traverse(obj);
569
+ return result;
570
+ }
571
+ /**
572
+ * Converts a flattened DottedPathValueObject back into a nested object structure.
573
+ *
574
+ * This function is the inverse of `objectToDottedPathValueObject`. It takes a flat map of
575
+ * dot-notation paths to values and reconstructs a hierarchical object structure.
576
+ *
577
+ * Each key in the input DottedPathValueObject represents a path through the object hierarchy,
578
+ * with dots separating each level. The function builds a nested object structure by
579
+ * parsing these paths and placing values at the appropriate locations.
580
+ *
581
+ * @template R - The type of the returned object (defaults to object)
582
+ * @param {DottedPathValueObject} pathValueSet - A flattened object with dot-notation path keys
583
+ * @returns {R} - A reconstructed nested object with the original hierarchy
584
+ *
585
+ * @remarks
586
+ * - Paths are validated for safety to prevent injection attacks
587
+ * - Invalid paths are silently skipped (not included in the result)
588
+ * - Path components should contain only alphanumeric characters, underscores, hyphens, and dots
589
+ *
590
+ * @see {@link objectToDottedPathValueObject} The inverse operation to convert an object to DottedPathValueObject
591
+ * @see {@link DottedPathValueObject} The interface for the input flattened object
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * // Convert a flat DottedPathValueObject to a nested object
596
+ * const flatData = {
597
+ * "id": 123,
598
+ * "name": "John Doe",
599
+ * "profile.age": 30,
600
+ * "profile.address.city": "New York",
601
+ * "profile.address.zip": "10001"
602
+ * };
603
+ *
604
+ * const nestedObject = dottedPathObjectToNested(flatData);
605
+ *
606
+ * Result:
607
+ * // {
608
+ * // id: 123,
609
+ * // name: "John Doe",
610
+ * // profile: {
611
+ * // age: 30,
612
+ * // address: {
613
+ * // city: "New York",
614
+ * // zip: "10001"
615
+ * // }
616
+ * // }
617
+ * // }
618
+ * ```
619
+ *
620
+ * @example
621
+ * ```typescript
622
+ * // Typed return value
623
+ * interface User {
624
+ * id: number;
625
+ * name: string;
626
+ * profile: {
627
+ * age: number;
628
+ * address: {
629
+ * city: string;
630
+ * zip: string;
631
+ * };
632
+ * };
633
+ * }
634
+ *
635
+ * const userData = dottedPathObjectToNested<User>(flatData);
636
+ * // Returns object with User type
637
+ * ```
638
+ */
639
+ function dottedPathObjectToNested(pathValueSet) {
640
+ const object = {};
641
+ // Helper function to validate paths
642
+ const isValidPath = path => {
643
+ const regex = /^[a-zA-Z0-9_.-]+$/;
644
+ return path.split(".").every(part => regex.test(part));
645
+ };
646
+ // Helper function to set nested properties
647
+ const setObjectValue = (obj, keys, value) => {
648
+ const [firstKey, ...remainingKeys] = keys;
649
+ if (remainingKeys.length === 0) {
650
+ obj[firstKey] = value; // Set value at the final key
651
+ return;
652
+ }
653
+ // Initialize the key if it doesn't exist
654
+ obj[firstKey] = obj[firstKey] || {};
655
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
656
+ setObjectValue(obj[firstKey], remainingKeys, value); // Recurse for the rest of the keys
657
+ // TODO: v2.0 Fix type for above
658
+ };
659
+ for (const path in pathValueSet) {
660
+ if (Object.prototype.hasOwnProperty.call(pathValueSet, path)) {
661
+ if (!isValidPath(path)) {
662
+ continue; // Skip invalid paths
663
+ }
664
+ const value = pathValueSet[path];
665
+ const keys = path.split("."); // Split path into keys
666
+ setObjectValue(object, keys, value); // Use helper to populate the object
667
+ }
668
+ }
669
+ return object;
670
+ }
671
+ /**
672
+ * Creates a new object by omitting specified properties and undefined values from the original object.
673
+ *
674
+ * This utility function performs a selective copy of an object, excluding both explicitly
675
+ * specified properties and any properties with undefined values. The function modifies
676
+ * the original object in-place by deleting the unwanted properties, making it useful for
677
+ * cleaning up objects before serialization, API calls, or data processing.
678
+ *
679
+ * This is particularly useful for preparing data for API requests where you need to remove
680
+ * sensitive fields, clean up form data, or exclude certain properties from being sent to
681
+ * external services. It's also helpful for removing undefined values that might cause
682
+ * issues in JSON serialization or database operations.
683
+ *
684
+ * @template T - The type of the object being processed
685
+ * @param {Partial<T>} obj - The object to process and remove properties from.
686
+ * This object will be modified in-place.
687
+ * @param {(keyof T)[]} [keys] - Optional array of property keys to omit from the object.
688
+ * If not provided, only undefined properties will be removed.
689
+ * @returns {Partial<T>} The same object reference with specified properties and undefined values removed
690
+ *
691
+ * @example
692
+ * ```typescript
693
+ * // Remove specific properties and undefined values
694
+ * interface User {
695
+ * id: number;
696
+ * name: string;
697
+ * email: string;
698
+ * role: string;
699
+ * address?: string;
700
+ * phone?: string;
701
+ * }
702
+ *
703
+ * const user: Partial<User> = {
704
+ * id: 123,
705
+ * name: "John Doe",
706
+ * email: "john@example.com",
707
+ * role: "admin",
708
+ * address: undefined,
709
+ * phone: "555-0123"
710
+ * };
711
+ *
712
+ * const cleanUser = omit(user, ["role"]);
713
+ * // Returns: { id: 123, name: "John Doe", email: "john@example.com", phone: "555-0123" }
714
+ * // Note: 'role' and 'address' (undefined) are removed
715
+ * ```
716
+ *
717
+ * @example
718
+ * ```typescript
719
+ * // Remove only undefined values (no specific keys)
720
+ * const formData = {
721
+ * firstName: "John",
722
+ * lastName: "Doe",
723
+ * middleName: undefined,
724
+ * email: "john@example.com",
725
+ * phone: undefined
726
+ * };
727
+ *
728
+ * const cleanFormData = omit(formData);
729
+ * // Returns: { firstName: "John", lastName: "Doe", email: "john@example.com" }
730
+ * ```
731
+ *
732
+ * @example
733
+ * ```typescript
734
+ * // Prepare data for API request by removing sensitive fields
735
+ * interface UserProfile {
736
+ * id: number;
737
+ * username: string;
738
+ * email: string;
739
+ * password: string;
740
+ * internalNotes: string;
741
+ * createdAt: Date;
742
+ * }
743
+ *
744
+ * const userProfile: Partial<UserProfile> = {
745
+ * id: 1,
746
+ * username: "johndoe",
747
+ * email: "john@example.com",
748
+ * password: "secret123",
749
+ * internalNotes: "VIP customer",
750
+ * createdAt: new Date()
751
+ * };
752
+ *
753
+ * const publicProfile = omit(userProfile, ["password", "internalNotes"]);
754
+ * // Safe to send to client: { id: 1, username: "johndoe", email: "john@example.com", createdAt: Date }
755
+ * ```
756
+ *
757
+ * @remarks
758
+ * - This function modifies the original object in-place rather than creating a copy
759
+ * - Both explicitly specified keys and undefined properties are removed
760
+ * - If the input object is falsy (null, undefined), it's returned as-is
761
+ * - The function uses delete operator, which may affect object performance in some JavaScript engines
762
+ * - Properties with null values are preserved (only undefined values are automatically removed)
763
+ *
764
+ * @see {@link prune} Function for more comprehensive object cleaning including null and empty values
765
+ */
766
+ const omit = (obj, keys) => {
767
+ if (obj) {
768
+ Object.keys(obj).forEach(key => {
769
+ return (obj[key] === undefined || keys?.includes(key)) && delete obj[key];
770
+ });
771
+ }
772
+ return obj;
773
+ };
774
+ /**
775
+ * Creates a deep copy of an object with all empty, null, undefined, and optionally prototype properties removed.
776
+ *
777
+ * This utility function recursively traverses an object and creates a clean copy by excluding
778
+ * properties that are null, undefined, or empty strings. It performs a deep cleaning operation,
779
+ * processing nested objects recursively to ensure that the entire object hierarchy is pruned
780
+ * of unwanted values.
781
+ *
782
+ * This is particularly useful for preparing data for serialization, cleaning up API responses,
783
+ * removing empty form fields, or ensuring that only meaningful data is processed or stored.
784
+ * The function is especially valuable when working with deeply nested objects that may contain
785
+ * sparse data or when you need to eliminate all traces of empty values from complex data structures.
786
+ *
787
+ * @template T - The type of the object being pruned
788
+ * @param {PartialWithNull<T>} obj - The object to prune. Can contain null, undefined, or empty values.
789
+ * @param {boolean} [omitPrototype=false] - Whether to exclude inherited properties from the prototype chain.
790
+ * If true, only own properties will be included in the result.
791
+ * If false (default), inherited enumerable properties will be processed.
792
+ * @returns {T} A new object with all empty, null, and undefined properties recursively removed
793
+ *
794
+ * @example
795
+ * ```typescript
796
+ * // Clean up a user profile with nested empty values
797
+ * interface UserProfile {
798
+ * id: number;
799
+ * name: string;
800
+ * contact: {
801
+ * email: string;
802
+ * phone?: string;
803
+ * address?: {
804
+ * street: string;
805
+ * city: string;
806
+ * zip?: string;
807
+ * };
808
+ * };
809
+ * preferences: {
810
+ * theme: string;
811
+ * notifications?: boolean;
812
+ * };
813
+ * }
814
+ *
815
+ * const userProfile = {
816
+ * id: 123,
817
+ * name: "John Doe",
818
+ * contact: {
819
+ * email: "john@example.com",
820
+ * phone: "",
821
+ * address: {
822
+ * street: "123 Main St",
823
+ * city: "New York",
824
+ * zip: null
825
+ * }
826
+ * },
827
+ * preferences: {
828
+ * theme: "dark",
829
+ * notifications: undefined
830
+ * }
831
+ * };
832
+ *
833
+ * const cleanProfile = prune(userProfile);
834
+ * // Returns:
835
+ * // {
836
+ * // id: 123,
837
+ * // name: "John Doe",
838
+ * // contact: {
839
+ * // email: "john@example.com",
840
+ * // address: {
841
+ * // street: "123 Main St",
842
+ * // city: "New York"
843
+ * // }
844
+ * // },
845
+ * // preferences: {
846
+ * // theme: "dark"
847
+ * // }
848
+ * // }
849
+ * ```
850
+ *
851
+ * @example
852
+ * ```typescript
853
+ * // Clean up form data before submission
854
+ * const formData = {
855
+ * firstName: "John",
856
+ * lastName: "Doe",
857
+ * middleName: "",
858
+ * email: "john@example.com",
859
+ * phone: null,
860
+ * address: {
861
+ * street: "123 Main St",
862
+ * apartment: "",
863
+ * city: "New York",
864
+ * state: undefined,
865
+ * zip: "10001"
866
+ * },
867
+ * emergencyContact: {
868
+ * name: "",
869
+ * phone: null
870
+ * }
871
+ * };
872
+ *
873
+ * const cleanFormData = prune(formData);
874
+ * // Returns only fields with meaningful values:
875
+ * // {
876
+ * // firstName: "John",
877
+ * // lastName: "Doe",
878
+ * // email: "john@example.com",
879
+ * // address: {
880
+ * // street: "123 Main St",
881
+ * // city: "New York",
882
+ * // zip: "10001"
883
+ * // }
884
+ * // }
885
+ * // Note: emergencyContact object is completely removed as all its properties were empty
886
+ * ```
887
+ *
888
+ * @example
889
+ * ```typescript
890
+ * // Control prototype property inclusion
891
+ * class BaseUser {
892
+ * role = "user";
893
+ * }
894
+ *
895
+ * class ExtendedUser extends BaseUser {
896
+ * name = "John";
897
+ * email = "";
898
+ * }
899
+ *
900
+ * const user = new ExtendedUser();
901
+ *
902
+ * const prunedWithPrototype = prune(user, false);
903
+ * // Includes inherited 'role' property: { role: "user", name: "John" }
904
+ *
905
+ * const prunedOwnOnly = prune(user, true);
906
+ * // Only own properties: { name: "John" }
907
+ * ```
908
+ *
909
+ * @remarks
910
+ * - Creates a new object rather than modifying the original (unlike the omit function)
911
+ * - Recursively processes nested objects to ensure deep cleaning
912
+ * - Removes properties with values: null, undefined, or empty string ("")
913
+ * - Preserves properties with falsy but meaningful values like 0, false
914
+ * - If a nested object becomes empty after pruning, it will be excluded from the result
915
+ * - Non-object types are returned as an empty object of the target type
916
+ * - The function preserves the type structure while removing empty values
917
+ *
918
+ * @see {@link omit} Function for selective property removal without deep cleaning
919
+ */
920
+ const prune = (obj, omitPrototype) => {
921
+ const objClone = {};
922
+ if (typeof obj !== "object") {
923
+ return objClone;
924
+ }
925
+ for (const key in obj) {
926
+ if (!omitPrototype || Object.prototype.hasOwnProperty.call(obj, key)) {
927
+ if (obj[key] !== null && typeof obj[key] === "object") {
928
+ objClone[key] = prune(obj[key], omitPrototype);
929
+ } else if (obj[key] !== null && obj[key] !== undefined && obj[key] !== "") {
930
+ objClone[key] = obj[key];
931
+ }
932
+ }
933
+ }
934
+ return objClone;
935
+ };
936
+ /**
937
+ * Checks if an object has all specified properties as its own properties (not inherited).
938
+ *
939
+ * This utility function verifies that an object contains all the properties listed in the
940
+ * provided array as direct (own) properties, not inherited from the prototype chain.
941
+ * It uses the modern Object.hasOwn() method for reliable property existence checking,
942
+ * making it safer than using hasOwnProperty() directly.
943
+ *
944
+ * This is particularly useful for validating object structures, ensuring required properties
945
+ * exist before processing, implementing type guards, or verifying that objects conform to
946
+ * expected interfaces. It's commonly used in data validation, API request validation,
947
+ * and ensuring objects have all necessary properties before performing operations.
948
+ *
949
+ * @param {Record<PropertyKey, unknown>} obj - The object to check for property existence
950
+ * @param {PropertyKey[]} props - Array of property keys to verify exist on the object.
951
+ * PropertyKey includes string, number, and symbol types.
952
+ * @returns {boolean} True if the object has all specified properties as own properties, false otherwise
953
+ *
954
+ * @example
955
+ * ```typescript
956
+ * // Validate that a user object has required properties
957
+ * interface User {
958
+ * id: number;
959
+ * name: string;
960
+ * email: string;
961
+ * }
962
+ *
963
+ * const user = {
964
+ * id: 123,
965
+ * name: "John Doe",
966
+ * email: "john@example.com",
967
+ * role: "admin"
968
+ * };
969
+ *
970
+ * const hasRequiredProps = hasOwnAll(user, ["id", "name", "email"]);
971
+ * // Returns: true
972
+ *
973
+ * const hasAllProps = hasOwnAll(user, ["id", "name", "email", "phone"]);
974
+ * // Returns: false (missing 'phone' property)
975
+ * ```
976
+ *
977
+ * @example
978
+ * ```typescript
979
+ * // Validate API request payload
980
+ * function processUserUpdate(payload: unknown): User | null {
981
+ * if (typeof payload === 'object' && payload !== null) {
982
+ * const requiredFields = ['id', 'name', 'email'];
983
+ *
984
+ * if (hasOwnAll(payload as Record<string, unknown>, requiredFields)) {
985
+ * // Safe to process as we know all required fields exist
986
+ * return updateUser(payload as User);
987
+ * }
988
+ * }
989
+ *
990
+ * throw new Error('Invalid payload: missing required fields');
991
+ * }
992
+ * ```
993
+ *
994
+ * @example
995
+ * ```typescript
996
+ * // Check configuration object completeness
997
+ * interface DatabaseConfig {
998
+ * host: string;
999
+ * port: number;
1000
+ * database: string;
1001
+ * username: string;
1002
+ * password: string;
1003
+ * }
1004
+ *
1005
+ * const config = {
1006
+ * host: "localhost",
1007
+ * port: 5432,
1008
+ * database: "myapp",
1009
+ * username: "admin"
1010
+ * // password is missing
1011
+ * };
1012
+ *
1013
+ * const requiredConfigKeys = ['host', 'port', 'database', 'username', 'password'];
1014
+ * const isConfigComplete = hasOwnAll(config, requiredConfigKeys);
1015
+ * // Returns: false
1016
+ *
1017
+ * if (!isConfigComplete) {
1018
+ * throw new Error('Database configuration is incomplete');
1019
+ * }
1020
+ * ```
1021
+ *
1022
+ * @example
1023
+ * ```typescript
1024
+ * // Work with symbol properties
1025
+ * const symbolKey = Symbol('secret');
1026
+ * const obj = {
1027
+ * name: "test",
1028
+ * [symbolKey]: "hidden value"
1029
+ * };
1030
+ *
1031
+ * const hasSymbolProp = hasOwnAll(obj, ['name', symbolKey]);
1032
+ * // Returns: true
1033
+ * ```
1034
+ *
1035
+ * @remarks
1036
+ * - Uses Object.hasOwn() which is more reliable than hasOwnProperty()
1037
+ * - Only checks for own properties, not inherited properties from the prototype chain
1038
+ * - Returns false immediately if any property is missing (short-circuit evaluation)
1039
+ * - Works with string, number, and symbol property keys
1040
+ * - Returns true for an empty properties array (vacuous truth)
1041
+ * - Does not check property values, only existence
1042
+ *
1043
+ * @see {@link Object.hasOwn} The underlying method used for property checking
1044
+ */
1045
+ function hasOwnAll(obj, props) {
1046
+ return props.every(prop => Object.hasOwn(obj, prop));
1047
+ }
1048
+ /**
1049
+ * Extracts all values from a TypeScript enum, handling both string and numeric enum types correctly.
1050
+ *
1051
+ * This utility function safely retrieves the actual values from TypeScript enums, accounting for
1052
+ * the different internal representations of string and numeric enums. For numeric enums, TypeScript
1053
+ * creates reverse mappings (value → key), so this function filters out the reverse mapping entries
1054
+ * to return only the actual enum values. For string enums, it returns all values directly.
1055
+ *
1056
+ * This is particularly useful when you need to iterate over enum values, validate input against
1057
+ * enum values, create dropdown options from enums, or perform any operation that requires access
1058
+ * to the actual enum values rather than the keys.
1059
+ *
1060
+ * @template T - The enum type extending object
1061
+ * @param {T} e - The enum object to extract values from
1062
+ * @returns {T[keyof T][]} An array containing all the actual values of the enum
1063
+ *
1064
+ * @example
1065
+ * ```typescript
1066
+ * // String enum example
1067
+ * enum Color {
1068
+ * RED = "red",
1069
+ * GREEN = "green",
1070
+ * BLUE = "blue"
1071
+ * }
1072
+ *
1073
+ * const colorValues = getEnumValues(Color);
1074
+ * // Returns: ["red", "green", "blue"]
1075
+ *
1076
+ * // Use for validation
1077
+ * function isValidColor(value: string): value is Color {
1078
+ * return getEnumValues(Color).includes(value as Color);
1079
+ * }
1080
+ * ```
1081
+ *
1082
+ * @example
1083
+ * ```typescript
1084
+ * // Numeric enum example
1085
+ * enum Status {
1086
+ * PENDING, // 0
1087
+ * APPROVED, // 1
1088
+ * REJECTED // 2
1089
+ * }
1090
+ *
1091
+ * const statusValues = getEnumValues(Status);
1092
+ * // Returns: [0, 1, 2] (not ["PENDING", "APPROVED", "REJECTED", 0, 1, 2])
1093
+ *
1094
+ * // Use for creating select options
1095
+ * const statusOptions = getEnumValues(Status).map(value => ({
1096
+ * value,
1097
+ * label: Status[value] // Get the key name for display
1098
+ * }));
1099
+ * ```
1100
+ *
1101
+ * @example
1102
+ * ```typescript
1103
+ * // Mixed numeric enum example
1104
+ * enum HttpStatus {
1105
+ * OK = 200,
1106
+ * NOT_FOUND = 404,
1107
+ * SERVER_ERROR = 500
1108
+ * }
1109
+ *
1110
+ * const httpStatusValues = getEnumValues(HttpStatus);
1111
+ * // Returns: [200, 404, 500]
1112
+ *
1113
+ * // Use for status code validation
1114
+ * function isValidHttpStatus(code: number): code is HttpStatus {
1115
+ * return getEnumValues(HttpStatus).includes(code as HttpStatus);
1116
+ * }
1117
+ * ```
1118
+ *
1119
+ * @example
1120
+ * ```typescript
1121
+ * // Creating dropdown options from enum
1122
+ * enum UserRole {
1123
+ * ADMIN = "admin",
1124
+ * USER = "user",
1125
+ * MODERATOR = "moderator"
1126
+ * }
1127
+ *
1128
+ * const roleOptions = getEnumValues(UserRole).map(role => ({
1129
+ * value: role,
1130
+ * label: role.charAt(0).toUpperCase() + role.slice(1)
1131
+ * }));
1132
+ * // Returns: [
1133
+ * // { value: "admin", label: "Admin" },
1134
+ * // { value: "user", label: "User" },
1135
+ * // { value: "moderator", label: "Moderator" }
1136
+ * // ]
1137
+ * ```
1138
+ *
1139
+ * @remarks
1140
+ * - Automatically detects numeric vs string enums and handles them appropriately
1141
+ * - For numeric enums, filters out reverse mapping entries that TypeScript automatically creates
1142
+ * - For string enums, returns all values as-is since there are no reverse mappings
1143
+ * - Works with mixed numeric enums (enums with explicit numeric values)
1144
+ * - The returned array maintains the order of enum declaration
1145
+ * - Type-safe: the return type is correctly inferred as an array of the enum's value types
1146
+ *
1147
+ * @see {@link Object.values} The underlying method used to extract enum entries
1148
+ */
1149
+ function getEnumValues(e) {
1150
+ const values = Object.values(e);
1151
+ // In numeric enums, values can appear as keys (reverse mapping)
1152
+ const isNumericEnum = values.some(v => typeof v === "number");
1153
+ return isNumericEnum ? values.filter(v => typeof v !== "string") : values;
1154
+ }
1155
+ /**
1156
+ * Filters items by recursively matching nested property values.
1157
+ */
1158
+ function filterByObject(items, filters) {
1159
+ function matchesObject(item, filter) {
1160
+ if (typeof filter !== "object" || filter === null) {
1161
+ return item === filter;
1162
+ }
1163
+ if (typeof item !== "object" || item === null) {
1164
+ return false;
1165
+ }
1166
+ const itemObj = item;
1167
+ const filterObj = filter;
1168
+ for (const key in filterObj) {
1169
+ if (!matchesObject(itemObj[key], filterObj[key])) {
1170
+ return false;
1171
+ }
1172
+ }
1173
+ return true;
1174
+ }
1175
+ return items.filter(item => matchesObject(item, filters));
1176
+ }
1177
+
1178
+ // noinspection JSUnusedGlobalSymbols
1179
+ /**
1180
+ * Comprehensive mapping of file extensions to their corresponding MIME types.
1181
+ *
1182
+ * This map provides a bidirectional mapping between file extensions and MIME types,
1183
+ * enabling applications to determine the appropriate content type for files or
1184
+ * to identify the likely file extension for a given MIME type.
1185
+ *
1186
+ * The map includes entries for common file formats across various categories:
1187
+ * - Documents (PDF, DOC, DOCX, XLS, XLSX, etc.)
1188
+ * - Images (JPG, PNG, GIF, SVG, WebP, etc.)
1189
+ * - Audio (MP3, WAV, OGG, etc.)
1190
+ * - Video (MP4, WebM, AVI, etc.)
1191
+ * - Archives (ZIP, RAR, 7Z, etc.)
1192
+ * - Web resources (HTML, CSS, JS, etc.)
1193
+ * - And many more specialized formats
1194
+ *
1195
+ * @remarks
1196
+ * The map is indexed by file extension (without the dot prefix) and contains
1197
+ * the corresponding MIME type as the value. Use utility functions like
1198
+ * getMimeTypeFromExtension and getExtensionFromMimeType to perform lookups
1199
+ * rather than accessing this map directly.
1200
+ *
1201
+ * Extensions are stored in lowercase to ensure case-insensitive matching.
1202
+ *
1203
+ * @example
1204
+ * ```typescript
1205
+ * // Get MIME type for a file extension
1206
+ * const mimeType = getMimeTypeFromExtension('pdf'); // 'application/pdf'
1207
+ *
1208
+ * // Get file extension for a MIME type
1209
+ * const extension = getExtensionFromMimeType('image/jpeg'); // 'jpg'
1210
+ * ```
1211
+ */
1212
+ const mimeTypes = new Map([["123", "application/vnd.lotus-1-2-3"], ["1km", "application/vnd.1000minds.decision-model+xml"], ["3dml", "text/vnd.in3d.3dml"], ["3ds", "image/x-3ds"], ["3g2", "video/3gpp2"], ["3gp", "video/3gpp"], ["3gpp", "video/3gpp"], ["3mf", "model/3mf"], ["7z", "application/x-7z-compressed"], ["aab", "application/x-authorware-bin"], ["aac", "audio/x-aac"], ["aam", "application/x-authorware-map"], ["aas", "application/x-authorware-seg"], ["abw", "application/x-abiword"], ["ac", "application/vnd.nokia.n-gage.ac+xml"], ["acc", "application/vnd.americandynamics.acc"], ["ace", "application/x-ace-compressed"], ["acu", "application/vnd.acucobol"], ["acutc", "application/vnd.acucorp"], ["adp", "audio/adpcm"], ["adts", "audio/aac"], ["aep", "application/vnd.audiograph"], ["afm", "application/x-font-type1"], ["afp", "application/vnd.ibm.modcap"], ["age", "application/vnd.age"], ["ahead", "application/vnd.ahead.space"], ["ai", "application/postscript"], ["aif", "audio/x-aiff"], ["aifc", "audio/x-aiff"], ["aiff", "audio/x-aiff"], ["air", "application/vnd.adobe.air-application-installer-package+zip"], ["ait", "application/vnd.dvb.ait"], ["ami", "application/vnd.amiga.ami"], ["aml", "application/automationml-aml+xml"], ["amlx", "application/automationml-amlx+zip"], ["amr", "audio/amr"], ["apk", "application/vnd.android.package-archive"], ["apng", "image/apng"], ["appcache", "text/cache-manifest"], ["appinstaller", "application/appinstaller"], ["application", "application/x-ms-application"], ["appx", "application/appx"], ["appxbundle", "application/appxbundle"], ["apr", "application/vnd.lotus-approach"], ["arc", "application/x-freearc"], ["arj", "application/x-arj"], ["asc", "application/pgp-signature"], ["asf", "video/x-ms-asf"], ["asm", "text/x-asm"], ["aso", "application/vnd.accpac.simply.aso"], ["asx", "video/x-ms-asf"], ["atc", "application/vnd.acucorp"], ["atom", "application/atom+xml"], ["atomcat", "application/atomcat+xml"], ["atomdeleted", "application/atomdeleted+xml"], ["atomsvc", "application/atomsvc+xml"], ["atx", "application/vnd.antix.game-component"], ["au", "audio/basic"], ["avci", "image/avci"], ["avcs", "image/avcs"], ["avi", "video/x-msvideo"], ["avif", "image/avif"], ["aw", "application/applixware"], ["azf", "application/vnd.airzip.filesecure.azf"], ["azs", "application/vnd.airzip.filesecure.azs"], ["azv", "image/vnd.airzip.accelerator.azv"], ["azw", "application/vnd.amazon.ebook"], ["b16", "image/vnd.pco.b16"], ["bat", "application/x-msdownload"], ["bcpio", "application/x-bcpio"], ["bdf", "application/x-font-bdf"], ["bdm", "application/vnd.syncml.dm+wbxml"], ["bdoc", "application/x-bdoc"], ["bed", "application/vnd.realvnc.bed"], ["bh2", "application/vnd.fujitsu.oasysprs"], ["bin", "application/octet-stream"], ["blb", "application/x-blorb"], ["blorb", "application/x-blorb"], ["bmi", "application/vnd.bmi"], ["bmml", "application/vnd.balsamiq.bmml+xml"], ["bmp", "image/x-ms-bmp"], ["book", "application/vnd.framemaker"], ["box", "application/vnd.previewsystems.box"], ["boz", "application/x-bzip2"], ["bpk", "application/octet-stream"], ["bsp", "model/vnd.valve.source.compiled-map"], ["btf", "image/prs.btif"], ["btif", "image/prs.btif"], ["buffer", "application/octet-stream"], ["bz", "application/x-bzip"], ["bz2", "application/x-bzip2"], ["c", "text/x-c"], ["c11amc", "application/vnd.cluetrust.cartomobile-config"], ["c11amz", "application/vnd.cluetrust.cartomobile-config-pkg"], ["c4d", "application/vnd.clonk.c4group"], ["c4f", "application/vnd.clonk.c4group"], ["c4g", "application/vnd.clonk.c4group"], ["c4p", "application/vnd.clonk.c4group"], ["c4u", "application/vnd.clonk.c4group"], ["cab", "application/vnd.ms-cab-compressed"], ["caf", "audio/x-caf"], ["cap", "application/vnd.tcpdump.pcap"], ["car", "application/vnd.curl.car"], ["cat", "application/vnd.ms-pki.seccat"], ["cb7", "application/x-cbr"], ["cba", "application/x-cbr"], ["cbr", "application/x-cbr"], ["cbt", "application/x-cbr"], ["cbz", "application/x-cbr"], ["cc", "text/x-c"], ["cco", "application/x-cocoa"], ["cct", "application/x-director"], ["ccxml", "application/ccxml+xml"], ["cdbcmsg", "application/vnd.contact.cmsg"], ["cdf", "application/x-netcdf"], ["cdfx", "application/cdfx+xml"], ["cdkey", "application/vnd.mediastation.cdkey"], ["cdmia", "application/cdmi-capability"], ["cdmic", "application/cdmi-container"], ["cdmid", "application/cdmi-domain"], ["cdmio", "application/cdmi-object"], ["cdmiq", "application/cdmi-queue"], ["cdx", "chemical/x-cdx"], ["cdxml", "application/vnd.chemdraw+xml"], ["cdy", "application/vnd.cinderella"], ["cer", "application/pkix-cert"], ["cfs", "application/x-cfs-compressed"], ["cgm", "image/cgm"], ["chat", "application/x-chat"], ["chm", "application/vnd.ms-htmlhelp"], ["chrt", "application/vnd.kde.kchart"], ["cif", "chemical/x-cif"], ["cii", "application/vnd.anser-web-certificate-issue-initiation"], ["cil", "application/vnd.ms-artgalry"], ["cjs", "application/node"], ["cla", "application/vnd.claymore"], ["class", "application/java-vm"], ["cld", "model/vnd.cld"], ["clkk", "application/vnd.crick.clicker.keyboard"], ["clkp", "application/vnd.crick.clicker.palette"], ["clkt", "application/vnd.crick.clicker.template"], ["clkw", "application/vnd.crick.clicker.wordbank"], ["clkx", "application/vnd.crick.clicker"], ["clp", "application/x-msclip"], ["cmc", "application/vnd.cosmocaller"], ["cmdf", "chemical/x-cmdf"], ["cml", "chemical/x-cml"], ["cmp", "application/vnd.yellowriver-custom-menu"], ["cmx", "image/x-cmx"], ["cod", "application/vnd.rim.cod"], ["coffee", "text/coffeescript"], ["com", "application/x-msdownload"], ["conf", "text/plain"], ["cpio", "application/x-cpio"], ["cpl", "application/cpl+xml"], ["cpp", "text/x-c"], ["cpt", "application/mac-compactpro"], ["crd", "application/x-mscardfile"], ["crl", "application/pkix-crl"], ["crt", "application/x-x509-ca-cert"], ["crx", "application/x-chrome-extension"], ["cryptonote", "application/vnd.rig.cryptonote"], ["csh", "application/x-csh"], ["csl", "application/vnd.citationstyles.style+xml"], ["csml", "chemical/x-csml"], ["csp", "application/vnd.commonspace"], ["css", "text/css"], ["cst", "application/x-director"], ["csv", "text/csv"], ["cu", "application/cu-seeme"], ["curl", "text/vnd.curl"], ["cwl", "application/cwl"], ["cww", "application/prs.cww"], ["cxt", "application/x-director"], ["cxx", "text/x-c"], ["dae", "model/vnd.collada+xml"], ["daf", "application/vnd.mobius.daf"], ["dart", "application/vnd.dart"], ["dataless", "application/vnd.fdsn.seed"], ["davmount", "application/davmount+xml"], ["dbf", "application/vnd.dbf"], ["dbk", "application/docbook+xml"], ["dcr", "application/x-director"], ["dcurl", "text/vnd.curl.dcurl"], ["dd2", "application/vnd.oma.dd2+xml"], ["ddd", "application/vnd.fujixerox.ddd"], ["ddf", "application/vnd.syncml.dmddf+xml"], ["dds", "image/vnd.ms-dds"], ["deb", "application/x-debian-package"], ["def", "text/plain"], ["deploy", "application/octet-stream"], ["der", "application/x-x509-ca-cert"], ["dfac", "application/vnd.dreamfactory"], ["dgc", "application/x-dgc-compressed"], ["dib", "image/bmp"], ["dic", "text/x-c"], ["dir", "application/x-director"], ["dis", "application/vnd.mobius.dis"], ["disposition-notification", "message/disposition-notification"], ["dist", "application/octet-stream"], ["distz", "application/octet-stream"], ["djv", "image/vnd.djvu"], ["djvu", "image/vnd.djvu"], ["dll", "application/x-msdownload"], ["dmg", "application/x-apple-diskimage"], ["dmp", "application/vnd.tcpdump.pcap"], ["dms", "application/octet-stream"], ["dna", "application/vnd.dna"], ["doc", "application/msword"], ["docm", "application/vnd.ms-word.document.macroenabled.12"], ["docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"], ["dot", "application/msword"], ["dotm", "application/vnd.ms-word.template.macroenabled.12"], ["dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"], ["dp", "application/vnd.osgi.dp"], ["dpg", "application/vnd.dpgraph"], ["dpx", "image/dpx"], ["dra", "audio/vnd.dra"], ["drle", "image/dicom-rle"], ["dsc", "text/prs.lines.tag"], ["dssc", "application/dssc+der"], ["dtb", "application/x-dtbook+xml"], ["dtd", "application/xml-dtd"], ["dts", "audio/vnd.dts"], ["dtshd", "audio/vnd.dts.hd"], ["dump", "application/octet-stream"], ["dvb", "video/vnd.dvb.file"], ["dvi", "application/x-dvi"], ["dwd", "application/atsc-dwd+xml"], ["dwf", "model/vnd.dwf"], ["dwg", "image/vnd.dwg"], ["dxf", "image/vnd.dxf"], ["dxp", "application/vnd.spotfire.dxp"], ["dxr", "application/x-director"], ["ear", "application/java-archive"], ["ecelp4800", "audio/vnd.nuera.ecelp4800"], ["ecelp7470", "audio/vnd.nuera.ecelp7470"], ["ecelp9600", "audio/vnd.nuera.ecelp9600"], ["ecma", "application/ecmascript"], ["edm", "application/vnd.novadigm.edm"], ["edx", "application/vnd.novadigm.edx"], ["efif", "application/vnd.picsel"], ["ei6", "application/vnd.pg.osasli"], ["elc", "application/octet-stream"], ["emf", "image/emf"], ["eml", "message/rfc822"], ["emma", "application/emma+xml"], ["emotionml", "application/emotionml+xml"], ["emz", "application/x-msmetafile"], ["eol", "audio/vnd.digital-winds"], ["eot", "application/vnd.ms-fontobject"], ["eps", "application/postscript"], ["epub", "application/epub+zip"], ["es3", "application/vnd.eszigno3+xml"], ["esa", "application/vnd.osgi.subsystem"], ["esf", "application/vnd.epson.esf"], ["et3", "application/vnd.eszigno3+xml"], ["etx", "text/x-setext"], ["eva", "application/x-eva"], ["evy", "application/x-envoy"], ["exe", "application/x-msdownload"], ["exi", "application/exi"], ["exp", "application/express"], ["exr", "image/aces"], ["ext", "application/vnd.novadigm.ext"], ["ez", "application/andrew-inset"], ["ez2", "application/vnd.ezpix-album"], ["ez3", "application/vnd.ezpix-package"], ["f", "text/x-fortran"], ["f4v", "video/x-f4v"], ["f77", "text/x-fortran"], ["f90", "text/x-fortran"], ["fbs", "image/vnd.fastbidsheet"], ["fcdt", "application/vnd.adobe.formscentral.fcdt"], ["fcs", "application/vnd.isac.fcs"], ["fdf", "application/vnd.fdf"], ["fdt", "application/fdt+xml"], ["fe_launch", "application/vnd.denovo.fcselayout-link"], ["fg5", "application/vnd.fujitsu.oasysgp"], ["fgd", "application/x-director"], ["fh", "image/x-freehand"], ["fh4", "image/x-freehand"], ["fh5", "image/x-freehand"], ["fh7", "image/x-freehand"], ["fhc", "image/x-freehand"], ["fig", "application/x-xfig"], ["fits", "image/fits"], ["flac", "audio/x-flac"], ["fli", "video/x-fli"], ["flo", "application/vnd.micrografx.flo"], ["flv", "video/x-flv"], ["flw", "application/vnd.kde.kivio"], ["flx", "text/vnd.fmi.flexstor"], ["fly", "text/vnd.fly"], ["fm", "application/vnd.framemaker"], ["fnc", "application/vnd.frogans.fnc"], ["fo", "application/vnd.software602.filler.form+xml"], ["for", "text/x-fortran"], ["fpx", "image/vnd.fpx"], ["frame", "application/vnd.framemaker"], ["fsc", "application/vnd.fsc.weblaunch"], ["fst", "image/vnd.fst"], ["ftc", "application/vnd.fluxtime.clip"], ["fti", "application/vnd.anser-web-funds-transfer-initiation"], ["fvt", "video/vnd.fvt"], ["fxp", "application/vnd.adobe.fxp"], ["fxpl", "application/vnd.adobe.fxp"], ["fzs", "application/vnd.fuzzysheet"], ["g2w", "application/vnd.geoplan"], ["g3", "image/g3fax"], ["g3w", "application/vnd.geospace"], ["gac", "application/vnd.groove-account"], ["gam", "application/x-tads"], ["gbr", "application/rpki-ghostbusters"], ["gca", "application/x-gca-compressed"], ["gdl", "model/vnd.gdl"], ["gdoc", "application/vnd.google-apps.document"], ["ged", "text/vnd.familysearch.gedcom"], ["geo", "application/vnd.dynageo"], ["geojson", "application/geo+json"], ["gex", "application/vnd.geometry-explorer"], ["ggb", "application/vnd.geogebra.file"], ["ggt", "application/vnd.geogebra.tool"], ["ghf", "application/vnd.groove-help"], ["gif", "image/gif"], ["gim", "application/vnd.groove-identity-message"], ["glb", "model/gltf-binary"], ["gltf", "model/gltf+json"], ["gml", "application/gml+xml"], ["gmx", "application/vnd.gmx"], ["gnumeric", "application/x-gnumeric"], ["gph", "application/vnd.flographit"], ["gpx", "application/gpx+xml"], ["gqf", "application/vnd.grafeq"], ["gqs", "application/vnd.grafeq"], ["gram", "application/srgs"], ["gramps", "application/x-gramps-xml"], ["gre", "application/vnd.geometry-explorer"], ["grv", "application/vnd.groove-injector"], ["grxml", "application/srgs+xml"], ["gsf", "application/x-font-ghostscript"], ["gsheet", "application/vnd.google-apps.spreadsheet"], ["gslides", "application/vnd.google-apps.presentation"], ["gtar", "application/x-gtar"], ["gtm", "application/vnd.groove-tool-message"], ["gtw", "model/vnd.gtw"], ["gv", "text/vnd.graphviz"], ["gxf", "application/gxf"], ["gxt", "application/vnd.geonext"], ["gz", "application/gzip"], ["h", "text/x-c"], ["h261", "video/h261"], ["h263", "video/h263"], ["h264", "video/h264"], ["hal", "application/vnd.hal+xml"], ["hbci", "application/vnd.hbci"], ["hbs", "text/x-handlebars-template"], ["hdd", "application/x-virtualbox-hdd"], ["hdf", "application/x-hdf"], ["heic", "image/heic"], ["heics", "image/heic-sequence"], ["heif", "image/heif"], ["heifs", "image/heif-sequence"], ["hej2", "image/hej2k"], ["held", "application/atsc-held+xml"], ["hh", "text/x-c"], ["hjson", "application/hjson"], ["hlp", "application/winhlp"], ["hpgl", "application/vnd.hp-hpgl"], ["hpid", "application/vnd.hp-hpid"], ["hps", "application/vnd.hp-hps"], ["hqx", "application/mac-binhex40"], ["hsj2", "image/hsj2"], ["htc", "text/x-component"], ["htke", "application/vnd.kenameaapp"], ["htm", "text/html"], ["html", "text/html"], ["hvd", "application/vnd.yamaha.hv-dic"], ["hvp", "application/vnd.yamaha.hv-voice"], ["hvs", "application/vnd.yamaha.hv-script"], ["i2g", "application/vnd.intergeo"], ["icc", "application/vnd.iccprofile"], ["ice", "x-conference/x-cooltalk"], ["icm", "application/vnd.iccprofile"], ["ico", "image/x-icon"], ["ics", "text/calendar"], ["ief", "image/ief"], ["ifb", "text/calendar"], ["ifm", "application/vnd.shana.informed.formdata"], ["iges", "model/iges"], ["igl", "application/vnd.igloader"], ["igm", "application/vnd.insors.igm"], ["igs", "model/iges"], ["igx", "application/vnd.micrografx.igx"], ["iif", "application/vnd.shana.informed.interchange"], ["img", "application/octet-stream"], ["imp", "application/vnd.accpac.simply.imp"], ["ims", "application/vnd.ms-ims"], ["in", "text/plain"], ["ini", "text/plain"], ["ink", "application/inkml+xml"], ["inkml", "application/inkml+xml"], ["install", "application/x-install-instructions"], ["iota", "application/vnd.astraea-software.iota"], ["ipfix", "application/ipfix"], ["ipk", "application/vnd.shana.informed.package"], ["irm", "application/vnd.ibm.rights-management"], ["irp", "application/vnd.irepository.package+xml"], ["iso", "application/x-iso9660-image"], ["itp", "application/vnd.shana.informed.formtemplate"], ["its", "application/its+xml"], ["ivp", "application/vnd.immervision-ivp"], ["ivu", "application/vnd.immervision-ivu"], ["jad", "text/vnd.sun.j2me.app-descriptor"], ["jade", "text/jade"], ["jam", "application/vnd.jam"], ["jar", "application/java-archive"], ["jardiff", "application/x-java-archive-diff"], ["java", "text/x-java-source"], ["jhc", "image/jphc"], ["jisp", "application/vnd.jisp"], ["jls", "image/jls"], ["jlt", "application/vnd.hp-jlyt"], ["jng", "image/x-jng"], ["jnlp", "application/x-java-jnlp-file"], ["joda", "application/vnd.joost.joda-archive"], ["jp2", "image/jp2"], ["jpe", "image/jpeg"], ["jpeg", "image/jpeg"], ["jpf", "image/jpx"], ["jpg", "image/jpeg"], ["jpg2", "image/jp2"], ["jpgm", "video/jpm"], ["jpgv", "video/jpeg"], ["jph", "image/jph"], ["jpm", "video/jpm"], ["jpx", "image/jpx"], ["js", "text/javascript"], ["json", "application/json"], ["json5", "application/json5"], ["jsonld", "application/ld+json"], ["jsonml", "application/jsonml+json"], ["jsx", "text/jsx"], ["jt", "model/jt"], ["jxr", "image/jxr"], ["jxra", "image/jxra"], ["jxrs", "image/jxrs"], ["jxs", "image/jxs"], ["jxsc", "image/jxsc"], ["jxsi", "image/jxsi"], ["jxss", "image/jxss"], ["kar", "audio/midi"], ["karbon", "application/vnd.kde.karbon"], ["kdbx", "application/x-keepass2"], ["key", "application/x-iwork-keynote-sffkey"], ["kfo", "application/vnd.kde.kformula"], ["kia", "application/vnd.kidspiration"], ["kml", "application/vnd.google-earth.kml+xml"], ["kmz", "application/vnd.google-earth.kmz"], ["kne", "application/vnd.kinar"], ["knp", "application/vnd.kinar"], ["kon", "application/vnd.kde.kontour"], ["kpr", "application/vnd.kde.kpresenter"], ["kpt", "application/vnd.kde.kpresenter"], ["kpxx", "application/vnd.ds-keypoint"], ["ksp", "application/vnd.kde.kspread"], ["ktr", "application/vnd.kahootz"], ["ktx", "image/ktx"], ["ktx2", "image/ktx2"], ["ktz", "application/vnd.kahootz"], ["kwd", "application/vnd.kde.kword"], ["kwt", "application/vnd.kde.kword"], ["lasxml", "application/vnd.las.las+xml"], ["latex", "application/x-latex"], ["lbd", "application/vnd.llamagraphics.life-balance.desktop"], ["lbe", "application/vnd.llamagraphics.life-balance.exchange+xml"], ["les", "application/vnd.hhe.lesson-player"], ["less", "text/less"], ["lgr", "application/lgr+xml"], ["lha", "application/x-lzh-compressed"], ["link66", "application/vnd.route66.link66+xml"], ["list", "text/plain"], ["list3820", "application/vnd.ibm.modcap"], ["listafp", "application/vnd.ibm.modcap"], ["litcoffee", "text/coffeescript"], ["lnk", "application/x-ms-shortcut"], ["log", "text/plain"], ["lostxml", "application/lost+xml"], ["lrf", "application/octet-stream"], ["lrm", "application/vnd.ms-lrm"], ["ltf", "application/vnd.frogans.ltf"], ["lua", "text/x-lua"], ["luac", "application/x-lua-bytecode"], ["lvp", "audio/vnd.lucent.voice"], ["lwp", "application/vnd.lotus-wordpro"], ["lzh", "application/x-lzh-compressed"], ["m13", "application/x-msmediaview"], ["m14", "application/x-msmediaview"], ["m1v", "video/mpeg"], ["m21", "application/mp21"], ["m2a", "audio/mpeg"], ["m2v", "video/mpeg"], ["m3a", "audio/mpeg"], ["m3u", "audio/x-mpegurl"], ["m3u8", "application/vnd.apple.mpegurl"], ["m4a", "audio/x-m4a"], ["m4p", "application/mp4"], ["m4s", "video/iso.segment"], ["m4u", "video/vnd.mpegurl"], ["m4v", "video/x-m4v"], ["ma", "application/mathematica"], ["mads", "application/mads+xml"], ["maei", "application/mmt-aei+xml"], ["mag", "application/vnd.ecowin.chart"], ["maker", "application/vnd.framemaker"], ["man", "text/troff"], ["manifest", "text/cache-manifest"], ["map", "application/json"], ["mar", "application/octet-stream"], ["markdown", "text/markdown"], ["mathml", "application/mathml+xml"], ["mb", "application/mathematica"], ["mbk", "application/vnd.mobius.mbk"], ["mbox", "application/mbox"], ["mc1", "application/vnd.medcalcdata"], ["mcd", "application/vnd.mcd"], ["mcurl", "text/vnd.curl.mcurl"], ["md", "text/markdown"], ["mdb", "application/x-msaccess"], ["mdi", "image/vnd.ms-modi"], ["mdx", "text/mdx"], ["me", "text/troff"], ["mesh", "model/mesh"], ["meta4", "application/metalink4+xml"], ["metalink", "application/metalink+xml"], ["mets", "application/mets+xml"], ["mfm", "application/vnd.mfmp"], ["mft", "application/rpki-manifest"], ["mgp", "application/vnd.osgeo.mapguide.package"], ["mgz", "application/vnd.proteus.magazine"], ["mid", "audio/midi"], ["midi", "audio/midi"], ["mie", "application/x-mie"], ["mif", "application/vnd.mif"], ["mime", "message/rfc822"], ["mj2", "video/mj2"], ["mjp2", "video/mj2"], ["mjs", "text/javascript"], ["mk3d", "video/x-matroska"], ["mka", "audio/x-matroska"], ["mkd", "text/x-markdown"], ["mks", "video/x-matroska"], ["mkv", "video/x-matroska"], ["mlp", "application/vnd.dolby.mlp"], ["mmd", "application/vnd.chipnuts.karaoke-mmd"], ["mmf", "application/vnd.smaf"], ["mml", "text/mathml"], ["mmr", "image/vnd.fujixerox.edmics-mmr"], ["mng", "video/x-mng"], ["mny", "application/x-msmoney"], ["mobi", "application/x-mobipocket-ebook"], ["mods", "application/mods+xml"], ["mov", "video/quicktime"], ["movie", "video/x-sgi-movie"], ["mp2", "audio/mpeg"], ["mp21", "application/mp21"], ["mp2a", "audio/mpeg"], ["mp3", "audio/mpeg"], ["mp4", "video/mp4"], ["mp4a", "audio/mp4"], ["mp4s", "application/mp4"], ["mp4v", "video/mp4"], ["mpc", "application/vnd.mophun.certificate"], ["mpd", "application/dash+xml"], ["mpe", "video/mpeg"], ["mpeg", "video/mpeg"], ["mpf", "application/media-policy-dataset+xml"], ["mpg", "video/mpeg"], ["mpg4", "video/mp4"], ["mpga", "audio/mpeg"], ["mpkg", "application/vnd.apple.installer+xml"], ["mpm", "application/vnd.blueice.multipass"], ["mpn", "application/vnd.mophun.application"], ["mpp", "application/vnd.ms-project"], ["mpt", "application/vnd.ms-project"], ["mpy", "application/vnd.ibm.minipay"], ["mqy", "application/vnd.mobius.mqy"], ["mrc", "application/marc"], ["mrcx", "application/marcxml+xml"], ["ms", "text/troff"], ["mscml", "application/mediaservercontrol+xml"], ["mseed", "application/vnd.fdsn.mseed"], ["mseq", "application/vnd.mseq"], ["msf", "application/vnd.epson.msf"], ["msg", "application/vnd.ms-outlook"], ["msh", "model/mesh"], ["msi", "application/x-msdownload"], ["msix", "application/msix"], ["msixbundle", "application/msixbundle"], ["msl", "application/vnd.mobius.msl"], ["msm", "application/octet-stream"], ["msp", "application/octet-stream"], ["msty", "application/vnd.muvee.style"], ["mtl", "model/mtl"], ["mts", "model/vnd.mts"], ["mus", "application/vnd.musician"], ["musd", "application/mmt-usd+xml"], ["musicxml", "application/vnd.recordare.musicxml+xml"], ["mvb", "application/x-msmediaview"], ["mvt", "application/vnd.mapbox-vector-tile"], ["mwf", "application/vnd.mfer"], ["mxf", "application/mxf"], ["mxl", "application/vnd.recordare.musicxml"], ["mxmf", "audio/mobile-xmf"], ["mxml", "application/xv+xml"], ["mxs", "application/vnd.triscape.mxs"], ["mxu", "video/vnd.mpegurl"], ["n-gage", "application/vnd.nokia.n-gage.symbian.install"], ["n3", "text/n3"], ["nb", "application/mathematica"], ["nbp", "application/vnd.wolfram.player"], ["nc", "application/x-netcdf"], ["ncx", "application/x-dtbncx+xml"], ["nfo", "text/x-nfo"], ["ngdat", "application/vnd.nokia.n-gage.data"], ["nitf", "application/vnd.nitf"], ["nlu", "application/vnd.neurolanguage.nlu"], ["nml", "application/vnd.enliven"], ["nnd", "application/vnd.noblenet-directory"], ["nns", "application/vnd.noblenet-sealer"], ["nnw", "application/vnd.noblenet-web"], ["npx", "image/vnd.net-fpx"], ["nq", "application/n-quads"], ["nsc", "application/x-conference"], ["nsf", "application/vnd.lotus-notes"], ["nt", "application/n-triples"], ["ntf", "application/vnd.nitf"], ["numbers", "application/x-iwork-numbers-sffnumbers"], ["nzb", "application/x-nzb"], ["oa2", "application/vnd.fujitsu.oasys2"], ["oa3", "application/vnd.fujitsu.oasys3"], ["oas", "application/vnd.fujitsu.oasys"], ["obd", "application/x-msbinder"], ["obgx", "application/vnd.openblox.game+xml"], ["obj", "model/obj"], ["oda", "application/oda"], ["odb", "application/vnd.oasis.opendocument.database"], ["odc", "application/vnd.oasis.opendocument.chart"], ["odf", "application/vnd.oasis.opendocument.formula"], ["odft", "application/vnd.oasis.opendocument.formula-template"], ["odg", "application/vnd.oasis.opendocument.graphics"], ["odi", "application/vnd.oasis.opendocument.image"], ["odm", "application/vnd.oasis.opendocument.text-master"], ["odp", "application/vnd.oasis.opendocument.presentation"], ["ods", "application/vnd.oasis.opendocument.spreadsheet"], ["odt", "application/vnd.oasis.opendocument.text"], ["oga", "audio/ogg"], ["ogex", "model/vnd.opengex"], ["ogg", "audio/ogg"], ["ogv", "video/ogg"], ["ogx", "application/ogg"], ["omdoc", "application/omdoc+xml"], ["onepkg", "application/onenote"], ["onetmp", "application/onenote"], ["onetoc", "application/onenote"], ["onetoc2", "application/onenote"], ["opf", "application/oebps-package+xml"], ["opml", "text/x-opml"], ["oprc", "application/vnd.palm"], ["opus", "audio/ogg"], ["org", "text/x-org"], ["osf", "application/vnd.yamaha.openscoreformat"], ["osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml"], ["osm", "application/vnd.openstreetmap.data+xml"], ["otc", "application/vnd.oasis.opendocument.chart-template"], ["otf", "font/otf"], ["otg", "application/vnd.oasis.opendocument.graphics-template"], ["oth", "application/vnd.oasis.opendocument.text-web"], ["oti", "application/vnd.oasis.opendocument.image-template"], ["otp", "application/vnd.oasis.opendocument.presentation-template"], ["ots", "application/vnd.oasis.opendocument.spreadsheet-template"], ["ott", "application/vnd.oasis.opendocument.text-template"], ["ova", "application/x-virtualbox-ova"], ["ovf", "application/x-virtualbox-ovf"], ["owl", "application/rdf+xml"], ["oxps", "application/oxps"], ["oxt", "application/vnd.openofficeorg.extension"], ["p", "text/x-pascal"], ["p10", "application/pkcs10"], ["p12", "application/x-pkcs12"], ["p7b", "application/x-pkcs7-certificates"], ["p7c", "application/pkcs7-mime"], ["p7m", "application/pkcs7-mime"], ["p7r", "application/x-pkcs7-certreqresp"], ["p7s", "application/pkcs7-signature"], ["p8", "application/pkcs8"], ["pac", "application/x-ns-proxy-autoconfig"], ["pages", "application/x-iwork-pages-sffpages"], ["pas", "text/x-pascal"], ["paw", "application/vnd.pawaafile"], ["pbd", "application/vnd.powerbuilder6"], ["pbm", "image/x-portable-bitmap"], ["pcap", "application/vnd.tcpdump.pcap"], ["pcf", "application/x-font-pcf"], ["pcl", "application/vnd.hp-pcl"], ["pclxl", "application/vnd.hp-pclxl"], ["pct", "image/x-pict"], ["pcurl", "application/vnd.curl.pcurl"], ["pcx", "image/x-pcx"], ["pdb", "application/x-pilot"], ["pde", "text/x-processing"], ["pem", "application/x-x509-ca-cert"], ["pfa", "application/x-font-type1"], ["pfb", "application/x-font-type1"], ["pfm", "application/x-font-type1"], ["pfr", "application/font-tdpfr"], ["pfx", "application/x-pkcs12"], ["pgm", "image/x-portable-graymap"], ["pgn", "application/x-chess-pgn"], ["pgp", "application/pgp-encrypted"], ["php", "application/x-httpd-php"], ["pic", "image/x-pict"], ["pkg", "application/octet-stream"], ["pki", "application/pkixcmp"], ["pkipath", "application/pkix-pkipath"], ["pkpass", "application/vnd.apple.pkpass"], ["pl", "application/x-perl"], ["plb", "application/vnd.3gpp.pic-bw-large"], ["plc", "application/vnd.mobius.plc"], ["plf", "application/vnd.pocketlearn"], ["pls", "application/pls+xml"], ["pm", "application/x-perl"], ["pml", "application/vnd.ctc-posml"], ["png", "image/png"], ["pnm", "image/x-portable-anymap"], ["portpkg", "application/vnd.macports.portpkg"], ["pot", "application/vnd.ms-powerpoint"], ["potm", "application/vnd.ms-powerpoint.template.macroenabled.12"], ["potx", "application/vnd.openxmlformats-officedocument.presentationml.template"], ["ppam", "application/vnd.ms-powerpoint.addin.macroenabled.12"], ["ppd", "application/vnd.cups-ppd"], ["ppm", "image/x-portable-pixmap"], ["pps", "application/vnd.ms-powerpoint"], ["ppsm", "application/vnd.ms-powerpoint.slideshow.macroenabled.12"], ["ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"], ["ppt", "application/vnd.ms-powerpoint"], ["pptm", "application/vnd.ms-powerpoint.presentation.macroenabled.12"], ["pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"], ["pqa", "application/vnd.palm"], ["prc", "model/prc"], ["pre", "application/vnd.lotus-freelance"], ["prf", "application/pics-rules"], ["provx", "application/provenance+xml"], ["ps", "application/postscript"], ["psb", "application/vnd.3gpp.pic-bw-small"], ["psd", "image/vnd.adobe.photoshop"], ["psf", "application/x-font-linux-psf"], ["pskcxml", "application/pskc+xml"], ["pti", "image/prs.pti"], ["ptid", "application/vnd.pvi.ptid1"], ["pub", "application/x-mspublisher"], ["pvb", "application/vnd.3gpp.pic-bw-var"], ["pwn", "application/vnd.3m.post-it-notes"], ["pya", "audio/vnd.ms-playready.media.pya"], ["pyo", "model/vnd.pytha.pyox"], ["pyox", "model/vnd.pytha.pyox"], ["pyv", "video/vnd.ms-playready.media.pyv"], ["qam", "application/vnd.epson.quickanime"], ["qbo", "application/vnd.intu.qbo"], ["qfx", "application/vnd.intu.qfx"], ["qps", "application/vnd.publishare-delta-tree"], ["qt", "video/quicktime"], ["qwd", "application/vnd.quark.quarkxpress"], ["qwt", "application/vnd.quark.quarkxpress"], ["qxb", "application/vnd.quark.quarkxpress"], ["qxd", "application/vnd.quark.quarkxpress"], ["qxl", "application/vnd.quark.quarkxpress"], ["qxt", "application/vnd.quark.quarkxpress"], ["ra", "audio/x-realaudio"], ["ram", "audio/x-pn-realaudio"], ["raml", "application/raml+yaml"], ["rapd", "application/route-apd+xml"], ["rar", "application/x-rar-compressed"], ["ras", "image/x-cmu-raster"], ["rcprofile", "application/vnd.ipunplugged.rcprofile"], ["rdf", "application/rdf+xml"], ["rdz", "application/vnd.data-vision.rdz"], ["relo", "application/p2p-overlay+xml"], ["rep", "application/vnd.businessobjects"], ["res", "application/x-dtbresource+xml"], ["rgb", "image/x-rgb"], ["rif", "application/reginfo+xml"], ["rip", "audio/vnd.rip"], ["ris", "application/x-research-info-systems"], ["rl", "application/resource-lists+xml"], ["rlc", "image/vnd.fujixerox.edmics-rlc"], ["rld", "application/resource-lists-diff+xml"], ["rm", "application/vnd.rn-realmedia"], ["rmi", "audio/midi"], ["rmp", "audio/x-pn-realaudio-plugin"], ["rms", "application/vnd.jcp.javame.midlet-rms"], ["rmvb", "application/vnd.rn-realmedia-vbr"], ["rnc", "application/relax-ng-compact-syntax"], ["rng", "application/xml"], ["roa", "application/rpki-roa"], ["roff", "text/troff"], ["rp9", "application/vnd.cloanto.rp9"], ["rpm", "application/x-redhat-package-manager"], ["rpss", "application/vnd.nokia.radio-presets"], ["rpst", "application/vnd.nokia.radio-preset"], ["rq", "application/sparql-query"], ["rs", "application/rls-services+xml"], ["rsat", "application/atsc-rsat+xml"], ["rsd", "application/rsd+xml"], ["rsheet", "application/urc-ressheet+xml"], ["rss", "application/rss+xml"], ["rtf", "text/rtf"], ["rtx", "text/richtext"], ["run", "application/x-makeself"], ["rusd", "application/route-usd+xml"], ["s", "text/x-asm"], ["s3m", "audio/s3m"], ["saf", "application/vnd.yamaha.smaf-audio"], ["sass", "text/x-sass"], ["sbml", "application/sbml+xml"], ["sc", "application/vnd.ibm.secure-container"], ["scd", "application/x-msschedule"], ["scm", "application/vnd.lotus-screencam"], ["scq", "application/scvp-cv-request"], ["scs", "application/scvp-cv-response"], ["scss", "text/x-scss"], ["scurl", "text/vnd.curl.scurl"], ["sda", "application/vnd.stardivision.draw"], ["sdc", "application/vnd.stardivision.calc"], ["sdd", "application/vnd.stardivision.impress"], ["sdkd", "application/vnd.solent.sdkm+xml"], ["sdkm", "application/vnd.solent.sdkm+xml"], ["sdp", "application/sdp"], ["sdw", "application/vnd.stardivision.writer"], ["sea", "application/x-sea"], ["see", "application/vnd.seemail"], ["seed", "application/vnd.fdsn.seed"], ["sema", "application/vnd.sema"], ["semd", "application/vnd.semd"], ["semf", "application/vnd.semf"], ["senmlx", "application/senml+xml"], ["sensmlx", "application/sensml+xml"], ["ser", "application/java-serialized-object"], ["setpay", "application/set-payment-initiation"], ["setreg", "application/set-registration-initiation"], ["sfd-hdstx", "application/vnd.hydrostatix.sof-data"], ["sfs", "application/vnd.spotfire.sfs"], ["sfv", "text/x-sfv"], ["sgi", "image/sgi"], ["sgl", "application/vnd.stardivision.writer-global"], ["sgm", "text/sgml"], ["sgml", "text/sgml"], ["sh", "application/x-sh"], ["shar", "application/x-shar"], ["shex", "text/shex"], ["shf", "application/shf+xml"], ["shtml", "text/html"], ["sid", "image/x-mrsid-image"], ["sieve", "application/sieve"], ["sig", "application/pgp-signature"], ["sil", "audio/silk"], ["silo", "model/mesh"], ["sis", "application/vnd.symbian.install"], ["sisx", "application/vnd.symbian.install"], ["sit", "application/x-stuffit"], ["sitx", "application/x-stuffitx"], ["siv", "application/sieve"], ["skd", "application/vnd.koan"], ["skm", "application/vnd.koan"], ["skp", "application/vnd.koan"], ["skt", "application/vnd.koan"], ["sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12"], ["sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"], ["slim", "text/slim"], ["slm", "text/slim"], ["sls", "application/route-s-tsid+xml"], ["slt", "application/vnd.epson.salt"], ["sm", "application/vnd.stepmania.stepchart"], ["smf", "application/vnd.stardivision.math"], ["smi", "application/smil+xml"], ["smil", "application/smil+xml"], ["smv", "video/x-smv"], ["smzip", "application/vnd.stepmania.package"], ["snd", "audio/basic"], ["snf", "application/x-font-snf"], ["so", "application/octet-stream"], ["spc", "application/x-pkcs7-certificates"], ["spdx", "text/spdx"], ["spf", "application/vnd.yamaha.smaf-phrase"], ["spl", "application/x-futuresplash"], ["spot", "text/vnd.in3d.spot"], ["spp", "application/scvp-vp-response"], ["spq", "application/scvp-vp-request"], ["spx", "audio/ogg"], ["sql", "application/x-sql"], ["src", "application/x-wais-source"], ["srt", "application/x-subrip"], ["sru", "application/sru+xml"], ["srx", "application/sparql-results+xml"], ["ssdl", "application/ssdl+xml"], ["sse", "application/vnd.kodak-descriptor"], ["ssf", "application/vnd.epson.ssf"], ["ssml", "application/ssml+xml"], ["st", "application/vnd.sailingtracker.track"], ["stc", "application/vnd.sun.xml.calc.template"], ["std", "application/vnd.sun.xml.draw.template"], ["stf", "application/vnd.wt.stf"], ["sti", "application/vnd.sun.xml.impress.template"], ["stk", "application/hyperstudio"], ["stl", "model/stl"], ["stpx", "model/step+xml"], ["stpxz", "model/step-xml+zip"], ["stpz", "model/step+zip"], ["str", "application/vnd.pg.format"], ["stw", "application/vnd.sun.xml.writer.template"], ["styl", "text/stylus"], ["stylus", "text/stylus"], ["sub", "text/vnd.dvb.subtitle"], ["sus", "application/vnd.sus-calendar"], ["susp", "application/vnd.sus-calendar"], ["sv4cpio", "application/x-sv4cpio"], ["sv4crc", "application/x-sv4crc"], ["svc", "application/vnd.dvb.service"], ["svd", "application/vnd.svd"], ["svg", "image/svg+xml"], ["svgz", "image/svg+xml"], ["swa", "application/x-director"], ["swf", "application/x-shockwave-flash"], ["swi", "application/vnd.aristanetworks.swi"], ["swidtag", "application/swid+xml"], ["sxc", "application/vnd.sun.xml.calc"], ["sxd", "application/vnd.sun.xml.draw"], ["sxg", "application/vnd.sun.xml.writer.global"], ["sxi", "application/vnd.sun.xml.impress"], ["sxm", "application/vnd.sun.xml.math"], ["sxw", "application/vnd.sun.xml.writer"], ["t", "text/troff"], ["t3", "application/x-t3vm-image"], ["t38", "image/t38"], ["taglet", "application/vnd.mynfc"], ["tao", "application/vnd.tao.intent-module-archive"], ["tap", "image/vnd.tencent.tap"], ["tar", "application/x-tar"], ["tcap", "application/vnd.3gpp2.tcap"], ["tcl", "application/x-tcl"], ["td", "application/urc-targetdesc+xml"], ["teacher", "application/vnd.smart.teacher"], ["tei", "application/tei+xml"], ["teicorpus", "application/tei+xml"], ["tex", "application/x-tex"], ["texi", "application/x-texinfo"], ["texinfo", "application/x-texinfo"], ["text", "text/plain"], ["tfi", "application/thraud+xml"], ["tfm", "application/x-tex-tfm"], ["tfx", "image/tiff-fx"], ["tga", "image/x-tga"], ["thmx", "application/vnd.ms-officetheme"], ["tif", "image/tiff"], ["tiff", "image/tiff"], ["tk", "application/x-tcl"], ["tmo", "application/vnd.tmobile-livetv"], ["toml", "application/toml"], ["torrent", "application/x-bittorrent"], ["tpl", "application/vnd.groove-tool-template"], ["tpt", "application/vnd.trid.tpt"], ["tr", "text/troff"], ["tra", "application/vnd.trueapp"], ["trig", "application/trig"], ["trm", "application/x-msterminal"], ["ts", "video/mp2t"], ["tsd", "application/timestamped-data"], ["tsv", "text/tab-separated-values"], ["ttc", "font/collection"], ["ttf", "font/ttf"], ["ttl", "text/turtle"], ["ttml", "application/ttml+xml"], ["twd", "application/vnd.simtech-mindmapper"], ["twds", "application/vnd.simtech-mindmapper"], ["txd", "application/vnd.genomatix.tuxedo"], ["txf", "application/vnd.mobius.txf"], ["txt", "text/plain"], ["u32", "application/x-authorware-bin"], ["u3d", "model/u3d"], ["u8dsn", "message/global-delivery-status"], ["u8hdr", "message/global-headers"], ["u8mdn", "message/global-disposition-notification"], ["u8msg", "message/global"], ["ubj", "application/ubjson"], ["udeb", "application/x-debian-package"], ["ufd", "application/vnd.ufdl"], ["ufdl", "application/vnd.ufdl"], ["ulx", "application/x-glulx"], ["umj", "application/vnd.umajin"], ["unityweb", "application/vnd.unity"], ["uo", "application/vnd.uoml+xml"], ["uoml", "application/vnd.uoml+xml"], ["uri", "text/uri-list"], ["uris", "text/uri-list"], ["urls", "text/uri-list"], ["usda", "model/vnd.usda"], ["usdz", "model/vnd.usdz+zip"], ["ustar", "application/x-ustar"], ["utz", "application/vnd.uiq.theme"], ["uu", "text/x-uuencode"], ["uva", "audio/vnd.dece.audio"], ["uvd", "application/vnd.dece.data"], ["uvf", "application/vnd.dece.data"], ["uvg", "image/vnd.dece.graphic"], ["uvh", "video/vnd.dece.hd"], ["uvi", "image/vnd.dece.graphic"], ["uvm", "video/vnd.dece.mobile"], ["uvp", "video/vnd.dece.pd"], ["uvs", "video/vnd.dece.sd"], ["uvt", "application/vnd.dece.ttml+xml"], ["uvu", "video/vnd.uvvu.mp4"], ["uvv", "video/vnd.dece.video"], ["uvva", "audio/vnd.dece.audio"], ["uvvd", "application/vnd.dece.data"], ["uvvf", "application/vnd.dece.data"], ["uvvg", "image/vnd.dece.graphic"], ["uvvh", "video/vnd.dece.hd"], ["uvvi", "image/vnd.dece.graphic"], ["uvvm", "video/vnd.dece.mobile"], ["uvvp", "video/vnd.dece.pd"], ["uvvs", "video/vnd.dece.sd"], ["uvvt", "application/vnd.dece.ttml+xml"], ["uvvu", "video/vnd.uvvu.mp4"], ["uvvv", "video/vnd.dece.video"], ["uvvx", "application/vnd.dece.unspecified"], ["uvvz", "application/vnd.dece.zip"], ["uvx", "application/vnd.dece.unspecified"], ["uvz", "application/vnd.dece.zip"], ["vbox", "application/x-virtualbox-vbox"], ["vbox-extpack", "application/x-virtualbox-vbox-extpack"], ["vcard", "text/vcard"], ["vcd", "application/x-cdlink"], ["vcf", "text/x-vcard"], ["vcg", "application/vnd.groove-vcard"], ["vcs", "text/x-vcalendar"], ["vcx", "application/vnd.vcx"], ["vdi", "application/x-virtualbox-vdi"], ["vds", "model/vnd.sap.vds"], ["vhd", "application/x-virtualbox-vhd"], ["vis", "application/vnd.visionary"], ["viv", "video/vnd.vivo"], ["vmdk", "application/x-virtualbox-vmdk"], ["vob", "video/x-ms-vob"], ["vor", "application/vnd.stardivision.writer"], ["vox", "application/x-authorware-bin"], ["vrml", "model/vrml"], ["vsd", "application/vnd.visio"], ["vsf", "application/vnd.vsf"], ["vss", "application/vnd.visio"], ["vst", "application/vnd.visio"], ["vsw", "application/vnd.visio"], ["vtf", "image/vnd.valve.source.texture"], ["vtt", "text/vtt"], ["vtu", "model/vnd.vtu"], ["vxml", "application/voicexml+xml"], ["w3d", "application/x-director"], ["wad", "application/x-doom"], ["wadl", "application/vnd.sun.wadl+xml"], ["war", "application/java-archive"], ["wasm", "application/wasm"], ["wav", "audio/x-wav"], ["wax", "audio/x-ms-wax"], ["wbmp", "image/vnd.wap.wbmp"], ["wbs", "application/vnd.criticaltools.wbs+xml"], ["wbxml", "application/vnd.wap.wbxml"], ["wcm", "application/vnd.ms-works"], ["wdb", "application/vnd.ms-works"], ["wdp", "image/vnd.ms-photo"], ["weba", "audio/webm"], ["webapp", "application/x-web-app-manifest+json"], ["webm", "video/webm"], ["webmanifest", "application/manifest+json"], ["webp", "image/webp"], ["wg", "application/vnd.pmi.widget"], ["wgsl", "text/wgsl"], ["wgt", "application/widget"], ["wif", "application/watcherinfo+xml"], ["wks", "application/vnd.ms-works"], ["wm", "video/x-ms-wm"], ["wma", "audio/x-ms-wma"], ["wmd", "application/x-ms-wmd"], ["wmf", "image/wmf"], ["wml", "text/vnd.wap.wml"], ["wmlc", "application/vnd.wap.wmlc"], ["wmls", "text/vnd.wap.wmlscript"], ["wmlsc", "application/vnd.wap.wmlscriptc"], ["wmv", "video/x-ms-wmv"], ["wmx", "video/x-ms-wmx"], ["wmz", "application/x-msmetafile"], ["woff", "font/woff"], ["woff2", "font/woff2"], ["wpd", "application/vnd.wordperfect"], ["wpl", "application/vnd.ms-wpl"], ["wps", "application/vnd.ms-works"], ["wqd", "application/vnd.wqd"], ["wri", "application/x-mswrite"], ["wrl", "model/vrml"], ["wsc", "message/vnd.wfa.wsc"], ["wsdl", "application/wsdl+xml"], ["wspolicy", "application/wspolicy+xml"], ["wtb", "application/vnd.webturbo"], ["wvx", "video/x-ms-wvx"], ["x32", "application/x-authorware-bin"], ["x3d", "model/x3d+xml"], ["x3db", "model/x3d+fastinfoset"], ["x3dbz", "model/x3d+binary"], ["x3dv", "model/x3d-vrml"], ["x3dvz", "model/x3d+vrml"], ["x3dz", "model/x3d+xml"], ["x_b", "model/vnd.parasolid.transmit.binary"], ["x_t", "model/vnd.parasolid.transmit.text"], ["xaml", "application/xaml+xml"], ["xap", "application/x-silverlight-app"], ["xar", "application/vnd.xara"], ["xav", "application/xcap-att+xml"], ["xbap", "application/x-ms-xbap"], ["xbd", "application/vnd.fujixerox.docuworks.binder"], ["xbm", "image/x-xbitmap"], ["xca", "application/xcap-caps+xml"], ["xcs", "application/calendar+xml"], ["xdf", "application/xcap-diff+xml"], ["xdm", "application/vnd.syncml.dm+xml"], ["xdp", "application/vnd.adobe.xdp+xml"], ["xdssc", "application/dssc+xml"], ["xdw", "application/vnd.fujixerox.docuworks"], ["xel", "application/xcap-el+xml"], ["xenc", "application/xenc+xml"], ["xer", "application/patch-ops-error+xml"], ["xfdf", "application/xfdf"], ["xfdl", "application/vnd.xfdl"], ["xht", "application/xhtml+xml"], ["xhtm", "application/vnd.pwg-xhtml-print+xml"], ["xhtml", "application/xhtml+xml"], ["xhvml", "application/xv+xml"], ["xif", "image/vnd.xiff"], ["xla", "application/vnd.ms-excel"], ["xlam", "application/vnd.ms-excel.addin.macroenabled.12"], ["xlc", "application/vnd.ms-excel"], ["xlf", "application/xliff+xml"], ["xlm", "application/vnd.ms-excel"], ["xls", "application/vnd.ms-excel"], ["xlsb", "application/vnd.ms-excel.sheet.binary.macroenabled.12"], ["xlsm", "application/vnd.ms-excel.sheet.macroenabled.12"], ["xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"], ["xlt", "application/vnd.ms-excel"], ["xltm", "application/vnd.ms-excel.template.macroenabled.12"], ["xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"], ["xlw", "application/vnd.ms-excel"], ["xm", "audio/xm"], ["xml", "text/xml"], ["xns", "application/xcap-ns+xml"], ["xo", "application/vnd.olpc-sugar"], ["xop", "application/xop+xml"], ["xpi", "application/x-xpinstall"], ["xpl", "application/xproc+xml"], ["xpm", "image/x-xpixmap"], ["xpr", "application/vnd.is-xpr"], ["xps", "application/vnd.ms-xpsdocument"], ["xpw", "application/vnd.intercon.formnet"], ["xpx", "application/vnd.intercon.formnet"], ["xsd", "application/xml"], ["xsf", "application/prs.xsf+xml"], ["xsl", "application/xslt+xml"], ["xslt", "application/xslt+xml"], ["xsm", "application/vnd.syncml+xml"], ["xspf", "application/xspf+xml"], ["xul", "application/vnd.mozilla.xul+xml"], ["xvm", "application/xv+xml"], ["xvml", "application/xv+xml"], ["xwd", "image/x-xwindowdump"], ["xyz", "chemical/x-xyz"], ["xz", "application/x-xz"], ["yaml", "text/yaml"], ["yang", "application/yang"], ["yin", "application/yin+xml"], ["yml", "text/yaml"], ["ymp", "text/x-suse-ymp"], ["z1", "application/x-zmachine"], ["z2", "application/x-zmachine"], ["z3", "application/x-zmachine"], ["z4", "application/x-zmachine"], ["z5", "application/x-zmachine"], ["z6", "application/x-zmachine"], ["z7", "application/x-zmachine"], ["z8", "application/x-zmachine"], ["zaz", "application/vnd.zzazz.deck+xml"], ["zip", "application/zip"], ["zir", "application/vnd.zul"], ["zirz", "application/vnd.zul"], ["zmm", "application/vnd.handheld-entertainment+xml"]]);
1213
+ /**
1214
+ * Get the file extension of the given mime type.
1215
+ * @param {string} mimeType - Mime type.
1216
+ * @param {Map<string, string>} [allowedMimeTypes] - Allowed mime types. If not provided, the default mime types map will be used.
1217
+ * @returns {string | undefined} File extension or undefined if the mime type is not found.
1218
+ *
1219
+ * @example
1220
+ * ```TypeScript
1221
+ * // Using the default mime types map
1222
+ * const extension = getFileExt('application/pdf');
1223
+ * // Output: 'pdf'
1224
+ * ```
1225
+ *
1226
+ * @example
1227
+ * ```TypeScript
1228
+ * // Using a custom mime types map
1229
+ * const customMimeTypes = new Map([
1230
+ * ['jpg', 'image/jpeg'],
1231
+ * ['png', 'image/png']
1232
+ * ]);
1233
+ * const extension = getFileExt('image/jpeg', customMimeTypes);
1234
+ * // Output: 'jpg'
1235
+ * ```
1236
+ */
1237
+ const getFileExt = (mimeType, allowedMimeTypes) => {
1238
+ return getMapKey(allowedMimeTypes ?? mimeTypes, mimeType);
1239
+ };
1240
+ /**
1241
+ * Get file size in human-readable format.
1242
+ * @param {number} size - File size in bytes.
1243
+ * @param {boolean} [round] - Whether to round the size to whole numbers. If true, decimals will be removed.
1244
+ * @returns {string} File size in human-readable format (B, KB, MB, or GB).
1245
+ *
1246
+ * @example
1247
+ * ```TypeScript
1248
+ * // Get file size with decimals
1249
+ * const readableSize = getFileSize(1536);
1250
+ * // Output: '1.50 KB'
1251
+ * ```
1252
+ *
1253
+ * @example
1254
+ * ```TypeScript
1255
+ * // Get rounded file size
1256
+ * const roundedSize = getFileSize(1536, true);
1257
+ * // Output: '2 KB'
1258
+ * ```
1259
+ *
1260
+ * @example
1261
+ * ```TypeScript
1262
+ * // Get size for larger files
1263
+ * const largeFileSize = getFileSize(1073741824);
1264
+ * // Output: '1.00 GB'
1265
+ * ```
1266
+ */
1267
+ const getFileSize = (size, round) => {
1268
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1269
+ if (size < 1024) {
1270
+ return `${Math.round(size)} B`;
1271
+ }
1272
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1273
+ if (size < 1024 * 1024) {
1274
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1275
+ return `${(size / 1024).toFixed(round ? 0 : 2)} KB`;
1276
+ }
1277
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1278
+ if (size < 1024 * 1024 * 1024) {
1279
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1280
+ return `${(size / 1024 / 1024).toFixed(round ? 0 : 2)} MB`;
1281
+ }
1282
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1283
+ return `${(size / 1024 / 1024 / 1024).toFixed(round ? 0 : 2)} GB`;
1284
+ };
1285
+
1286
+ /**
1287
+ * Base for hexadecimal number conversion
1288
+ *
1289
+ * This constant defines the radix (base) used for hexadecimal number conversion.
1290
+ * The value 16 represents the standard hexadecimal numbering system (0-9, A-F).
1291
+ * It's used in toString() and parseInt() operations involving hex values.
1292
+ *
1293
+ * @example
1294
+ * ```typescript
1295
+ * // Converting a number to hexadecimal string
1296
+ * const hexString = number.toString(HEX_RADIX);
1297
+ *
1298
+ * // Converting a hexadecimal string to a number
1299
+ * const number = parseInt(hexString, HEX_RADIX);
1300
+ * ```
1301
+ *
1302
+ * @see {@link HEX_PADDING_LENGTH} Related constant for hex string formatting
1303
+ * @see {@link HEX_PADDING_CHAR} Related constant for hex string padding
1304
+ */
1305
+ const HEX_RADIX = 16;
1306
+ /**
1307
+ * Minimum length for padded hexadecimal strings
1308
+ *
1309
+ * This constant defines the minimum length for hexadecimal strings after padding.
1310
+ * It ensures that hex values have a consistent length (e.g., "0A" instead of "A").
1311
+ * The value 2 ensures that single-digit hex values are padded with a leading zero.
1312
+ *
1313
+ * @example
1314
+ * ```typescript
1315
+ * // Padding a hex string to ensure consistent length
1316
+ * const paddedHex = hexString.padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR);
1317
+ *
1318
+ * // Full example with conversion and padding
1319
+ * const byteValue = 10;
1320
+ * const hexByte = byteValue.toString(HEX_RADIX).padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR);
1321
+ * // hexByte = "0A"
1322
+ * ```
1323
+ *
1324
+ * @see {@link HEX_RADIX} Related constant for hex conversion
1325
+ * @see {@link HEX_PADDING_CHAR} Related constant for the padding character
1326
+ */
1327
+ const HEX_PADDING_LENGTH = 2;
1328
+ /**
1329
+ * Character used for padding hexadecimal strings
1330
+ *
1331
+ * This constant defines the character used to pad hexadecimal strings to reach
1332
+ * the minimum length defined by HEX_PADDING_LENGTH. The value "0" is the standard
1333
+ * padding character for hexadecimal values.
1334
+ *
1335
+ * @example
1336
+ * ```typescript
1337
+ * // Padding a hex string with zeros
1338
+ * const paddedHex = hexString.padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR);
1339
+ *
1340
+ * // Example with a single-digit hex value
1341
+ * const hexValue = "F";
1342
+ * const paddedHex = hexValue.padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR);
1343
+ * // paddedHex = "0F"
1344
+ * ```
1345
+ *
1346
+ * @see {@link HEX_RADIX} Related constant for hex conversion
1347
+ * @see {@link HEX_PADDING_LENGTH} Related constant for the minimum length
1348
+ */
1349
+ const HEX_PADDING_CHAR = "0";
1350
+ /**
1351
+ * Number of characters to remove in string truncation operations
1352
+ *
1353
+ * This constant defines the default number of characters to remove when
1354
+ * truncating strings in various utility functions. The value 2 is commonly
1355
+ * used when removing the last two characters of a string (e.g., removing
1356
+ * a trailing comma and space).
1357
+ *
1358
+ * @example
1359
+ * ```typescript
1360
+ * // Removing trailing characters from a string
1361
+ * const items = ["apple", "banana", "cherry"];
1362
+ * let result = "";
1363
+ *
1364
+ * for (const item of items) {
1365
+ * result += item + ", ";
1366
+ * }
1367
+ *
1368
+ * // Remove trailing comma and space
1369
+ * if (result.length > 0) {
1370
+ * result = result.slice(0, -CHARACTERS_TO_REMOVE);
1371
+ * }
1372
+ * // result = "apple, banana, cherry"
1373
+ * ```
1374
+ */
1375
+ const CHARACTERS_TO_REMOVE = 2;
1376
+ /**
1377
+ * Default maximum line length for word wrapping
1378
+ *
1379
+ * This constant defines the default maximum length of a line when performing
1380
+ * word wrapping operations. The value 80 is a common standard for line length
1381
+ * in many text formats and terminal displays.
1382
+ *
1383
+ * @example
1384
+ * ```typescript
1385
+ * // Wrapping a long text to the default line length
1386
+ * function wrapText(text: string, maxLength = DEFAULT_LINE_LENGTH, breakChar = DEFAULT_BREAK_CHAR): string {
1387
+ * // Word wrapping implementation
1388
+ * const result = [];
1389
+ * let line = "";
1390
+ *
1391
+ * for (const word of text.split(" ")) {
1392
+ * if ((line + word).length > maxLength) {
1393
+ * result.push(line);
1394
+ * line = word;
1395
+ * } else {
1396
+ * line += (line ? " " : "") + word;
1397
+ * }
1398
+ * }
1399
+ *
1400
+ * if (line) {
1401
+ * result.push(line);
1402
+ * }
1403
+ *
1404
+ * return result.join(breakChar);
1405
+ * }
1406
+ * ```
1407
+ *
1408
+ * @see {@link DEFAULT_BREAK_CHAR} Related constant for line break character
1409
+ */
1410
+ const DEFAULT_LINE_LENGTH = 80;
1411
+ /**
1412
+ * Default line break character for word wrapping
1413
+ *
1414
+ * This constant defines the default character or string used to separate lines
1415
+ * when performing word wrapping operations. The value "\n" represents a standard
1416
+ * line feed character used for line breaks in most text formats.
1417
+ *
1418
+ * @example
1419
+ * ```typescript
1420
+ * // Using the default break character in word wrapping
1421
+ * const wrappedText = longText.match(new RegExp(`.{1,${DEFAULT_LINE_LENGTH}}(\\s|$)`, 'g'))
1422
+ * ?.join(DEFAULT_BREAK_CHAR) || longText;
1423
+ * ```
1424
+ *
1425
+ * @see {@link DEFAULT_LINE_LENGTH} Related constant for maximum line length
1426
+ */
1427
+ const DEFAULT_BREAK_CHAR = "\n";
1428
+ /**
1429
+ * Default context length for text excerpts
1430
+ *
1431
+ * This constant defines the default number of characters to include before and after
1432
+ * a search term when generating text excerpts or snippets. The value 40 provides
1433
+ * enough context to understand the meaning while keeping the excerpt concise.
1434
+ *
1435
+ * @example
1436
+ * ```typescript
1437
+ * // Generating an excerpt around a search term
1438
+ * function generateExcerpt(text: string, searchTerm: string): string {
1439
+ * const index = text.toLowerCase().indexOf(searchTerm.toLowerCase());
1440
+ * if (index === -1) return text.substring(0, DEFAULT_CONTEXT_LENGTH * 2) + DEFAULT_ELLIPSIS;
1441
+ *
1442
+ * const start = Math.max(0, index - DEFAULT_CONTEXT_LENGTH);
1443
+ * const end = Math.min(text.length, index + searchTerm.length + DEFAULT_CONTEXT_LENGTH);
1444
+ *
1445
+ * let excerpt = text.substring(start, end);
1446
+ * if (start > 0) excerpt = DEFAULT_ELLIPSIS + excerpt;
1447
+ * if (end < text.length) excerpt = excerpt + DEFAULT_ELLIPSIS;
1448
+ *
1449
+ * return excerpt;
1450
+ * }
1451
+ * ```
1452
+ *
1453
+ * @see {@link DEFAULT_ELLIPSIS} Related constant for indicating truncated text
1454
+ * @see {@link CONTEXT_MULTIPLIER} Related constant for adjusting context length
1455
+ */
1456
+ const DEFAULT_CONTEXT_LENGTH = 40;
1457
+ /**
1458
+ * Default ellipsis string for indicating truncated text
1459
+ *
1460
+ * This constant defines the default string used to indicate that text has been
1461
+ * truncated or omitted in excerpts and summaries. The value "..." is the standard
1462
+ * ellipsis used to represent omitted content.
1463
+ *
1464
+ * @example
1465
+ * ```typescript
1466
+ * // Truncating a long string with ellipsis
1467
+ * function truncate(text: string, maxLength: number): string {
1468
+ * if (text.length <= maxLength) return text;
1469
+ * return text.substring(0, maxLength - DEFAULT_ELLIPSIS.length) + DEFAULT_ELLIPSIS;
1470
+ * }
1471
+ *
1472
+ * const longText = "This is a very long text that needs to be truncated";
1473
+ * const truncated = truncate(longText, 20);
1474
+ * // truncated = "This is a very lo..."
1475
+ * ```
1476
+ *
1477
+ * @see {@link DEFAULT_CONTEXT_LENGTH} Related constant for excerpt generation
1478
+ */
1479
+ const DEFAULT_ELLIPSIS = "...";
1480
+ /**
1481
+ * Multiplier for adjusting context length in excerpts
1482
+ *
1483
+ * This constant defines a multiplier used to adjust the context length when
1484
+ * generating excerpts under different conditions. The value 2 is typically
1485
+ * used to double the context length for certain scenarios, such as when no
1486
+ * search term is found.
1487
+ *
1488
+ * @example
1489
+ * ```typescript
1490
+ * // Using the multiplier to adjust context length
1491
+ * function getContextLength(hasSearchTerm: boolean): number {
1492
+ * return hasSearchTerm
1493
+ * ? DEFAULT_CONTEXT_LENGTH
1494
+ * : DEFAULT_CONTEXT_LENGTH * CONTEXT_MULTIPLIER;
1495
+ * }
1496
+ *
1497
+ * // Getting the first part of a text when no search term is found
1498
+ * function getFirstPart(text: string): string {
1499
+ * const length = DEFAULT_CONTEXT_LENGTH * CONTEXT_MULTIPLIER;
1500
+ * return text.length > length
1501
+ * ? text.substring(0, length) + DEFAULT_ELLIPSIS
1502
+ * : text;
1503
+ * }
1504
+ * ```
1505
+ *
1506
+ * @see {@link DEFAULT_CONTEXT_LENGTH} Related constant for base context length
1507
+ */
1508
+ const CONTEXT_MULTIPLIER = 2;
1509
+
1510
+ /**
1511
+ * Comprehensive rules for English word inflection (singular/plural transformations)
1512
+ *
1513
+ * This module provides a structured collection of rules for transforming English words
1514
+ * between singular and plural forms. The rules are organized into categories based on
1515
+ * linguistic patterns and exceptions, ensuring accurate transformations for various
1516
+ * word types.
1517
+ *
1518
+ * Key features:
1519
+ * - Invariant words that are identical in singular and plural forms
1520
+ * - Irregular plural forms that don't follow standard patterns
1521
+ * - Latin and Greek origin words with their specific plural formations
1522
+ * - Pattern-based rules for common English word endings
1523
+ * - Default fallback rules for standard English pluralization
1524
+ *
1525
+ * The rules are applied in a specific priority order, from most specific to most general,
1526
+ * to ensure correct handling of special cases. Each rule consists of a regular expression
1527
+ * pattern and a replacement string or function.
1528
+ *
1529
+ * @example
1530
+ * ```typescript
1531
+ * // Using the rules with the plural() function
1532
+ * import { EnglishInflectionRules } from '@hichchi/utils/constants';
1533
+ * import { InflectionRule } from '@hichchi/utils/interfaces';
1534
+ *
1535
+ * function plural(word: string): string {
1536
+ * // Combine all rules in priority order
1537
+ * const rules: InflectionRule[] = [
1538
+ * ...EnglishInflectionRules.INVARIANT,
1539
+ * ...EnglishInflectionRules.IRREGULAR.toPlural,
1540
+ * ...EnglishInflectionRules.LATIN_GREEK.toPlural,
1541
+ * ...EnglishInflectionRules.PATTERN_RULES.toPlural,
1542
+ * ...EnglishInflectionRules.DEFAULT.toPlural,
1543
+ * ];
1544
+ *
1545
+ * // Apply the first matching rule
1546
+ * for (const rule of rules) {
1547
+ * if (rule.regex.test(word)) {
1548
+ * return word.replace(rule.regex, rule.replacement as string);
1549
+ * }
1550
+ * }
1551
+ *
1552
+ * return word;
1553
+ * }
1554
+ * ```
1555
+ *
1556
+ * @see {@link plural} Function that uses these rules to convert words to plural form
1557
+ * @see {@link singular} Function that uses these rules to convert words to singular form
1558
+ * @see {@link InflectionRuleCategories} Interface defining the structure of the rules
1559
+ * @see {@link InflectionRule} Interface for individual transformation rules
1560
+ */
1561
+ const EnglishInflectionRules = {
1562
+ /**
1563
+ * Words that are the same in singular and plural form
1564
+ *
1565
+ * This category contains rules for words that don't change between singular and plural forms.
1566
+ * These invariant words (also called "zero plurals") have identical singular and plural forms
1567
+ * in English. The rule simply returns the word unchanged.
1568
+ *
1569
+ * Common examples include:
1570
+ * - Animal names: "fish", "deer", "sheep"
1571
+ * - Scientific terms: "species", "series"
1572
+ * - Uncountable nouns: "equipment"
1573
+ *
1574
+ * @example
1575
+ * ```typescript
1576
+ * // These words remain unchanged in plural form
1577
+ * plural("fish"); // "fish"
1578
+ * plural("deer"); // "deer"
1579
+ * plural("sheep"); // "sheep"
1580
+ * plural("species"); // "species"
1581
+ * plural("equipment"); // "equipment"
1582
+ * ```
1583
+ *
1584
+ * @see {@link plural} Function that applies these rules
1585
+ * @see {@link singular} Function that applies these rules
1586
+ */
1587
+ INVARIANT: [{
1588
+ regex: /^(fish|deer|sheep|species|series|equipment)$/i,
1589
+ replacement: "$1"
1590
+ }],
1591
+ /**
1592
+ * Irregular plural forms
1593
+ *
1594
+ * This category contains rules for words with irregular plural forms that don't
1595
+ * follow standard English pluralization patterns. These words have unique
1596
+ * transformations between singular and plural forms that must be handled as
1597
+ * special cases.
1598
+ *
1599
+ * The category is divided into two rule sets:
1600
+ * - toPlural: Rules for converting singular forms to their irregular plural forms
1601
+ * - toSingular: Rules for converting irregular plural forms back to singular
1602
+ *
1603
+ * These rules handle common irregular English plurals like "child/children",
1604
+ * "man/men", "tooth/teeth", etc. Many of these irregular forms have historical
1605
+ * origins in Old English and other Germanic languages.
1606
+ *
1607
+ * @example
1608
+ * ```typescript
1609
+ * // Singular to plural transformations
1610
+ * plural("child"); // "children"
1611
+ * plural("man"); // "men"
1612
+ * plural("tooth"); // "teeth"
1613
+ * plural("person"); // "people"
1614
+ *
1615
+ * // Plural to singular transformations
1616
+ * singular("children"); // "child"
1617
+ * singular("men"); // "man"
1618
+ * singular("teeth"); // "tooth"
1619
+ * singular("people"); // "person"
1620
+ * ```
1621
+ *
1622
+ * @see {@link plural} Function that applies these rules for pluralization
1623
+ * @see {@link singular} Function that applies these rules for singularization
1624
+ */
1625
+ IRREGULAR: {
1626
+ // Singular to plural transformations
1627
+ toPlural: [{
1628
+ regex: /^child$/i,
1629
+ replacement: "children"
1630
+ }, {
1631
+ regex: /^man$/i,
1632
+ replacement: "men"
1633
+ }, {
1634
+ regex: /^woman$/i,
1635
+ replacement: "women"
1636
+ }, {
1637
+ regex: /^tooth$/i,
1638
+ replacement: "teeth"
1639
+ }, {
1640
+ regex: /^foot$/i,
1641
+ replacement: "feet"
1642
+ }, {
1643
+ regex: /^goose$/i,
1644
+ replacement: "geese"
1645
+ }, {
1646
+ regex: /^mouse$/i,
1647
+ replacement: "mice"
1648
+ }, {
1649
+ regex: /^person$/i,
1650
+ replacement: "people"
1651
+ }, {
1652
+ regex: /^ox$/i,
1653
+ replacement: "oxen"
1654
+ }],
1655
+ // Plural to singular transformations
1656
+ toSingular: [{
1657
+ regex: /children$/i,
1658
+ replacement: "child"
1659
+ }, {
1660
+ regex: /men$/i,
1661
+ replacement: "man"
1662
+ }, {
1663
+ regex: /women$/i,
1664
+ replacement: "woman"
1665
+ }, {
1666
+ regex: /teeth$/i,
1667
+ replacement: "tooth"
1668
+ }, {
1669
+ regex: /feet$/i,
1670
+ replacement: "foot"
1671
+ }, {
1672
+ regex: /geese$/i,
1673
+ replacement: "goose"
1674
+ }, {
1675
+ regex: /mice$/i,
1676
+ replacement: "mouse"
1677
+ }, {
1678
+ regex: /people$/i,
1679
+ replacement: "person"
1680
+ }, {
1681
+ regex: /oxen$/i,
1682
+ replacement: "ox"
1683
+ }]
1684
+ },
1685
+ /**
1686
+ * Latin/Greek origin words
1687
+ *
1688
+ * This category contains rules for words borrowed from Latin and Greek that
1689
+ * retain their original pluralization patterns. These words follow different
1690
+ * rules than native English words and often have distinctive ending changes.
1691
+ *
1692
+ * The category is divided into two rule sets:
1693
+ * - toPlural: Rules for converting Latin/Greek singular forms to their plural forms
1694
+ * - toSingular: Rules for converting Latin/Greek plural forms back to singular
1695
+ *
1696
+ * Common patterns include:
1697
+ * - "-us" → "-i" (cactus/cacti, fungus/fungi)
1698
+ * - "-is" → "-es" (analysis/analyses, thesis/theses)
1699
+ * - "-on"/"-um" → "-a" (criterion/criteria, datum/data)
1700
+ * - "-ex"/"-ix" → "-ices" (index/indices, matrix/matrices)
1701
+ *
1702
+ * These words are often found in academic, scientific, and technical contexts.
1703
+ * Note that some of these words have alternative anglicized plural forms that
1704
+ * are also acceptable in modern English (e.g., "cactuses" alongside "cacti").
1705
+ *
1706
+ * @example
1707
+ * ```typescript
1708
+ * // Singular to plural transformations
1709
+ * plural("cactus"); // "cacti"
1710
+ * plural("analysis"); // "analyses"
1711
+ * plural("criterion"); // "criteria"
1712
+ * plural("index"); // "indices"
1713
+ * plural("matrix"); // "matrices"
1714
+ *
1715
+ * // Plural to singular transformations
1716
+ * singular("cacti"); // "cactus"
1717
+ * singular("analyses"); // "analysis"
1718
+ * singular("criteria"); // "criterion"
1719
+ * singular("indices"); // "index"
1720
+ * singular("matrices"); // "matrix"
1721
+ * ```
1722
+ *
1723
+ * @see {@link plural} Function that applies these rules for pluralization
1724
+ * @see {@link singular} Function that applies these rules for singularization
1725
+ */
1726
+ LATIN_GREEK: {
1727
+ // Singular to plural transformations
1728
+ toPlural: [{
1729
+ regex: /^cactus$/i,
1730
+ replacement: "cacti"
1731
+ }, {
1732
+ regex: /^focus$/i,
1733
+ replacement: "foci"
1734
+ }, {
1735
+ regex: /^fungus$/i,
1736
+ replacement: "fungi"
1737
+ }, {
1738
+ regex: /^nucleus$/i,
1739
+ replacement: "nuclei"
1740
+ }, {
1741
+ regex: /^syllabus$/i,
1742
+ replacement: "syllabi"
1743
+ }, {
1744
+ regex: /^analysis$/i,
1745
+ replacement: "analyses"
1746
+ }, {
1747
+ regex: /^diagnosis$/i,
1748
+ replacement: "diagnoses"
1749
+ }, {
1750
+ regex: /^thesis$/i,
1751
+ replacement: "theses"
1752
+ }, {
1753
+ regex: /^criterion$/i,
1754
+ replacement: "criteria"
1755
+ }, {
1756
+ regex: /^datum$/i,
1757
+ replacement: "data"
1758
+ }, {
1759
+ regex: /^phenomenon$/i,
1760
+ replacement: "phenomena"
1761
+ }, {
1762
+ regex: /^index$/i,
1763
+ replacement: "indices"
1764
+ }, {
1765
+ regex: /^matrix$/i,
1766
+ replacement: "matrices"
1767
+ }, {
1768
+ regex: /^vertex$/i,
1769
+ replacement: "vertices"
1770
+ }, {
1771
+ regex: /^appendix$/i,
1772
+ replacement: "appendices"
1773
+ }],
1774
+ // Plural to singular transformations
1775
+ toSingular: [{
1776
+ regex: /matrices$/i,
1777
+ replacement: "matrix"
1778
+ }, {
1779
+ regex: /indices$/i,
1780
+ replacement: "index"
1781
+ }, {
1782
+ regex: /vertices$/i,
1783
+ replacement: "vertex"
1784
+ }, {
1785
+ regex: /appendices$/i,
1786
+ replacement: "appendix"
1787
+ }, {
1788
+ regex: /cacti$/i,
1789
+ replacement: "cactus"
1790
+ }, {
1791
+ regex: /foci$/i,
1792
+ replacement: "focus"
1793
+ }, {
1794
+ regex: /fungi$/i,
1795
+ replacement: "fungus"
1796
+ }, {
1797
+ regex: /nuclei$/i,
1798
+ replacement: "nucleus"
1799
+ }, {
1800
+ regex: /syllabi$/i,
1801
+ replacement: "syllabus"
1802
+ }, {
1803
+ regex: /analyses$/i,
1804
+ replacement: "analysis"
1805
+ }, {
1806
+ regex: /diagnoses$/i,
1807
+ replacement: "diagnosis"
1808
+ }, {
1809
+ regex: /theses$/i,
1810
+ replacement: "thesis"
1811
+ }, {
1812
+ regex: /criteria$/i,
1813
+ replacement: "criterion"
1814
+ }, {
1815
+ regex: /data$/i,
1816
+ replacement: "datum"
1817
+ }, {
1818
+ regex: /phenomena$/i,
1819
+ replacement: "phenomenon"
1820
+ }]
1821
+ },
1822
+ /**
1823
+ * Words with specific ending patterns
1824
+ *
1825
+ * This category contains rules for words that follow regular patterns based on
1826
+ * their endings. These patterns represent the most common rules for English
1827
+ * pluralization and singularization, covering a wide range of everyday words.
1828
+ *
1829
+ * The category is divided into two rule sets:
1830
+ * - toPlural: Rules for converting singular forms to plural based on word endings
1831
+ * - toSingular: Rules for converting plural forms back to singular
1832
+ *
1833
+ * Key pattern groups include:
1834
+ *
1835
+ * 1. Words ending in -f or -fe:
1836
+ * - "knife" → "knives", "life" → "lives", "wolf" → "wolves"
1837
+ *
1838
+ * 2. Words ending in -y:
1839
+ * - After consonant: "city" → "cities", "baby" → "babies"
1840
+ * - After vowel: "boy" → "boys", "key" → "keys"
1841
+ *
1842
+ * 3. Words ending in -o:
1843
+ * - Some specific words: "hero" → "heroes", "potato" → "potatoes"
1844
+ * - After consonant: "echo" → "echoes", "tomato" → "tomatoes"
1845
+ *
1846
+ * 4. Words ending in -s, -ss, -sh, -ch, -x, -z:
1847
+ * - "bus" → "buses", "box" → "boxes", "dish" → "dishes"
1848
+ *
1849
+ * These patterns cover the majority of regular English pluralization rules
1850
+ * and are applied after checking for invariant words, irregular forms, and
1851
+ * Latin/Greek words.
1852
+ *
1853
+ * @example
1854
+ * ```typescript
1855
+ * // Words ending in -f or -fe
1856
+ * plural("knife"); // "knives"
1857
+ * plural("life"); // "lives"
1858
+ * plural("wolf"); // "wolves"
1859
+ *
1860
+ * // Words ending in -y
1861
+ * plural("city"); // "cities"
1862
+ * plural("boy"); // "boys"
1863
+ *
1864
+ * // Words ending in -o
1865
+ * plural("hero"); // "heroes"
1866
+ * plural("photo"); // "photos"
1867
+ *
1868
+ * // Words ending in -s, -ss, -sh, -ch, -x, -z
1869
+ * plural("bus"); // "buses"
1870
+ * plural("box"); // "boxes"
1871
+ * plural("dish"); // "dishes"
1872
+ *
1873
+ * // Plural to singular
1874
+ * singular("knives"); // "knife"
1875
+ * singular("cities"); // "city"
1876
+ * singular("boxes"); // "box"
1877
+ * ```
1878
+ *
1879
+ * @see {@link plural} Function that applies these rules for pluralization
1880
+ * @see {@link singular} Function that applies these rules for singularization
1881
+ * @see {@link CHARACTERS_TO_REMOVE} Constant used in some replacement functions
1882
+ */
1883
+ PATTERN_RULES: {
1884
+ // Singular to plural transformations
1885
+ toPlural: [
1886
+ // Words ending in -f or -fe
1887
+ {
1888
+ regex: /^((?:wi|li)fe)$/i,
1889
+ replacement: "$1ves"
1890
+ }, {
1891
+ regex: /(fe)$/i,
1892
+ replacement: "ves"
1893
+ }, {
1894
+ regex: /([^f])f$/i,
1895
+ replacement: "$1ves"
1896
+ },
1897
+ // Words ending in -y
1898
+ {
1899
+ regex: /([^aeiou])y$/i,
1900
+ replacement: "$1ies"
1901
+ }, {
1902
+ regex: /([aeiou]y)$/i,
1903
+ replacement: "$1s"
1904
+ },
1905
+ // Words ending in -o
1906
+ {
1907
+ regex: /(hero|potato|tomato|torpedo|veto)$/i,
1908
+ replacement: "$1es"
1909
+ }, {
1910
+ regex: /([^aeiou])o$/i,
1911
+ replacement: "$1oes"
1912
+ },
1913
+ // Words ending in -is
1914
+ {
1915
+ regex: /(is)$/i,
1916
+ replacement: "es"
1917
+ },
1918
+ // Words ending in -us
1919
+ {
1920
+ regex: /(us)$/i,
1921
+ replacement: "uses"
1922
+ },
1923
+ // Words ending in -s, -ss, -sh, -ch, -x, -z
1924
+ {
1925
+ regex: /(s|ss|sh|ch|x|z)$/i,
1926
+ replacement: "$1es"
1927
+ }],
1928
+ // Plural to singular transformations
1929
+ toSingular: [
1930
+ // Special spelling changes
1931
+ {
1932
+ regex: /knives$/i,
1933
+ replacement: "knife"
1934
+ }, {
1935
+ regex: /lives$/i,
1936
+ replacement: "life"
1937
+ }, {
1938
+ regex: /shelves$/i,
1939
+ replacement: "shelf"
1940
+ },
1941
+ // Common word patterns
1942
+ {
1943
+ regex: /(buses|dishes|matches)$/i,
1944
+ replacement: match => match.slice(0, -CHARACTERS_TO_REMOVE)
1945
+ }, {
1946
+ regex: /([^aeiouy]|qu)ies$/i,
1947
+ replacement: "$1y"
1948
+ }, {
1949
+ regex: /(x|ch|ss|sh)es$/i,
1950
+ replacement: "$1"
1951
+ }, {
1952
+ regex: /oes$/i,
1953
+ replacement: "o"
1954
+ }, {
1955
+ regex: /ves$/i,
1956
+ replacement: "f"
1957
+ }]
1958
+ },
1959
+ /**
1960
+ * Default rules for standard transformations
1961
+ *
1962
+ * This category contains fallback rules that are applied when no other rules match.
1963
+ * These represent the most basic and common English pluralization pattern: adding
1964
+ * an "s" to form plurals and removing an "s" to form singulars.
1965
+ *
1966
+ * The category is divided into two rule sets:
1967
+ * - toPlural: Adds "s" to the end of any word to form its plural
1968
+ * - toSingular: Removes a trailing "s" from any word to form its singular
1969
+ *
1970
+ * These rules are applied as a last resort after all other more specific rules
1971
+ * have been checked. They handle the majority of regular English words that don't
1972
+ * fall into any of the special categories or patterns.
1973
+ *
1974
+ * While simple, these rules can sometimes produce incorrect results for words that
1975
+ * should follow more specific patterns but weren't included in the other rule sets.
1976
+ * They're designed as a reasonable fallback rather than a comprehensive solution.
1977
+ *
1978
+ * @example
1979
+ * ```typescript
1980
+ * // Singular to plural with default rule
1981
+ * plural("book"); // "books"
1982
+ * plural("car"); // "cars"
1983
+ * plural("apple"); // "apples"
1984
+ *
1985
+ * // Plural to singular with default rule
1986
+ * singular("books"); // "book"
1987
+ * singular("cars"); // "car"
1988
+ * singular("apples"); // "apple"
1989
+ * ```
1990
+ *
1991
+ * @see {@link plural} Function that applies these rules for pluralization
1992
+ * @see {@link singular} Function that applies these rules for singularization
1993
+ */
1994
+ DEFAULT: {
1995
+ toPlural: [{
1996
+ regex: /(.*)$/i,
1997
+ replacement: "$1s"
1998
+ }],
1999
+ toSingular: [{
2000
+ regex: /s$/i,
2001
+ replacement: ""
2002
+ }]
2003
+ }
2004
+ };
2005
+
2006
+ // noinspection JSUnusedGlobalSymbols
2007
+ function breakToWords(str, format) {
2008
+ if (!str) {
2009
+ return format ? "" : [];
2010
+ }
2011
+ try {
2012
+ const words = str.match(/[A-Z]{2,}(?=[A-Z][a-z]+\d*|\b)|[A-Z]?[a-z]+\d*|[A-Z]+|\d+/g)?.map(s => s.toLowerCase()) ?? [];
2013
+ return format ? words.map(format).join(" ") : words;
2014
+ } catch {
2015
+ return format ? "" : [];
2016
+ }
2017
+ }
2018
+ /**
2019
+ * Converts a string to lowercase with safe handling of undefined or null values.
2020
+ *
2021
+ * This utility function provides a safe way to convert strings to lowercase, automatically
2022
+ * handling edge cases where the input might be undefined, null, or empty. Unlike the native
2023
+ * String.prototype.toLowerCase() method, this function won't throw an error when called
2024
+ * with undefined or null values, instead returning an empty string.
2025
+ *
2026
+ * This is particularly useful when working with user input, API responses, or any scenario
2027
+ * where string values might be optional or potentially undefined. It's commonly used for
2028
+ * case-insensitive comparisons, search functionality, or normalizing text data.
2029
+ *
2030
+ * @param {string} [str] - The string to convert to lowercase. Can be undefined or null.
2031
+ * @returns {string} The lowercase version of the input string, or an empty string if input is falsy
2032
+ *
2033
+ * @example
2034
+ * ```typescript
2035
+ * // Basic string conversion
2036
+ * const result1 = toLowerCase("Hello World");
2037
+ * // Returns: "hello world"
2038
+ *
2039
+ * const result2 = toLowerCase("JAVASCRIPT");
2040
+ * // Returns: "javascript"
2041
+ * ```
2042
+ *
2043
+ * @example
2044
+ * ```typescript
2045
+ * // Safe handling of undefined/null values
2046
+ * const result1 = toLowerCase(undefined);
2047
+ * // Returns: ""
2048
+ *
2049
+ * const result2 = toLowerCase(null);
2050
+ * // Returns: ""
2051
+ *
2052
+ * const result3 = toLowerCase("");
2053
+ * // Returns: ""
2054
+ * ```
2055
+ *
2056
+ * @example
2057
+ * ```typescript
2058
+ * // Use in case-insensitive search
2059
+ * function searchUsers(users: User[], searchTerm: string): User[] {
2060
+ * const normalizedSearch = toLowerCase(searchTerm);
2061
+ * return users.filter(user =>
2062
+ * toLowerCase(user.name).includes(normalizedSearch) ||
2063
+ * toLowerCase(user.email).includes(normalizedSearch)
2064
+ * );
2065
+ * }
2066
+ * ```
2067
+ *
2068
+ * @example
2069
+ * ```typescript
2070
+ * // Use in form validation
2071
+ * function validateEmail(email?: string): boolean {
2072
+ * const normalizedEmail = toLowerCase(email);
2073
+ * const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2074
+ * return emailRegex.test(normalizedEmail);
2075
+ * }
2076
+ * ```
2077
+ *
2078
+ * @remarks
2079
+ * - Returns an empty string for any falsy input (undefined, null, empty string)
2080
+ * - Uses the native String.prototype.toLowerCase() method for actual conversion
2081
+ * - Does not modify the original string (strings are immutable in JavaScript)
2082
+ * - Handles all Unicode characters correctly, following locale-independent rules
2083
+ * - More robust than direct .toLowerCase() calls when input might be undefined
2084
+ *
2085
+ * @see {@link toUpperCase} Function for converting strings to uppercase
2086
+ * @see {@link toLowerCaseBreak} Function for converting to lowercase and breaking into words
2087
+ */
2088
+ function toLowerCase(str) {
2089
+ if (!str) {
2090
+ return "";
2091
+ }
2092
+ return str.toLowerCase();
2093
+ }
2094
+ /**
2095
+ * Converts a string to lowercase and breaks it into words, joining them with a specified separator.
2096
+ *
2097
+ * This utility function combines word breaking and case conversion in a single operation.
2098
+ * It first breaks the input string into individual words using intelligent pattern recognition
2099
+ * (handling camelCase, snake_case, kebab-case, etc.), converts each word to lowercase,
2100
+ * and then joins them with the specified separator or a space by default.
2101
+ *
2102
+ * This is particularly useful for converting various naming conventions to a consistent
2103
+ * lowercase format, creating readable labels from variable names, generating search-friendly
2104
+ * text, or preparing strings for further processing where consistent word separation is needed.
2105
+ *
2106
+ * @param {string} [str] - The string to convert and break into words. Can be undefined or null.
2107
+ * @param {string} [join=" "] - The separator to use when joining the words. Defaults to a single space.
2108
+ * @returns {string} A lowercase string with words separated by the specified join character,
2109
+ * or an empty string if input is falsy
2110
+ *
2111
+ * @example
2112
+ * ```typescript
2113
+ * // Basic camelCase conversion
2114
+ * const result1 = toLowerCaseBreak("HelloWorld");
2115
+ * // Returns: "hello world"
2116
+ *
2117
+ * const result2 = toLowerCaseBreak("getUserProfile");
2118
+ * // Returns: "get user profile"
2119
+ * ```
2120
+ *
2121
+ * @example
2122
+ * ```typescript
2123
+ * // Custom separators
2124
+ * const result1 = toLowerCaseBreak("HelloWorld", "-");
2125
+ * // Returns: "hello-world"
2126
+ *
2127
+ * const result2 = toLowerCaseBreak("APIResponseHandler", "_");
2128
+ * // Returns: "api_response_handler"
2129
+ *
2130
+ * const result3 = toLowerCaseBreak("userAccountSettings", " | ");
2131
+ * // Returns: "user | account | settings"
2132
+ * ```
2133
+ *
2134
+ * @example
2135
+ * ```typescript
2136
+ * // Various input formats
2137
+ * const result1 = toLowerCaseBreak("snake_case_example");
2138
+ * // Returns: "snake case example"
2139
+ *
2140
+ * const result2 = toLowerCaseBreak("kebab-case-example");
2141
+ * // Returns: "kebab case example"
2142
+ *
2143
+ * const result3 = toLowerCaseBreak("CONSTANT_VALUE");
2144
+ * // Returns: "constant value"
2145
+ * ```
2146
+ *
2147
+ * @example
2148
+ * ```typescript
2149
+ * // Creating user-friendly labels
2150
+ * function createLabel(fieldName: string): string {
2151
+ * return toLowerCaseBreak(fieldName)
2152
+ * .split(' ')
2153
+ * .map(word => word.charAt(0).toUpperCase() + word.slice(1))
2154
+ * .join(' ');
2155
+ * }
2156
+ *
2157
+ * createLabel("firstName"); // "First Name"
2158
+ * createLabel("emailAddress"); // "Email Address"
2159
+ * ```
2160
+ *
2161
+ * @example
2162
+ * ```typescript
2163
+ * // Safe handling of edge cases
2164
+ * const result1 = toLowerCaseBreak(undefined);
2165
+ * // Returns: ""
2166
+ *
2167
+ * const result2 = toLowerCaseBreak("", "-");
2168
+ * // Returns: ""
2169
+ *
2170
+ * const result3 = toLowerCaseBreak("singleword");
2171
+ * // Returns: "singleword"
2172
+ * ```
2173
+ *
2174
+ * @remarks
2175
+ * - Returns an empty string for any falsy input (undefined, null, empty string)
2176
+ * - Uses the same intelligent word-breaking algorithm as the breakToWords function
2177
+ * - Handles camelCase, PascalCase, snake_case, kebab-case, and mixed formats
2178
+ * - Preserves numbers as separate words when they appear in the string
2179
+ * - The join parameter can be any string, including empty string for concatenation
2180
+ * - More efficient than calling breakToWords and toLowerCase separately
2181
+ *
2182
+ * @see {@link breakToWords} Function for breaking strings into word arrays
2183
+ * @see {@link toLowerCase} Function for simple lowercase conversion
2184
+ * @see {@link toUpperCaseBreak} Function for uppercase word breaking
2185
+ */
2186
+ function toLowerCaseBreak(str, join) {
2187
+ if (!str) {
2188
+ return "";
2189
+ }
2190
+ return breakToWords(str).map(s => s.toLowerCase()).join(join ?? " ");
2191
+ }
2192
+ /**
2193
+ * Converts a string to uppercase with safe handling of undefined or null values.
2194
+ *
2195
+ * This utility function provides a safe way to convert strings to uppercase, automatically
2196
+ * handling edge cases where the input might be undefined, null, or empty. Unlike the native
2197
+ * String.prototype.toUpperCase() method, this function won't throw an error when called
2198
+ * with undefined or null values, instead returning an empty string.
2199
+ *
2200
+ * This is particularly useful when working with user input, API responses, or any scenario
2201
+ * where string values might be optional or potentially undefined. It's commonly used for
2202
+ * creating constants, formatting display text, generating identifiers, or normalizing text
2203
+ * data that needs to be in uppercase format.
2204
+ *
2205
+ * @param {string} [str] - The string to convert to uppercase. Can be undefined or null.
2206
+ * @returns {string} The uppercase version of the input string, or an empty string if input is falsy
2207
+ *
2208
+ * @example
2209
+ * ```typescript
2210
+ * // Basic string conversion
2211
+ * const result1 = toUpperCase("hello world");
2212
+ * // Returns: "HELLO WORLD"
2213
+ *
2214
+ * const result2 = toUpperCase("javascript");
2215
+ * // Returns: "JAVASCRIPT"
2216
+ * ```
2217
+ *
2218
+ * @example
2219
+ * ```typescript
2220
+ * // Safe handling of undefined/null values
2221
+ * const result1 = toUpperCase(undefined);
2222
+ * // Returns: ""
2223
+ *
2224
+ * const result2 = toUpperCase(null);
2225
+ * // Returns: ""
2226
+ *
2227
+ * const result3 = toUpperCase("");
2228
+ * // Returns: ""
2229
+ * ```
2230
+ *
2231
+ * @example
2232
+ * ```typescript
2233
+ * // Creating constants or identifiers
2234
+ * function generateConstantName(variableName: string): string {
2235
+ * return toUpperCase(variableName.replace(/[^a-zA-Z0-9]/g, '_'));
2236
+ * }
2237
+ *
2238
+ * generateConstantName("user-profile"); // "USER_PROFILE"
2239
+ * generateConstantName("api.endpoint"); // "API_ENDPOINT"
2240
+ * ```
2241
+ *
2242
+ * @example
2243
+ * ```typescript
2244
+ * // Use in text formatting
2245
+ * function formatTitle(title?: string): string {
2246
+ * if (!title) return "UNTITLED";
2247
+ * return toUpperCase(title.trim());
2248
+ * }
2249
+ *
2250
+ * formatTitle("my document"); // "MY DOCUMENT"
2251
+ * formatTitle(undefined); // "UNTITLED"
2252
+ * ```
2253
+ *
2254
+ * @example
2255
+ * ```typescript
2256
+ * // Use in case-insensitive comparisons
2257
+ * function compareIgnoreCase(str1?: string, str2?: string): boolean {
2258
+ * return toUpperCase(str1) === toUpperCase(str2);
2259
+ * }
2260
+ *
2261
+ * compareIgnoreCase("Hello", "HELLO"); // true
2262
+ * compareIgnoreCase("Test", "test"); // true
2263
+ * ```
2264
+ *
2265
+ * @remarks
2266
+ * - Returns an empty string for any falsy input (undefined, null, empty string)
2267
+ * - Uses the native String.prototype.toUpperCase() method for actual conversion
2268
+ * - Does not modify the original string (strings are immutable in JavaScript)
2269
+ * - Handles all Unicode characters correctly, following locale-independent rules
2270
+ * - More robust than direct .toUpperCase() calls when input might be undefined
2271
+ * - Particularly useful for creating constants, headers, or display text
2272
+ *
2273
+ * @see {@link toLowerCase} Function for converting strings to lowercase
2274
+ * @see {@link toUpperCaseBreak} Function for converting to uppercase and breaking into words
2275
+ */
2276
+ function toUpperCase(str) {
2277
+ if (!str) {
2278
+ return "";
2279
+ }
2280
+ return str.toUpperCase();
2281
+ }
2282
+ /**
2283
+ * Compares two strings for similarity using Levenshtein distance algorithm.
2284
+ *
2285
+ * The Levenshtein distance is a measure of the difference between two strings
2286
+ * by counting the minimum number of single-character edits (insertions, deletions,
2287
+ * or substitutions) required to change one string into another.
2288
+ *
2289
+ * @param {string} str1 - First string to compare
2290
+ * @param {string} str2 - Second string to compare
2291
+ * @returns {number} - A value between 0 and 1, where 1 means identical strings
2292
+ *
2293
+ * @example
2294
+ * ```typescript
2295
+ * stringSimilarity("hello", "hallo"); // 0.8 (4/5 characters match)
2296
+ * stringSimilarity("kitten", "sitting"); // ~0.57 (higher distance)
2297
+ * stringSimilarity("same", "same"); // 1.0 (identical)
2298
+ * ```
2299
+ */
2300
+ function stringSimilarity(str1, str2) {
2301
+ if (str1 === str2) return 1.0;
2302
+ if (!str1 || !str2) return 0.0;
2303
+ const len1 = str1.length;
2304
+ const len2 = str2.length;
2305
+ // Initialize the distance matrix
2306
+ const matrix = Array(len1 + 1).fill(null).map(() => Array(len2 + 1).fill(0));
2307
+ // Fill the first row and column
2308
+ for (let i = 0; i <= len1; i++) matrix[i][0] = i;
2309
+ for (let j = 0; j <= len2; j++) matrix[0][j] = j;
2310
+ // Fill the rest of the matrix
2311
+ for (let i = 1; i <= len1; i++) {
2312
+ for (let j = 1; j <= len2; j++) {
2313
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
2314
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1,
2315
+ // deletion
2316
+ matrix[i][j - 1] + 1,
2317
+ // insertion
2318
+ matrix[i - 1][j - 1] + cost);
2319
+ }
2320
+ }
2321
+ // Calculate similarity from Levenshtein distance
2322
+ const maxLen = Math.max(len1, len2);
2323
+ if (maxLen === 0) return 1.0;
2324
+ return 1.0 - matrix[len1][len2] / maxLen;
2325
+ }
2326
+ /**
2327
+ * Generates a slug from a string, suitable for URLs or file names.
2328
+ *
2329
+ * @param {string} str - The string to convert to a slug
2330
+ * @param {string} [separator="-"] - The character to use as separator
2331
+ * @returns {string} - The slug string
2332
+ *
2333
+ * @example
2334
+ * ```typescript
2335
+ * slugify("Hello World"); // "hello-world"
2336
+ * slugify("10 Tips & Tricks!"); // "10-tips-tricks"
2337
+ * slugify("My Product (2023 Edition)"); // "my-product-2023-edition"
2338
+ * slugify("Über résumé"); // "uber-resume"
2339
+ * slugify("Blog Post", "_"); // "blog_post"
2340
+ * ```
2341
+ */
2342
+ function slugify(str, separator = "-") {
2343
+ if (!str) return "";
2344
+ return str.normalize("NFD") // Normalize to decomposed form for handling accents
2345
+ .replace(/[\u0300-\u036f]/g, "") // Remove accents/diacritics
2346
+ .toLowerCase() // Convert to lowercase
2347
+ .trim() // Trim whitespace
2348
+ .replace(/[^\w\s-]/g, "") // Remove non-word chars (except spaces and hyphens)
2349
+ .replace(/[\s_]+/g, separator) // Replace spaces and underscores with separator
2350
+ .replace(new RegExp(`${separator}+`, "g"), separator) // Replace multiple separators with single
2351
+ .replace(new RegExp(`^${separator}|${separator}$`, "g"), ""); // Remove leading/trailing separators
2352
+ }
2353
+ /**
2354
+ * Extracts all email addresses from a string.
2355
+ *
2356
+ * @param {string} str - The string to extract emails from
2357
+ * @returns {string[]} - Array of found email addresses
2358
+ *
2359
+ * @example
2360
+ * ```typescript
2361
+ * extractEmails("Contact us at support@example.com or sales@example.com");
2362
+ * // ["support@example.com", "sales@example.com"]
2363
+ * ```
2364
+ */
2365
+ function extractEmails(str) {
2366
+ if (!str) return [];
2367
+ const emailRegex = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi;
2368
+ return str.match(emailRegex) || [];
2369
+ }
2370
+ /**
2371
+ * Extracts all URLs from a string.
2372
+ *
2373
+ * @param {string} str - The string to extract URLs from
2374
+ * @returns {string[]} - Array of found URLs
2375
+ *
2376
+ * @example
2377
+ * ```typescript
2378
+ * extractUrls("Visit https://example.com or http://test.org/page?id=5");
2379
+ * // ["https://example.com", "http://test.org/page?id=5"]
2380
+ * ```
2381
+ */
2382
+ function extractUrls(str) {
2383
+ if (!str) return [];
2384
+ // noinspection RegExpSimplifiable
2385
+ const urlRegex = /(https?:\/\/[^\s]+)/g;
2386
+ return str.match(urlRegex) || [];
2387
+ }
2388
+ /**
2389
+ * Converts a string to a secure hash using SHA-256.
2390
+ *
2391
+ * Note: This is a browser-compatible implementation using the Web Crypto API.
2392
+ * For Node.js environments, you might want to use the crypto module instead.
2393
+ *
2394
+ * @param {string} str - The string to hash
2395
+ * @returns {Promise<string>} - Promise resolving to the hex hash string
2396
+ *
2397
+ * @example
2398
+ * ```typescript
2399
+ * // Browser usage
2400
+ * await hashString("password123");
2401
+ * // "ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f"
2402
+ * ```
2403
+ */
2404
+ async function hashString(str) {
2405
+ if (!str) return "";
2406
+ // Convert string to Uint8Array
2407
+ const encoder = new TextEncoder();
2408
+ const data = encoder.encode(str);
2409
+ // Hash the data
2410
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
2411
+ // Convert ArrayBuffer to hex string
2412
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2413
+ return hashArray.map(b => b.toString(HEX_RADIX).padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR)).join("");
2414
+ }
2415
+ /**
2416
+ * Generates a random string with specified length and character set.
2417
+ *
2418
+ * @param {number} length - The length of the random string
2419
+ * @param {string} [charset] - The characters to use (defaults to alphanumeric)
2420
+ * @returns {string} - The generated random string
2421
+ *
2422
+ * @example
2423
+ * ```typescript
2424
+ * // Default alphanumeric string
2425
+ * randomString(8);
2426
+ * // e.g. "A7bC9Df3"
2427
+ *
2428
+ * // Hex string
2429
+ * randomString(6, "0123456789abcdef");
2430
+ * // e.g. "a3f9d2"
2431
+ *
2432
+ * // PIN code
2433
+ * randomString(4, "0123456789");
2434
+ * // e.g. "8302"
2435
+ * ```
2436
+ */
2437
+ function randomString(length, charset) {
2438
+ if (length <= 0) return "";
2439
+ const chars = charset || "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2440
+ let result = "";
2441
+ // Generate cryptographically secure random values if available
2442
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
2443
+ const values = new Uint32Array(length);
2444
+ crypto.getRandomValues(values);
2445
+ for (let i = 0; i < length; i++) {
2446
+ result += chars[values[i] % chars.length];
2447
+ }
2448
+ } else {
2449
+ // Fallback to Math.random (less secure)
2450
+ for (let i = 0; i < length; i++) {
2451
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
2452
+ }
2453
+ }
2454
+ return result;
2455
+ }
2456
+ /**
2457
+ * Masks a portion of a string, useful for displaying sensitive information.
2458
+ *
2459
+ * @param {string} str - The string to mask
2460
+ * @param {number} [visibleStart=0] - Number of characters to show at the beginning
2461
+ * @param {number} [visibleEnd=0] - Number of characters to show at the end
2462
+ * @param {string} [maskChar="*"] - Character to use for masking
2463
+ * @returns {string} - The masked string
2464
+ *
2465
+ * @example
2466
+ * ```typescript
2467
+ * // Mask credit card
2468
+ * maskString("4111111111111111", 4, 4);
2469
+ * // "4111********1111"
2470
+ *
2471
+ * // Mask email
2472
+ * maskString("user@example.com", 2, 4, "x");
2473
+ * // "usxxxxxx.com"
2474
+ *
2475
+ * // Mask phone number
2476
+ * maskString("+1-555-123-4567", 0, 4);
2477
+ * // "*********4567"
2478
+ * ```
2479
+ */
2480
+ function maskString(str, visibleStart = 0, visibleEnd = 0, maskChar = "*") {
2481
+ if (!str) return "";
2482
+ const length = str.length;
2483
+ if (visibleStart + visibleEnd >= length) return str;
2484
+ const start = str.substring(0, visibleStart);
2485
+ const middle = maskChar.repeat(length - visibleStart - visibleEnd);
2486
+ const end = str.substring(length - visibleEnd);
2487
+ return start + middle + end;
2488
+ }
2489
+ /**
2490
+ * Convert a string to first case (Capitalize first letter of the string).
2491
+ * @param {string} [str] Optional string to join the words with.
2492
+ * @returns {string} String in first case.
2493
+ *
2494
+ * @example
2495
+ * ```TypeScript
2496
+ * toFirstCase("hello world"); // "Hello world"
2497
+ * ```
2498
+ */
2499
+ function toFirstCase(str) {
2500
+ if (!str) {
2501
+ return "";
2502
+ }
2503
+ return (str[0]?.toUpperCase() || "") + str.slice(1).toLowerCase();
2504
+ }
2505
+ /**
2506
+ * Converts a string to a camelCase or PascalCase variable name.
2507
+ *
2508
+ * @param {string} str - The string to convert
2509
+ * @param {boolean} [pascalCase=false] - Whether to use PascalCase (true) or camelCase (false)
2510
+ * @returns {string} - The variable name
2511
+ *
2512
+ * @example
2513
+ * ```typescript
2514
+ * toVariableName("Hello World"); // "helloWorld"
2515
+ * toVariableName("API Response"); // "apiResponse"
2516
+ * toVariableName("first-name"); // "firstName"
2517
+ * toVariableName("user_id", true); // "UserId"
2518
+ * ```
2519
+ */
2520
+ function toVariableName(str, pascalCase = false) {
2521
+ if (!str) return "";
2522
+ // Remove special characters and replace with spaces
2523
+ const cleaned = str.replace(/[^\w\s]/g, " ");
2524
+ // Break into words and process
2525
+ return breakToWords(cleaned).map((word, index) => {
2526
+ // Skip first word for camelCase unless it's PascalCase
2527
+ if (index === 0 && !pascalCase) {
2528
+ return word.toLowerCase();
2529
+ }
2530
+ // Capitalize first letter
2531
+ return toFirstCase(word);
2532
+ }).join("");
2533
+ }
2534
+ /**
2535
+ * Wraps words in a string to ensure each line is no longer than a specified length.
2536
+ *
2537
+ * @param {string} str - The string to wrap
2538
+ * @param {number} [lineLength=80] - Maximum length of each line
2539
+ * @param {string} [breakChar="\n"] - Character(s) to insert at line breaks
2540
+ * @returns {string} - The wrapped string
2541
+ *
2542
+ * @example
2543
+ * ```typescript
2544
+ * const text = "This is a long sentence that needs to be wrapped to fit within a certain width.";
2545
+ *
2546
+ * wordWrap(text, 20);
2547
+ * // "This is a long\nsentence that needs\nto be wrapped to\nfit within a\ncertain width."
2548
+ *
2549
+ * wordWrap(text, 30, "<br>");
2550
+ * // "This is a long sentence that<br>needs to be wrapped to fit<br>within a certain width."
2551
+ * ```
2552
+ */
2553
+ function wordWrap(str, lineLength = DEFAULT_LINE_LENGTH, breakChar = DEFAULT_BREAK_CHAR) {
2554
+ if (!str) return "";
2555
+ if (str.length <= lineLength) return str;
2556
+ const words = str.split(" ");
2557
+ let result = "";
2558
+ let currentLine = "";
2559
+ for (const word of words) {
2560
+ // If adding this word exceeds line length, start a new line
2561
+ if (currentLine.length + word.length + 1 > lineLength) {
2562
+ result += currentLine.trim() + breakChar;
2563
+ currentLine = `${word} `;
2564
+ } else {
2565
+ currentLine += `${word} `;
2566
+ }
2567
+ }
2568
+ // Add the last line
2569
+ result += currentLine.trim();
2570
+ return result;
2571
+ }
2572
+ /**
2573
+ * Creates an excerpt from a longer text by extracting a portion around a search term.
2574
+ * Useful for search result highlighting or previews.
2575
+ *
2576
+ * @param {string} text - The full text to create an excerpt from
2577
+ * @param {string} searchTerm - The search term to find in the text
2578
+ * @param {number} [contextLength=40] - Number of characters to include before and after the search term
2579
+ * @param {string} [ellipsis="..."] - Characters to use for indicating truncated text
2580
+ * @returns {string} - The excerpt with the search term in context
2581
+ *
2582
+ * @example
2583
+ * ```typescript
2584
+ * const article = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam in dui mauris.";
2585
+ *
2586
+ * createExcerpt(article, "dolor", 10);
2587
+ * // "...ipsum dolor sit amet..."
2588
+ *
2589
+ * createExcerpt(article, "consectetur", 15, "[...]");
2590
+ * // "[...]sit amet, consectetur adipiscing[...]"
2591
+ * ```
2592
+ */
2593
+ function createExcerpt(text, searchTerm, contextLength = DEFAULT_CONTEXT_LENGTH, ellipsis = DEFAULT_ELLIPSIS) {
2594
+ if (!text || !searchTerm) return text || "";
2595
+ // Case-insensitive search
2596
+ const lowerText = text.toLowerCase();
2597
+ const lowerSearchTerm = searchTerm.toLowerCase();
2598
+ const index = lowerText.indexOf(lowerSearchTerm);
2599
+ if (index === -1) {
2600
+ // If search term not found, return beginning of text
2601
+ return text.length > contextLength * CONTEXT_MULTIPLIER ? text.substring(0, contextLength * CONTEXT_MULTIPLIER) + ellipsis : text;
2602
+ }
2603
+ // Calculate the start and end positions for the excerpt
2604
+ const startPos = Math.max(0, index - contextLength);
2605
+ const endPos = Math.min(text.length, index + searchTerm.length + contextLength);
2606
+ // Add ellipsis if we're not at the beginning or end
2607
+ const prefix = startPos > 0 ? ellipsis : "";
2608
+ const suffix = endPos < text.length ? ellipsis : "";
2609
+ // Extract the excerpt
2610
+ return prefix + text.substring(startPos, endPos) + suffix;
2611
+ }
2612
+ /**
2613
+ * Normalizes a string by removing accents, diacritics, and converting to lowercase.
2614
+ *
2615
+ * @param {string} str - The string to normalize
2616
+ * @returns {string} - The normalized string
2617
+ *
2618
+ * @example
2619
+ * ```typescript
2620
+ * normalizeString("Café"); // "cafe"
2621
+ * normalizeString("Über"); // "uber"
2622
+ * normalizeString("résumé"); // "resume"
2623
+ * normalizeString("ESPAÑA"); // "espana"
2624
+ * ```
2625
+ */
2626
+ function normalizeString(str) {
2627
+ if (!str) return "";
2628
+ return str.normalize("NFD") // Decompose accented characters
2629
+ .replace(/[\u0300-\u036f]/g, "") // Remove accent marks
2630
+ .toLowerCase(); // Convert to lowercase
2631
+ }
2632
+ /**
2633
+ * Convert a string to upper cases and break into words with optional join or space.
2634
+ * @param {string} [str] String to convert to upper cases and break into words.
2635
+ * @param {string} [join] Optional string to join the words with.
2636
+ * @returns {string} String in upper cases and broken into words.
2637
+ *
2638
+ * @example
2639
+ * ```TypeScript
2640
+ * toUpperCaseBreak("HelloWorld"); // "HELLO WORLD"
2641
+ * ```
2642
+ *
2643
+ * @example
2644
+ * ```TypeScript
2645
+ * toUpperCaseBreak("HelloWorld", "! "); // "HELLO! WORLD"
2646
+ */
2647
+ function toUpperCaseBreak(str, join) {
2648
+ if (!str) {
2649
+ return "";
2650
+ }
2651
+ return breakToWords(str).map(s => s.toUpperCase()).join(join ?? " ");
2652
+ }
2653
+ /**
2654
+ * Converts a string to Sentence case (first word capitalized, rest lowercase) with customizable word separators.
2655
+ *
2656
+ * This function breaks a string into words, capitalizes the first word, converts remaining
2657
+ * words to lowercase, and then joins them using a custom separator (or space by default).
2658
+ *
2659
+ * Unlike `toFirstCase()` which works on a single word, this function processes multi-word
2660
+ * strings from various formats (camelCase, snake_case, etc.) and gives control over how
2661
+ * the words are joined back together.
2662
+ *
2663
+ * @param {string} [str] - The input string to convert
2664
+ * @param {string} [join] - Optional separator to join words (defaults to space)
2665
+ * @returns {string} - The formatted string with first word capitalized and custom separators,
2666
+ * or empty string if input is falsy
2667
+ *
2668
+ * @example
2669
+ * ```typescript
2670
+ * // With default space separator
2671
+ * toFirstCaseBreak("helloWorld"); // "Hello world"
2672
+ * toFirstCaseBreak("SYSTEM_ERROR"); // "System error"
2673
+ * toFirstCaseBreak("api-request-log"); // "Api request log"
2674
+ *
2675
+ * // With custom separators
2676
+ * toFirstCaseBreak("hello_world", "-"); // "Hello-world"
2677
+ * toFirstCaseBreak("userAuthConfig", "."); // "User.auth.config"
2678
+ * toFirstCaseBreak("GET_USER_DATA", "/"); // "Get/user/data"
2679
+ * ```
2680
+ *
2681
+ * @see {@link toFirstCase} For capitalizing a single word
2682
+ * @see {@link toTitleCase} For capitalizing the first letter of every word
2683
+ * @see {@link breakToWords} The underlying function used to split input into words
2684
+ */
2685
+ function toFirstCaseBreak(str, join) {
2686
+ if (!str) {
2687
+ return "";
2688
+ }
2689
+ return breakToWords(str).map((s, i) => i === 0 ? toFirstCase(s) : s.toLowerCase()).join(join ?? " ");
2690
+ }
2691
+ /**
2692
+ * Convert a string to title case (Capitalize first letter of each word).
2693
+ * @param {string} [str] String to convert to title case.
2694
+ * @returns {string} String in title case.
2695
+ *
2696
+ * @example
2697
+ * ```TypeScript
2698
+ * toTitleCase("hello world"); // "Hello World"
2699
+ * ```
2700
+ */
2701
+ function toTitleCase(str) {
2702
+ if (!str) {
2703
+ return "";
2704
+ }
2705
+ return breakToWords(str).map(s => toFirstCase(s)).join(" ");
2706
+ }
2707
+ /**
2708
+ * Convert a string to camel case.
2709
+ * @param {string} [str] String to convert to camel case.
2710
+ * @returns {string} String in camel case.
2711
+ *
2712
+ * @example
2713
+ * ```TypeScript
2714
+ * toCamelCase("hello world"); // "helloWorld"
2715
+ * ```
2716
+ */
2717
+ function toCamelCase(str) {
2718
+ if (!str) {
2719
+ return "";
2720
+ }
2721
+ return breakToWords(str).map((s, i) => i === 0 ? s.toLowerCase() : toFirstCase(s)).join("");
2722
+ }
2723
+ /**
2724
+ * Convert a string to pascal case.
2725
+ * @param {string} [str] String to convert to pascal case.
2726
+ * @returns {string} String in pascal case.
2727
+ *
2728
+ * @example
2729
+ * ```TypeScript
2730
+ * toPascalCase("hello world"); // "HelloWorld"
2731
+ * ```
2732
+ */
2733
+ function toPascalCase(str) {
2734
+ if (!str) {
2735
+ return "";
2736
+ }
2737
+ return breakToWords(str).map(s => toFirstCase(s)).join("");
2738
+ }
2739
+ /**
2740
+ * Convert a string to sentence case. (Capitalize first letter of every sentence).
2741
+ * @param {string} [str] String to convert to sentence case.
2742
+ * @returns {string} String in sentence case.
2743
+ *
2744
+ * @example
2745
+ * ```TypeScript
2746
+ * toSentenceCase("hello world. how are you?"); // "Hello world. How are you?"
2747
+ * ```
2748
+ */
2749
+ function toSentenceCase(str) {
2750
+ return str.split(".").map(s => toFirstCase(s.trim())).join(". ");
2751
+ }
2752
+ /**
2753
+ * Converts a string to snake_case format.
2754
+ *
2755
+ * This function transforms a string from any common case format (camel, pascal, kebab, etc.)
2756
+ * into snake_case, where words are lowercase and separated by underscores.
2757
+ *
2758
+ * When the optional `caps` parameter is set to true, the result will be UPPER_SNAKE_CASE
2759
+ * (also known as SCREAMING_SNAKE_CASE or CONSTANT_CASE).
2760
+ *
2761
+ * @param {string} [str] - The input string to convert
2762
+ * @param {boolean} [caps=false] - When true, converts to UPPER_SNAKE_CASE
2763
+ * @returns {string} - The converted snake_case string, or empty string if input is falsy
2764
+ *
2765
+ * @example
2766
+ * ```typescript
2767
+ * // From various formats to snake_case
2768
+ * toSnakeCase("hello world"); // "hello_world"
2769
+ * toSnakeCase("HelloWorld"); // "hello_world"
2770
+ * toSnakeCase("hello-world"); // "hello_world"
2771
+ * toSnakeCase("HELLO WORLD"); // "hello_world"
2772
+ *
2773
+ * // To UPPER_SNAKE_CASE (CONSTANT_CASE)
2774
+ * toSnakeCase("hello world", true); // "HELLO_WORLD"
2775
+ * toSnakeCase("helloWorld", true); // "HELLO_WORLD"
2776
+ * ```
2777
+ *
2778
+ * @remarks
2779
+ * This function first splits the string into words using `breakToWords()`,
2780
+ * then joins them with underscores, applying the requested case transformation.
2781
+ */
2782
+ function toSnakeCase(str, caps) {
2783
+ if (!str) {
2784
+ return "";
2785
+ }
2786
+ const snake = breakToWords(str).join("_");
2787
+ return caps ? snake.toUpperCase() : snake.toLowerCase();
2788
+ }
2789
+ /**
2790
+ * Convert a string to kebab case.
2791
+ * @param {string} [str] String to convert to kebab case.
2792
+ * @param {boolean} [caps] Whether to convert to upper case.
2793
+ * @returns {string} String in kebab case.
2794
+ *
2795
+ * @example
2796
+ * ```TypeScript
2797
+ * toKebabCase("hello world"); // "hello-world"
2798
+ * ```
2799
+ *
2800
+ * @example
2801
+ * ```TypeScript
2802
+ * toKebabCase("hello world", true); // "HELLO-WORLD"
2803
+ * ```
2804
+ */
2805
+ function toKebabCase(str, caps) {
2806
+ if (!str) {
2807
+ return "";
2808
+ }
2809
+ const kebab = breakToWords(str).join("-");
2810
+ return caps ? kebab.toUpperCase() : kebab.toLowerCase();
2811
+ }
2812
+ /**
2813
+ * Converts a string to a number.
2814
+ * @param {number|string} str String to convert to a number.
2815
+ * @returns {number|undefined} Number or undefined.
2816
+ *
2817
+ * @example
2818
+ * ```TypeScript
2819
+ * toNumber("123"); // 123
2820
+ * ```
2821
+ */
2822
+ function toNumber(str) {
2823
+ return !isNaN(Number(str)) ? Number(str) : undefined;
2824
+ }
2825
+ /**
2826
+ * Remove HTML tags from a string and return plain text.
2827
+ * @param {string} str String to remove HTML tags from.
2828
+ * @returns {string} Plain text.
2829
+ *
2830
+ * @example
2831
+ * ```TypeScript
2832
+ * htmlToText("<h1>Hello World</h1>"); // "Hello World"
2833
+ * ```
2834
+ */
2835
+ function htmlToText(str) {
2836
+ return str.replace(/<[^>]*>?/gm, "");
2837
+ }
2838
+ /**
2839
+ * Converts a singular English word to its plural form.
2840
+ *
2841
+ * This function applies common English pluralization rules to transform
2842
+ * singular words into their plural equivalents. It handles regular patterns
2843
+ * as well as many irregular cases and special rules for different word endings.
2844
+ *
2845
+ * @param {string} str - The singular word to convert to plural form
2846
+ * @returns {string} - The plural form of the word, or the original string if:
2847
+ * - Input is empty/falsy
2848
+ * - The word is already plural
2849
+ *
2850
+ * @example
2851
+ * ```typescript
2852
+ * // Regular plurals
2853
+ * plural("cat"); // "cats"
2854
+ * plural("dog"); // "dogs"
2855
+ *
2856
+ * // Words ending in -y
2857
+ * plural("party"); // "parties"
2858
+ * plural("day"); // "days"
2859
+ *
2860
+ * // Words ending in -f or -fe
2861
+ * plural("wolf"); // "wolves"
2862
+ * plural("knife"); // "knives"
2863
+ *
2864
+ * // Words ending in -o
2865
+ * plural("potato"); // "potatoes"
2866
+ * plural("photo"); // "photos"
2867
+ *
2868
+ * // Irregular plurals
2869
+ * plural("child"); // "children"
2870
+ * plural("man"); // "men"
2871
+ * plural("foot"); // "feet"
2872
+ *
2873
+ * // Latin/Greek words
2874
+ * plural("cactus"); // "cacti"
2875
+ * plural("analysis"); // "analyses"
2876
+ * ```
2877
+ *
2878
+ * @remarks
2879
+ * - This function is designed for English language words only
2880
+ * - It pairs well with the `singular()` function for round-trip conversion
2881
+ */
2882
+ function plural(str) {
2883
+ if (!str) return str;
2884
+ // Combine all rules in priority order
2885
+ const rules = [...EnglishInflectionRules.INVARIANT, ...EnglishInflectionRules.IRREGULAR.toPlural, ...EnglishInflectionRules.LATIN_GREEK.toPlural, ...EnglishInflectionRules.PATTERN_RULES.toPlural, ...EnglishInflectionRules.DEFAULT.toPlural];
2886
+ // Apply the first matching rule
2887
+ for (const rule of rules) {
2888
+ if (rule.regex.test(str)) {
2889
+ return str.replace(rule.regex, typeof rule.replacement === "string" ? rule.replacement : rule.replacement(str));
2890
+ }
2891
+ }
2892
+ return str;
2893
+ }
2894
+ /**
2895
+ * Truncates a string to a specified length and adds an ellipsis if needed.
2896
+ *
2897
+ * @param {string} str - The string to truncate
2898
+ * @param {number} length - Maximum length of the truncated string (excluding ellipsis)
2899
+ * @param {string} [ellipsis="..."] - The ellipsis to add to truncated strings
2900
+ * @returns {string} - The truncated string with ellipsis if needed
2901
+ *
2902
+ * @example
2903
+ * ```typescript
2904
+ * truncate("This is a long sentence", 10); // "This is a..."
2905
+ * truncate("Short", 10); // "Short"
2906
+ * truncate("Custom ellipsis", 6, " [more]"); // "Custom [more]"
2907
+ * ```
2908
+ */
2909
+ function truncate(str, length, ellipsis = "...") {
2910
+ if (!str) return "";
2911
+ if (str.length <= length) return str;
2912
+ return str.substring(0, length) + ellipsis;
2913
+ }
2914
+ /**
2915
+ * Pads a string to a specified length with a specified character.
2916
+ *
2917
+ * @param {string} str - The string to pad
2918
+ * @param {number} length - The target length of the resulting string
2919
+ * @param {string} [char=" "] - The character to pad with (defaults to space)
2920
+ * @param {boolean} [padEnd=true] - Whether to pad at the end (true) or beginning (false)
2921
+ * @returns {string} - The padded string
2922
+ *
2923
+ * @example
2924
+ * ```typescript
2925
+ * padString("Hello", 10); // "Hello "
2926
+ * padString("Hello", 10, "*"); // "Hello*****"
2927
+ * padString("Hello", 10, "0", false); // "00000Hello"
2928
+ * ```
2929
+ */
2930
+ function padString(str, length, char = " ", padEnd = true) {
2931
+ if (!str) return "";
2932
+ if (str.length >= length) return str;
2933
+ const padding = char.repeat(length - str.length);
2934
+ return padEnd ? str + padding : padding + str;
2935
+ }
2936
+ /**
2937
+ * Escapes special characters in a string for use in regular expressions.
2938
+ *
2939
+ * @param {string} str - The string to escape
2940
+ * @returns {string} - The escaped string safe for use in RegExp
2941
+ *
2942
+ * @example
2943
+ * ```typescript
2944
+ * escapeRegExp("hello.world*"); // "hello\.world\*"
2945
+ *
2946
+ * // Usage in RegExp:
2947
+ * const userInput = "hello.world*";
2948
+ * const regex = new RegExp(escapeRegExp(userInput));
2949
+ * ```
2950
+ */
2951
+ function escapeRegExp(str) {
2952
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2953
+ }
2954
+ /**
2955
+ * Capitalizes each word in a string according to title case rules.
2956
+ *
2957
+ * This function implements proper title case rules, where:
2958
+ * - The first and last words are always capitalized
2959
+ * - All other major words are capitalized
2960
+ * - Minor words (articles, conjunctions, short prepositions) are not capitalized
2961
+ * unless they are the first or last word
2962
+ *
2963
+ * @param {string} str - The string to convert to title case
2964
+ * @returns {string} - The string in proper title case format
2965
+ *
2966
+ * @example
2967
+ * ```typescript
2968
+ * toProperTitleCase("the quick brown fox jumps over the lazy dog");
2969
+ * // "The Quick Brown Fox Jumps over the Lazy Dog"
2970
+ *
2971
+ * toProperTitleCase("a tale of two cities");
2972
+ * // "A Tale of Two Cities"
2973
+ * ```
2974
+ */
2975
+ function toProperTitleCase(str) {
2976
+ if (!str) return "";
2977
+ const minorWords = ["a", "an", "and", "as", "at", "but", "by", "for", "in", "nor", "of", "on", "or", "per", "the", "to", "via", "vs"];
2978
+ const words = str.toLowerCase().split(/\s+/);
2979
+ const result = words.map((word, index) => {
2980
+ // Always capitalize first and last word
2981
+ if (index === 0 || index === words.length - 1) {
2982
+ return toFirstCase(word);
2983
+ }
2984
+ // Don't capitalize minor words
2985
+ if (minorWords.includes(word.toLowerCase())) {
2986
+ return word.toLowerCase();
2987
+ }
2988
+ // Capitalize major words
2989
+ return toFirstCase(word);
2990
+ });
2991
+ return result.join(" ");
2992
+ }
2993
+ /**
2994
+ * Implementation of the format function that handles both overloads.
2995
+ */
2996
+ function format(template, ...values) {
2997
+ if (!template) return "";
2998
+ // If the first value is an object (for named placeholders), use it directly
2999
+ if (values.length === 1 && typeof values[0] === "object" && values[0] !== null && !Array.isArray(values[0])) {
3000
+ const replacements = values[0];
3001
+ return template.replace(/{([^{}]+)}/g, (match, key) => {
3002
+ const value = replacements[key];
3003
+ return value !== undefined ? String(value) : match;
3004
+ });
3005
+ }
3006
+ // Otherwise use indexed placeholders
3007
+ return template.replace(/{(\d+)}/g, (match, index) => {
3008
+ const value = values[Number(index)];
3009
+ return value !== undefined ? String(value) : match;
3010
+ });
3011
+ }
3012
+ /**
3013
+ * Counts the occurrences of a substring within a string.
3014
+ *
3015
+ * @param {string} str - The string to search within
3016
+ * @param {string} searchValue - The substring to search for
3017
+ * @param {boolean} [caseSensitive=true] - Whether the search should be case-sensitive
3018
+ * @returns {number} - The number of occurrences
3019
+ *
3020
+ * @example
3021
+ * ```typescript
3022
+ * countOccurrences("hello hello world", "hello"); // 2
3023
+ * countOccurrences("Hello hello", "hello", false); // 2
3024
+ * countOccurrences("Hello hello", "hello", true); // 1
3025
+ * ```
3026
+ */
3027
+ function countOccurrences(str, searchValue, caseSensitive = true) {
3028
+ if (!str || !searchValue) return 0;
3029
+ const regex = new RegExp(escapeRegExp(searchValue), caseSensitive ? "g" : "gi");
3030
+ const matches = str.match(regex);
3031
+ return matches ? matches.length : 0;
3032
+ }
3033
+ /**
3034
+ * Reverses a string.
3035
+ *
3036
+ * @param {string} str - The string to reverse
3037
+ * @returns {string} - The reversed string
3038
+ *
3039
+ * @example
3040
+ * ```typescript
3041
+ * reverse("hello"); // "olleh"
3042
+ * reverse("12345"); // "54321"
3043
+ * ```
3044
+ */
3045
+ function reverse(str) {
3046
+ if (!str) return "";
3047
+ return str.split("").reverse().join("");
3048
+ }
3049
+ /**
3050
+ * Checks if a string contains only alphanumeric characters.
3051
+ *
3052
+ * @param {string} str - The string to check
3053
+ * @returns {boolean} - True if the string contains only alphanumeric characters
3054
+ *
3055
+ * @example
3056
+ * ```typescript
3057
+ * isAlphanumeric("abc123"); // true
3058
+ * isAlphanumeric("abc-123"); // false
3059
+ * ```
3060
+ */
3061
+ function isAlphanumeric(str) {
3062
+ if (!str) return false;
3063
+ return /^[a-zA-Z0-9]+$/.test(str);
3064
+ }
3065
+ /**
3066
+ * Removes all whitespace from a string.
3067
+ *
3068
+ * @param {string} str - The string to process
3069
+ * @returns {string} - The string with all whitespace removed
3070
+ *
3071
+ * @example
3072
+ * ```typescript
3073
+ * removeWhitespace("hello world"); // "helloworld"
3074
+ * removeWhitespace(" spaces tabs "); // "spacestabs"
3075
+ * ```
3076
+ */
3077
+ function removeWhitespace(str) {
3078
+ if (!str) return "";
3079
+ return str.replace(/\s+/g, "");
3080
+ }
3081
+ /**
3082
+ * Converts plural English words to their singular form.
3083
+ *
3084
+ * This function applies a series of linguistic rules and exceptions to transform
3085
+ * plural English words into their singular equivalents. It handles irregular plurals
3086
+ * (e.g., "children" → "child"), common suffix patterns (e.g., "parties" → "party"),
3087
+ * and Latin-derived words (e.g., "cacti" → "cactus").
3088
+ *
3089
+ * The function applies rules in priority order, from most specific to most general,
3090
+ * to ensure correct handling of special cases.
3091
+ *
3092
+ * @param {string} str - The plural word to convert to singular form
3093
+ * @returns {string} - The singular form of the word, or the original string if:
3094
+ * - Input is empty/falsy
3095
+ * - No singular form is recognized
3096
+ * - The word is already singular
3097
+ *
3098
+ * @example
3099
+ * ```typescript
3100
+ * // Irregular plurals
3101
+ * singular("children"); // "child"
3102
+ * singular("men"); // "man"
3103
+ * singular("feet"); // "foot"
3104
+ *
3105
+ * // Common patterns
3106
+ * singular("cats"); // "cat"
3107
+ * singular("boxes"); // "box"
3108
+ * singular("parties"); // "party"
3109
+ * singular("wolves"); // "wolf"
3110
+ *
3111
+ * // Latin-derived words
3112
+ * singular("cacti"); // "cactus"
3113
+ * singular("phenomena"); // "phenomenon"
3114
+ * ```
3115
+ *
3116
+ * @remarks
3117
+ * - This function is designed for English language words only
3118
+ * - It handles many common cases but isn't exhaustive for all English plurals
3119
+ * - Words that are identical in singular and plural form (e.g., "sheep") are returned unchanged
3120
+ */
3121
+ function singular(str) {
3122
+ if (!str) return str;
3123
+ // Combine all rules in priority order
3124
+ const rules = [...EnglishInflectionRules.INVARIANT, ...EnglishInflectionRules.IRREGULAR.toSingular, ...EnglishInflectionRules.LATIN_GREEK.toSingular, ...EnglishInflectionRules.PATTERN_RULES.toSingular, ...EnglishInflectionRules.DEFAULT.toSingular];
3125
+ // Iterate over rules and apply the first matching one
3126
+ for (const rule of rules) {
3127
+ if (rule.regex.test(str)) {
3128
+ return str.replace(rule.regex, typeof rule.replacement === "string" ? rule.replacement : rule.replacement(str));
3129
+ }
3130
+ }
3131
+ // Return the original string if no rules match
3132
+ return str;
3133
+ }
3134
+
3135
+ // noinspection JSUnusedGlobalSymbols
3136
+ /**
3137
+ * Type-safe utility to check if a value is an array of a specific type.
3138
+ *
3139
+ * This function acts as a type guard that not only checks if the provided value
3140
+ * is an array (using the native Array.isArray method), but also narrows the TypeScript
3141
+ * type to an array of the generic type T. This enables safer handling of potentially
3142
+ * array-typed values with full type inference in the conditional branches.
3143
+ *
3144
+ * @template T The expected element type of the array
3145
+ * @param {T | T[] | undefined} value The value to check, which could be a single item,
3146
+ * an array of items, or undefined
3147
+ * @returns {value is T[]} Type predicate that narrows the type to T[] when true
3148
+ *
3149
+ * @remarks
3150
+ * While this function essentially wraps Array.isArray, its value comes from the
3151
+ * TypeScript type narrowing it provides, making it especially useful in code that
3152
+ * needs to handle both single items and collections of items with type safety.
3153
+ *
3154
+ * The function properly handles undefined values by returning false, making it safe
3155
+ * to use with optional parameters or potentially undefined values.
3156
+ *
3157
+ * @example
3158
+ * ```typescript
3159
+ * // Function that can accept either a single user or multiple users
3160
+ * async function createUser(userOrUsers: UserDto | UserDto[] | undefined): Promise<User | User[]> {
3161
+ * // TypeScript knows userOrUsers is UserDto[] in this branch
3162
+ * if (isArray<UserDto>(userOrUsers)) {
3163
+ * return Promise.all(userOrUsers.map(user => userService.createUser(user)));
3164
+ * }
3165
+ * // TypeScript knows userOrUsers is UserDto or undefined in this branch
3166
+ * else if (userOrUsers) {
3167
+ * return userService.createUser(userOrUsers);
3168
+ * }
3169
+ * else {
3170
+ * throw new BadRequestException('User data is required');
3171
+ * }
3172
+ * }
3173
+ * ```
3174
+ */
3175
+ function isArray(value) {
3176
+ return Array.isArray(value);
3177
+ }
3178
+ /**
3179
+ * Type-safe utility to check if a value is a non-array object of a specific type.
3180
+ *
3181
+ * This function serves as a type guard that checks if the provided value is an object
3182
+ * (but not an array) and narrows the TypeScript type to the generic type T. It properly
3183
+ * excludes null, arrays, and primitive values, focusing only on object instances.
3184
+ *
3185
+ * @template T The expected object type to narrow to when the check passes
3186
+ * @param {T | T[] | undefined} [value] The value to check, which could be a single object,
3187
+ * an array of objects, or undefined
3188
+ * @returns {value is T} Type predicate that narrows the type to T when true
3189
+ *
3190
+ * @remarks
3191
+ * This function performs three checks to ensure the value is a proper object:
3192
+ * 1. It's not an array (using Array.isArray)
3193
+ * 2. It's of type "object" according to JavaScript's typeof operator
3194
+ * 3. It's not null (which would pass the typeof check but isn't a valid object)
3195
+ *
3196
+ * This is particularly useful when handling parameters that could be either an object
3197
+ * or a simpler identifier (like an ID), allowing for type-safe conditional logic.
3198
+ *
3199
+ * @example
3200
+ * ```typescript
3201
+ * // Function that can accept either a user ID or a user object
3202
+ * async function getUserInfo(userIdOrUser: number | User | undefined): Promise<UserInfo> {
3203
+ * // TypeScript knows userIdOrUser is User in this branch
3204
+ * if (isObject<User>(userIdOrUser)) {
3205
+ * // Can safely access object properties
3206
+ * return await userService.getUserInfo(userIdOrUser.id);
3207
+ * }
3208
+ * // TypeScript knows userIdOrUser is number or undefined in this branch
3209
+ * else {
3210
+ * return await userService.getUserInfo(userIdOrUser);
3211
+ * }
3212
+ * }
3213
+ *
3214
+ * // Works with optional parameters too
3215
+ * function formatUserName(user?: User | string): string {
3216
+ * if (isObject<User>(user)) {
3217
+ * return `${user.firstName} ${user.lastName}`;
3218
+ * }
3219
+ * return user || 'Guest';
3220
+ * }
3221
+ * ```
3222
+ */
3223
+ function isObject(value) {
3224
+ return !Array.isArray(value) && typeof value === "object" && value !== null;
3225
+ }
3226
+ /**
3227
+ * Type-safe utility to check if a value is an object with a specific property.
3228
+ *
3229
+ * This function acts as a type guard that determines if an unknown value is an object
3230
+ * that contains a specified property, and narrows the TypeScript type to the generic
3231
+ * type T when true. This provides a robust way to implement duck typing in TypeScript,
3232
+ * allowing for safe property access in the conditional branches.
3233
+ *
3234
+ * @template T The expected object type that should contain the property
3235
+ * @param {unknown} value Any value to check, with no type constraints
3236
+ * @param {keyof T} propertyName The name of the property that should exist on the object
3237
+ * @returns {value is T} Type predicate that narrows the type to T when true
3238
+ *
3239
+ * @remarks
3240
+ * This function uses Object.prototype.hasOwnProperty.call to safely check for property
3241
+ * existence, even if the object has a custom implementation of hasOwnProperty or if the
3242
+ * property is named 'hasOwnProperty'.
3243
+ *
3244
+ * Unlike the simpler isObject utility, this function can distinguish between different
3245
+ * object types based on their properties, making it ideal for discriminating between
3246
+ * different interface implementations or handling union types.
3247
+ *
3248
+ * @example
3249
+ * ```typescript
3250
+ * // Check if an object has the properties of a User interface
3251
+ * interface User {
3252
+ * id: number;
3253
+ * name: string;
3254
+ * }
3255
+ *
3256
+ * interface Order {
3257
+ * orderNumber: string;
3258
+ * items: string[];
3259
+ * }
3260
+ *
3261
+ * // Function that can handle different object types
3262
+ * function processEntity(entity: unknown): void {
3263
+ * // Check if entity has 'id' property, indicating it's a User
3264
+ * if (isObjectWith<User>(entity, 'id')) {
3265
+ * // TypeScript knows entity is User in this branch
3266
+ * console.log(`Processing user: ${entity.name}`);
3267
+ * }
3268
+ * // Check if entity has 'orderNumber' property, indicating it's an Order
3269
+ * else if (isObjectWith<Order>(entity, 'orderNumber')) {
3270
+ * // TypeScript knows entity is Order in this branch
3271
+ * console.log(`Processing order: ${entity.orderNumber} with ${entity.items.length} items`);
3272
+ * }
3273
+ * else {
3274
+ * console.log('Unknown entity type');
3275
+ * }
3276
+ * }
3277
+ *
3278
+ * // Useful for API responses where types may vary
3279
+ * async function handleResponse(response: unknown): Promise<void> {
3280
+ * if (isObjectWith<ErrorResponse>(response, 'errorCode')) {
3281
+ * throw new ApiException(response.errorCode, response.message);
3282
+ * }
3283
+ * else if (isObjectWith<SuccessResponse>(response, 'data')) {
3284
+ * await processData(response.data);
3285
+ * }
3286
+ * }
3287
+ * ```
3288
+ */
3289
+ function isObjectWith(value, propertyName) {
3290
+ return Object.prototype.hasOwnProperty.call(value, propertyName);
3291
+ }
3292
+
3293
+ /**
3294
+ * Template tags for string transformations
3295
+ *
3296
+ * This enum defines all available template tags that can be used with the `applyTemplate`
3297
+ * and `applyTemplates` functions. Each tag corresponds to a specific string transformation
3298
+ * function from the string.utils module.
3299
+ *
3300
+ * Template tags are represented as strings in the format `#{transformationName}`. When these
3301
+ * tags appear in template strings, they are replaced with the result of applying the
3302
+ * corresponding transformation to the provided value.
3303
+ *
3304
+ * @see {@link applyTemplate} For applying a single value to a template
3305
+ * @see {@link applyTemplates} For applying multiple values to a template
3306
+ */
3307
+ var TemplateTag;
3308
+ (function (TemplateTag) {
3309
+ TemplateTag["UPPER_CASE"] = "#{upperCase}";
3310
+ TemplateTag["SNAKE_CASE"] = "#{snakeCase}";
3311
+ TemplateTag["UPPER_SNAKE_CASE"] = "#{upperSnakeCase}";
3312
+ TemplateTag["LOWER_CASE"] = "#{lowerCase}";
3313
+ TemplateTag["SENTENCE_CASE"] = "#{sentenceCase}";
3314
+ TemplateTag["FIRST_CASE"] = "#{firstCase}";
3315
+ TemplateTag["CAMEL_CASE"] = "#{camelCase}";
3316
+ TemplateTag["PASCAL_CASE"] = "#{pascalCase}";
3317
+ TemplateTag["KEBAB_CASE"] = "#{kebabCase}";
3318
+ TemplateTag["UPPER_KEBAB_CASE"] = "#{upperKebabCase}";
3319
+ TemplateTag["TITLE_CASE"] = "#{titleCase}";
3320
+ TemplateTag["LOWER_CASE_BREAK"] = "#{lowerCaseBreak}";
3321
+ TemplateTag["UPPER_CASE_BREAK"] = "#{upperCaseBreak}";
3322
+ TemplateTag["FIRST_CASE_BREAK"] = "#{firstCaseBreak}";
3323
+ TemplateTag["SINGULAR"] = "#{singular}";
3324
+ TemplateTag["NUMBER"] = "#{number}";
3325
+ TemplateTag["HTML_TO_TEXT"] = "#{htmlToText}";
3326
+ // FORMAT = "#{format}",
3327
+ })(TemplateTag || (TemplateTag = {}));
3328
+
3329
+ // noinspection JSUnusedGlobalSymbols
3330
+ /**
3331
+ * Applies a value to a template string containing transformation tags.
3332
+ *
3333
+ * This function replaces template tags in a string with transformed versions of a provided value.
3334
+ * It's particularly useful for creating dynamic messages, error notifications, or any text
3335
+ * that needs to include the same value in different formats.
3336
+ *
3337
+ * Each template tag is replaced with the result of applying the corresponding transformation
3338
+ * function to the provided value. If a tag isn't present in the template string, that
3339
+ * transformation is skipped.
3340
+ *
3341
+ * Available template tags:
3342
+ * - `#{upperCase}` - Converts to UPPERCASE ("USER")
3343
+ * - `#{snakeCase}` - Converts to snake_case ("user_profile")
3344
+ * - `#{upperSnakeCase}` - Converts to UPPER_SNAKE_CASE ("USER_PROFILE")
3345
+ * - `#{lowerCase}` - Converts to lowercase ("user")
3346
+ * - `#{sentenceCase}` - Converts to Sentence case ("User profile")
3347
+ * - `#{firstCase}` - Converts to First case ("User")
3348
+ * - `#{camelCase}` - Converts to camelCase ("userProfile")
3349
+ * - `#{pascalCase}` - Converts to PascalCase ("UserProfile")
3350
+ * - `#{kebabCase}` - Converts to kebab-case ("user-profile")
3351
+ * - `#{upperKebabCase}` - Converts to KEBAB-CASE ("USER-PROFILE")
3352
+ * - `#{titleCase}` - Converts to Title Case ("User Profile")
3353
+ * - `#{lowerCaseBreak}` - Breaks words and converts to lowercase ("user profile")
3354
+ * - `#{upperCaseBreak}` - Breaks words and converts to UPPERCASE ("USER PROFILE")
3355
+ * - `#{firstCaseBreak}` - Breaks words and applies First case ("User profile")
3356
+ * - `#{singular}` - Converts plural to singular form ("users" → "user")
3357
+ * - `#{number}` - Converts to a number if possible ("123" → 123)
3358
+ * - `#{htmlToText}` - Removes HTML tags ("<b>User</b>" → "User")
3359
+ * - `#{format}` - Formats a string with placeholders (used with additional parameters)
3360
+ *
3361
+ * @example
3362
+ * ```typescript
3363
+ * // Error message with different versions of the same term
3364
+ * applyTemplate(
3365
+ * 'Cannot create a #{lowerCase} with this email. #{sentenceCase} already exists.',
3366
+ * 'User'
3367
+ * );
3368
+ * // Output: "Cannot create a user with this email. User already exists."
3369
+ * ```
3370
+ *
3371
+ * @example
3372
+ * ```typescript
3373
+ * // Using multiple transformations of the same value
3374
+ * applyTemplate(
3375
+ * 'Model: #{pascalCase}\nTable: #{snakeCase}\nAPI Path: #{kebabCase}',
3376
+ * 'blogPost'
3377
+ * );
3378
+ * // Output:
3379
+ * // Model: BlogPost
3380
+ * // Table: blog_post
3381
+ * // API Path: blog-post
3382
+ * ```
3383
+ *
3384
+ * @param {string} str - Template string containing transformation tags
3385
+ * @param {string} prefix - The prefix to update with
3386
+ * @returns {string} - The template with all tags replaced by transformed values
3387
+ *
3388
+ * @see {@link TemplateTag} For the full list of available template tags
3389
+ * @see {@link applyTemplates} For applying multiple different values to a template
3390
+ */
3391
+ function applyTemplate(str, prefix) {
3392
+ // Apply all available template tags
3393
+ return str.replace(TemplateTag.UPPER_CASE, toUpperCase(prefix)).replace(TemplateTag.SNAKE_CASE, toSnakeCase(prefix)).replace(TemplateTag.UPPER_SNAKE_CASE, toSnakeCase(prefix, true)).replace(TemplateTag.LOWER_CASE, toLowerCase(prefix)).replace(TemplateTag.SENTENCE_CASE, toSentenceCase(prefix)).replace(TemplateTag.FIRST_CASE, toFirstCase(prefix)).replace(TemplateTag.CAMEL_CASE, toCamelCase(prefix)).replace(TemplateTag.PASCAL_CASE, toPascalCase(prefix)).replace(TemplateTag.KEBAB_CASE, toKebabCase(prefix)).replace(TemplateTag.UPPER_KEBAB_CASE, toKebabCase(prefix, true)).replace(TemplateTag.TITLE_CASE, toTitleCase(prefix)).replace(TemplateTag.LOWER_CASE_BREAK, toLowerCaseBreak(prefix)).replace(TemplateTag.UPPER_CASE_BREAK, toUpperCaseBreak(prefix)).replace(TemplateTag.FIRST_CASE_BREAK, toFirstCaseBreak(prefix)).replace(TemplateTag.SINGULAR, singular(prefix)).replace(TemplateTag.NUMBER, String(toNumber(prefix) ?? prefix)).replace(TemplateTag.HTML_TO_TEXT, htmlToText(prefix));
3394
+ }
3395
+ /**
3396
+ * Applies multiple named template transformations to a string with different values for each prefix.
3397
+ *
3398
+ * This advanced templating function allows you to use multiple different values within a single
3399
+ * template string, each with their own set of transformation tags. Unlike `applyTemplate` which
3400
+ * applies one value to all tags, this function lets you specify different values for different
3401
+ * prefixes, enabling complex template scenarios with multiple entities.
3402
+ *
3403
+ * Each prefix in the template is identified by a namespace (e.g., `user`, `post`) followed by
3404
+ * a dot and the transformation tag (e.g., `#{user.lowerCase}`, `#{post.titleCase}`). This allows
3405
+ * for sophisticated template generation where different parts of the text need different source
3406
+ * values with various transformations applied.
3407
+ *
3408
+ * This is particularly useful for generating complex messages, code templates, documentation,
3409
+ * or any text that involves multiple entities that need to be formatted differently within
3410
+ * the same template.
3411
+ *
3412
+ * @param {string} str - Template string containing namespaced transformation tags in the format
3413
+ * `#{prefix.transformationType}` where prefix matches keys in the prefixes object
3414
+ * @param {Record<string, string>} prefixes - Object mapping prefix names to their corresponding values.
3415
+ * Each key becomes a namespace in the template, and the value
3416
+ * is the string that will be transformed according to the tags.
3417
+ * @returns {string} The template string with all namespaced tags replaced by their transformed values
3418
+ *
3419
+ * @example
3420
+ * ```typescript
3421
+ * // Multi-entity message generation
3422
+ * const message = applyTemplates(
3423
+ * 'User #{user.lowerCase} created a new #{entity.sentenceCase} titled "#{title.titleCase}"',
3424
+ * {
3425
+ * user: 'JohnDoe',
3426
+ * entity: 'blogPost',
3427
+ * title: 'my first programming tutorial'
3428
+ * }
3429
+ * );
3430
+ * // Returns: "User johndoe created a new Blog post titled "My First Programming Tutorial""
3431
+ * ```
3432
+ *
3433
+ * @example
3434
+ * ```typescript
3435
+ * // Code generation with multiple entities
3436
+ * const codeTemplate = applyTemplates(
3437
+ * `class #{model.pascalCase} {
3438
+ * constructor(private #{service.camelCase}: #{service.pascalCase}Service) {}
3439
+ *
3440
+ * async create#{model.pascalCase}(data: #{model.pascalCase}Data): Promise<#{model.pascalCase}> {
3441
+ * return this.#{service.camelCase}.create(data);
3442
+ * }
3443
+ * }`,
3444
+ * {
3445
+ * model: 'user-profile',
3446
+ * service: 'database'
3447
+ * }
3448
+ * );
3449
+ * // Generates a complete class with proper naming conventions
3450
+ * ```
3451
+ *
3452
+ * @example
3453
+ * ```typescript
3454
+ * // API documentation generation
3455
+ * const apiDoc = applyTemplates(
3456
+ * `## #{endpoint.titleCase}
3457
+ *
3458
+ * **URL:** \`/api/#{endpoint.kebabCase}\`
3459
+ * **Method:** #{method.upperCase}
3460
+ * **Model:** #{model.pascalCase}
3461
+ *
3462
+ * Creates a new #{model.lowerCaseBreak} in the system.`,
3463
+ * {
3464
+ * endpoint: 'userProfiles',
3465
+ * method: 'post',
3466
+ * model: 'UserProfile'
3467
+ * }
3468
+ * );
3469
+ * ```
3470
+ *
3471
+ * @example
3472
+ * ```typescript
3473
+ * // Database migration script generation
3474
+ * const migration = applyTemplates(
3475
+ * `CREATE TABLE #{table.snakeCase} (
3476
+ * id SERIAL PRIMARY KEY,
3477
+ * #{field.snakeCase} VARCHAR(255) NOT NULL,
3478
+ * created_at TIMESTAMP DEFAULT NOW()
3479
+ * );
3480
+ *
3481
+ * CREATE INDEX idx_#{table.snakeCase}_#{field.snakeCase} ON #{table.snakeCase}(#{field.snakeCase});`,
3482
+ * {
3483
+ * table: 'UserProfiles',
3484
+ * field: 'emailAddress'
3485
+ * }
3486
+ * );
3487
+ * ```
3488
+ *
3489
+ * @remarks
3490
+ * - Each prefix in the prefixes object becomes a namespace in the template
3491
+ * - Template tags must follow the format `#{prefix.transformationType}`
3492
+ * - All transformation types available in `applyTemplate` are supported
3493
+ * - If a prefixed tag is not found in the template, it's simply ignored
3494
+ * - The function processes all prefixes and their transformations in the order they appear
3495
+ * - Supports the same transformation types as the TemplateTag enum
3496
+ * - More flexible than `applyTemplate` but with slightly more complex syntax
3497
+ *
3498
+ * @see {@link applyTemplate} For applying a single value to multiple transformation tags
3499
+ * @see {@link TemplateTag} For the complete list of available transformation types
3500
+ */
3501
+ function applyTemplates(str, prefixes) {
3502
+ let result = str;
3503
+ // Process each prefix
3504
+ for (const [key, prefix] of Object.entries(prefixes)) {
3505
+ // Replace all possible template tags for this prefix
3506
+ // eslint-disable-next-line no-loop-func
3507
+ Object.values(TemplateTag).forEach(tag => {
3508
+ // Convert #{tag} to #{prefix.tag}
3509
+ const prefixedTag = tag.replace("#{", `#{${key}.`);
3510
+ if (result.includes(prefixedTag)) {
3511
+ // Get the transformation function name
3512
+ const templateFunction = tag.replace(/#{(.+)}/, "$1");
3513
+ // Apply the appropriate transformation based on the tag
3514
+ let replacement = prefix;
3515
+ switch (templateFunction) {
3516
+ case TemplateTag.UPPER_CASE:
3517
+ replacement = toUpperCase(prefix);
3518
+ break;
3519
+ case TemplateTag.SNAKE_CASE:
3520
+ replacement = toSnakeCase(prefix);
3521
+ break;
3522
+ case TemplateTag.UPPER_SNAKE_CASE:
3523
+ replacement = toSnakeCase(prefix, true);
3524
+ break;
3525
+ case TemplateTag.LOWER_CASE:
3526
+ replacement = toLowerCase(prefix);
3527
+ break;
3528
+ case TemplateTag.SENTENCE_CASE:
3529
+ replacement = toSentenceCase(prefix);
3530
+ break;
3531
+ case TemplateTag.FIRST_CASE:
3532
+ replacement = toFirstCase(prefix);
3533
+ break;
3534
+ case TemplateTag.CAMEL_CASE:
3535
+ replacement = toCamelCase(prefix);
3536
+ break;
3537
+ case TemplateTag.PASCAL_CASE:
3538
+ replacement = toPascalCase(prefix);
3539
+ break;
3540
+ case TemplateTag.KEBAB_CASE:
3541
+ replacement = toKebabCase(prefix);
3542
+ break;
3543
+ case TemplateTag.UPPER_KEBAB_CASE:
3544
+ replacement = toKebabCase(prefix, true);
3545
+ break;
3546
+ case TemplateTag.TITLE_CASE:
3547
+ replacement = toTitleCase(prefix);
3548
+ break;
3549
+ case TemplateTag.LOWER_CASE_BREAK:
3550
+ replacement = toLowerCaseBreak(prefix);
3551
+ break;
3552
+ case TemplateTag.UPPER_CASE_BREAK:
3553
+ replacement = toUpperCaseBreak(prefix);
3554
+ break;
3555
+ case TemplateTag.FIRST_CASE_BREAK:
3556
+ replacement = toFirstCaseBreak(prefix);
3557
+ break;
3558
+ case TemplateTag.SINGULAR:
3559
+ replacement = singular(prefix);
3560
+ break;
3561
+ case TemplateTag.NUMBER:
3562
+ replacement = String(toNumber(prefix) ?? prefix);
3563
+ break;
3564
+ case TemplateTag.HTML_TO_TEXT:
3565
+ replacement = htmlToText(prefix);
3566
+ break;
3567
+ }
3568
+ result = result.replace(prefixedTag, replacement);
3569
+ }
3570
+ });
3571
+ }
3572
+ return result;
3573
+ }
3574
+
3575
+ export { CHARACTERS_TO_REMOVE, CONTEXT_MULTIPLIER, DEFAULT_BREAK_CHAR, DEFAULT_CONTEXT_LENGTH, DEFAULT_ELLIPSIS, DEFAULT_LINE_LENGTH, EnglishInflectionRules, HEX_PADDING_CHAR, HEX_PADDING_LENGTH, HEX_RADIX, TemplateTag, applyTemplate, applyTemplates, breakToWords, countOccurrences, createExcerpt, deepCopy, dottedPathObjectToNested, escapeRegExp, extractEmails, extractUrls, filterByObject, format, getEnumValues, getFileExt, getFileSize, getMapKey, getMapKeys, getValueByPath, groupBy, hasOwnAll, hashString, htmlToText, isAlphanumeric, isArray, isObject, isObjectWith, isValidRedirectUrl, maskString, mimeTypes, normalizeString, objectToDottedPathValueObject, omit, padString, plural, prune, randomString, removeWhitespace, reverse, searchMapValues, singular, slugify, stringSimilarity, toCamelCase, toFirstCase, toFirstCaseBreak, toKebabCase, toLowerCase, toLowerCaseBreak, toNumber, toPascalCase, toProperTitleCase, toSentenceCase, toSnakeCase, toTitleCase, toUpperCase, toUpperCaseBreak, toVariableName, truncate, wordWrap };