@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.cjs.js ADDED
@@ -0,0 +1,3645 @@
1
+ 'use strict';
2
+
3
+ // noinspection JSUnusedGlobalSymbols
4
+ /**
5
+ * Validates if a redirect URL is allowed based on a whitelist of permitted domains.
6
+ *
7
+ * This security utility function checks whether a given URL's domain is included in a list
8
+ * of allowed domains, helping prevent open redirect vulnerabilities. It supports both exact
9
+ * domain matching and subdomain matching, where a URL's hostname must either exactly match
10
+ * an allowed domain or be a subdomain of an allowed domain.
11
+ *
12
+ * This is essential for secure redirect functionality in web applications, preventing
13
+ * attackers from redirecting users to malicious external sites. It's commonly used in
14
+ * authentication flows, logout redirects, and any feature that accepts user-provided
15
+ * redirect URLs.
16
+ *
17
+ * @param {string} url - The URL to validate. Must be a valid URL string that can be parsed
18
+ * by the URL constructor. Can include protocol, path, query parameters, etc.
19
+ * @param {string[]} allowedDomains - Array of domain names that are permitted for redirects.
20
+ * Domains should be specified without protocol (e.g., "example.com").
21
+ * Subdomains of these domains will also be allowed.
22
+ * @returns {boolean} True if the URL's hostname matches or is a subdomain of any allowed domain,
23
+ * false if the domain is not allowed or if the URL is invalid
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // Basic domain validation
28
+ * const allowedDomains = ["example.com", "myapp.com"];
29
+ *
30
+ * const isValid1 = isValidRedirectUrl("https://example.com/dashboard", allowedDomains);
31
+ * // Returns: true (exact domain match)
32
+ *
33
+ * const isValid2 = isValidRedirectUrl("https://api.example.com/callback", allowedDomains);
34
+ * // Returns: true (subdomain of allowed domain)
35
+ *
36
+ * const isValid3 = isValidRedirectUrl("https://malicious.com/phishing", allowedDomains);
37
+ * // Returns: false (domain not in allowed list)
38
+ * ```
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * // Authentication redirect validation
43
+ * function handleAuthRedirect(redirectUrl: string): void {
44
+ * const trustedDomains = [
45
+ * "myapp.com",
46
+ * "admin.myapp.com",
47
+ * "localhost" // for development
48
+ * ];
49
+ *
50
+ * if (isValidRedirectUrl(redirectUrl, trustedDomains)) {
51
+ * window.location.href = redirectUrl;
52
+ * } else {
53
+ * // Redirect to safe default instead
54
+ * window.location.href = "/dashboard";
55
+ * console.warn("Attempted redirect to untrusted domain:", redirectUrl);
56
+ * }
57
+ * }
58
+ * ```
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * // API endpoint validation
63
+ * app.post('/auth/login', (req, res) => {
64
+ * const { username, password, redirectUrl } = req.body;
65
+ *
66
+ * if (authenticateUser(username, password)) {
67
+ * const allowedDomains = ["myapp.com", "partner.com"];
68
+ *
69
+ * if (redirectUrl && isValidRedirectUrl(redirectUrl, allowedDomains)) {
70
+ * res.json({ success: true, redirectUrl });
71
+ * } else {
72
+ * res.json({ success: true, redirectUrl: "/dashboard" });
73
+ * }
74
+ * } else {
75
+ * res.status(401).json({ error: "Invalid credentials" });
76
+ * }
77
+ * });
78
+ * ```
79
+ *
80
+ * @example
81
+ * ```typescript
82
+ * // Complex subdomain scenarios
83
+ * const domains = ["company.com"];
84
+ *
85
+ * isValidRedirectUrl("https://app.company.com", domains); // true
86
+ * isValidRedirectUrl("https://api.app.company.com", domains); // true
87
+ * isValidRedirectUrl("https://company.com.evil.com", domains); // false
88
+ * isValidRedirectUrl("https://fakecompany.com", domains); // false
89
+ * ```
90
+ *
91
+ * @remarks
92
+ * - Uses the native URL constructor for reliable URL parsing
93
+ * - Supports both exact domain matches and subdomain matches
94
+ * - Returns false for invalid URLs that cannot be parsed
95
+ * - Domain matching is case-insensitive (handled by URL constructor)
96
+ * - Does not validate the protocol - focuses only on hostname validation
97
+ * - Subdomain matching uses endsWith() to ensure proper domain boundaries
98
+ * - Empty or null URL strings will cause the function to return false
99
+ *
100
+ * @throws {never} This function catches all URL parsing errors and returns false instead of throwing
101
+ */
102
+ function isValidRedirectUrl(url, allowedDomains) {
103
+ try {
104
+ const parsedUrl = new URL(url);
105
+ return allowedDomains.some(domain => parsedUrl.hostname === domain || parsedUrl.hostname.endsWith(`.${domain}`));
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+
111
+ /* eslint-disable @typescript-eslint/no-explicit-any */
112
+ // noinspection JSUnusedGlobalSymbols
113
+ /**
114
+ * Deep copy an object.
115
+ * @template T Type of the object.
116
+ * @param {T} obj Object to copy.
117
+ * @returns {T} Copied object.
118
+ *
119
+ * @example
120
+ * ```TypeScript
121
+ * // Example usage
122
+ * const object = {
123
+ * name: "John Doe"
124
+ * }
125
+ *
126
+ * const copiedObject = deepCopy(object);
127
+ * ```
128
+ */
129
+ function deepCopy(obj) {
130
+ if (Array.isArray(obj)) {
131
+ return obj.map(deepCopy);
132
+ } else if (typeof obj === "object" && obj !== null) {
133
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, deepCopy(value)]));
134
+ }
135
+ return obj;
136
+ }
137
+ /**
138
+ * Get the key of a map by value.
139
+ * @param {Map<string, unknown>} map Map to get key from.
140
+ * @param {unknown} value Value to get key for.
141
+ * @returns {string | undefined} Key of the map.
142
+ *
143
+ * @example
144
+ * ```TypeScript
145
+ * // Example usage
146
+ * const user = new Map<string, string>([
147
+ * ["firstName", "John"],
148
+ * ["lastName", "Doe"],
149
+ * ["preferredName", "John"],
150
+ * ["age", 30],
151
+ * ]);
152
+ *
153
+ * const key = getMapKey(user, "value2");
154
+ *
155
+ * // Example output: "firstName"
156
+ * ```
157
+ */
158
+ function getMapKey(map, value) {
159
+ return [...Array.from(map.entries())].find(([, v]) => v === value)?.[0];
160
+ }
161
+ /**
162
+ * Retrieves all keys from a Map where the corresponding values contain a specified substring.
163
+ *
164
+ * This utility function searches through a Map's values and returns an array of keys
165
+ * whose associated values include the provided partial string. The search is case-sensitive
166
+ * and uses JavaScript's native string.includes() method for matching.
167
+ *
168
+ * This is particularly useful for implementing search functionality, filtering operations,
169
+ * or finding related entries in configuration maps, user preference stores, or any
170
+ * key-value data structure where you need to locate entries by partial value matching.
171
+ *
172
+ * @param {Map<string, string>} map - The Map to search through for matching values
173
+ * @param {string} partialValue - The substring to search for within the Map's values
174
+ * @returns {string[]} An array of keys whose corresponding values contain the partial value
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * // Search user preferences for entries containing "John"
179
+ * const userPreferences = new Map<string, string>([
180
+ * ["displayName", "John Doe"],
181
+ * ["firstName", "John"],
182
+ * ["lastName", "Doe"],
183
+ * ["preferredName", "Johnny"],
184
+ * ["email", "john.doe@example.com"]
185
+ * ]);
186
+ *
187
+ * const johnKeys = getMapKeys(userPreferences, "John");
188
+ * // Returns: ["displayName", "firstName", "email"]
189
+ * ```
190
+ *
191
+ * @example
192
+ * ```typescript
193
+ * // Find configuration keys related to database settings
194
+ * const config = new Map<string, string>([
195
+ * ["db_host", "localhost"],
196
+ * ["db_port", "5432"],
197
+ * ["cache_host", "redis-server"],
198
+ * ["db_name", "myapp_database"],
199
+ * ["log_level", "debug"]
200
+ * ]);
201
+ *
202
+ * const dbKeys = getMapKeys(config, "db");
203
+ * // Returns: ["db_name"] (only values containing "db", not keys)
204
+ * ```
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * // Search in a translations map
209
+ * const translations = new Map<string, string>([
210
+ * ["welcome_message", "Welcome to our application"],
211
+ * ["error_message", "An error occurred"],
212
+ * ["success_message", "Operation completed successfully"],
213
+ * ["info_message", "Please check your information"]
214
+ * ]);
215
+ *
216
+ * const messageKeys = getMapKeys(translations, "message");
217
+ * // Returns: ["welcome_message", "error_message", "success_message", "info_message"]
218
+ * ```
219
+ *
220
+ * @remarks
221
+ * - The search is case-sensitive; "John" will not match "john"
222
+ * - Returns an empty array if no matches are found
223
+ * - The function iterates through all Map entries, so performance scales with Map size
224
+ * - Only works with Maps that have string values; other value types will cause runtime errors
225
+ */
226
+ const getMapKeys = (map, partialValue) => {
227
+ const keys = [];
228
+ for (const [key, value] of Array.from(map.entries())) {
229
+ if (value.includes(partialValue)) {
230
+ keys.push(key);
231
+ }
232
+ }
233
+ return keys;
234
+ };
235
+ /**
236
+ * Groups an array of objects into a Map based on a key extraction function.
237
+ *
238
+ * This utility function takes an array of objects and organizes them into groups
239
+ * based on a common key or property. The grouping is performed using a key extraction
240
+ * function that you provide, which determines how each object should be categorized.
241
+ * The result is a Map where each key represents a group and the value is an array
242
+ * of objects belonging to that group.
243
+ *
244
+ * This is particularly useful for data analysis, reporting, organizing collections
245
+ * for display purposes, or preparing data for further processing where items need
246
+ * to be categorized by shared characteristics.
247
+ *
248
+ * @template K - The type of the grouping key (can be string, number, boolean, etc.)
249
+ * @template V - The type of the objects being grouped
250
+ * @param {Array<V>} list - The array of objects to group
251
+ * @param {(input: V) => K} keyGetter - A function that extracts the grouping key from each object.
252
+ * This function receives an object and should return the value
253
+ * to group by. If the function returns null or undefined,
254
+ * the object will be grouped under a null key.
255
+ * @returns {Map<K | null, Array<V>>} A Map where keys are the grouping values and values are arrays
256
+ * of objects that share that grouping key
257
+ *
258
+ * @example
259
+ * ```typescript
260
+ * // Group users by age
261
+ * interface User {
262
+ * name: string;
263
+ * age: number;
264
+ * department: string;
265
+ * }
266
+ *
267
+ * const users: User[] = [
268
+ * { name: "John", age: 30, department: "Engineering" },
269
+ * { name: "Jane", age: 25, department: "Marketing" },
270
+ * { name: "Bob", age: 30, department: "Engineering" },
271
+ * { name: "Alice", age: 25, department: "Sales" },
272
+ * { name: "Charlie", age: 35, department: "Engineering" }
273
+ * ];
274
+ *
275
+ * const usersByAge = groupBy(users, user => user.age);
276
+ * // Returns:
277
+ * // Map {
278
+ * // 30 => [
279
+ * // { name: "John", age: 30, department: "Engineering" },
280
+ * // { name: "Bob", age: 30, department: "Engineering" }
281
+ * // ],
282
+ * // 25 => [
283
+ * // { name: "Jane", age: 25, department: "Marketing" },
284
+ * // { name: "Alice", age: 25, department: "Sales" }
285
+ * // ],
286
+ * // 35 => [
287
+ * // { name: "Charlie", age: 35, department: "Engineering" }
288
+ * // ]
289
+ * // }
290
+ * ```
291
+ *
292
+ * @example
293
+ * ```typescript
294
+ * // Group products by category
295
+ * interface Product {
296
+ * id: number;
297
+ * name: string;
298
+ * category: string;
299
+ * price: number;
300
+ * }
301
+ *
302
+ * const products: Product[] = [
303
+ * { id: 1, name: "Laptop", category: "Electronics", price: 999 },
304
+ * { id: 2, name: "Book", category: "Education", price: 29 },
305
+ * { id: 3, name: "Phone", category: "Electronics", price: 699 },
306
+ * { id: 4, name: "Pen", category: "Office", price: 5 }
307
+ * ];
308
+ *
309
+ * const productsByCategory = groupBy(products, product => product.category);
310
+ * // Useful for creating category-based product listings
311
+ * ```
312
+ *
313
+ * @example
314
+ * ```typescript
315
+ * // Group tasks by completion status with boolean keys
316
+ * interface Task {
317
+ * id: number;
318
+ * title: string;
319
+ * completed: boolean;
320
+ * }
321
+ *
322
+ * const tasks: Task[] = [
323
+ * { id: 1, title: "Review code", completed: true },
324
+ * { id: 2, title: "Write tests", completed: false },
325
+ * { id: 3, title: "Deploy app", completed: true },
326
+ * { id: 4, title: "Update docs", completed: false }
327
+ * ];
328
+ *
329
+ * const tasksByStatus = groupBy(tasks, task => task.completed);
330
+ * // Returns Map with boolean keys: true and false
331
+ * ```
332
+ *
333
+ * @remarks
334
+ * - The function preserves the original order of objects within each group
335
+ * - If the keyGetter function returns null or undefined, those objects will be grouped under a null key
336
+ * - The Map structure allows for efficient lookups and iteration over groups
337
+ * - Works with any type of grouping key (string, number, boolean, objects, etc.)
338
+ * - Empty arrays will return an empty Map
339
+ */
340
+ const groupBy = (list, keyGetter) => {
341
+ const map = new Map();
342
+ list.forEach(item => {
343
+ const key = keyGetter(item);
344
+ const collection = map.get(key);
345
+ if (!collection) {
346
+ map.set(key, [item]);
347
+ } else {
348
+ collection.push(item);
349
+ }
350
+ });
351
+ return map;
352
+ };
353
+ /**
354
+ * Retrieves all values from a Map that contain a specified substring.
355
+ *
356
+ * This utility function searches through a Map's values and returns an array of all values
357
+ * that include the provided partial string. The search is case-sensitive and uses JavaScript's
358
+ * native string.includes() method for matching. This is the counterpart to getMapKeys(),
359
+ * but instead of returning the keys, it returns the actual values that match.
360
+ *
361
+ * This is particularly useful for implementing search functionality where you need to find
362
+ * and display matching content, filtering data for autocomplete features, or extracting
363
+ * related values from configuration maps, user data, or any key-value store where you
364
+ * need to locate entries by partial content matching.
365
+ *
366
+ * @param {Map<string, string>} map - The Map to search through for matching values
367
+ * @param {string} partialValue - The substring to search for within the Map's values
368
+ * @returns {string[]} An array of values that contain the specified partial value
369
+ *
370
+ * @example
371
+ * ```typescript
372
+ * // Search user information for values containing "John"
373
+ * const userInfo = new Map<string, string>([
374
+ * ["fullName", "John Doe"],
375
+ * ["firstName", "John"],
376
+ * ["lastName", "Doe"],
377
+ * ["nickname", "Johnny"],
378
+ * ["email", "john.doe@example.com"],
379
+ * ["phone", "555-0123"]
380
+ * ]);
381
+ *
382
+ * const johnValues = searchMapValues(userInfo, "John");
383
+ * // Returns: ["John Doe", "John", "Johnny", "john.doe@example.com"]
384
+ * ```
385
+ *
386
+ * @example
387
+ * ```typescript
388
+ * // Find error messages containing specific keywords
389
+ * const errorMessages = new Map<string, string>([
390
+ * ["auth_failed", "Authentication failed. Please check your credentials."],
391
+ * ["network_error", "Network connection failed. Please try again."],
392
+ * ["validation_error", "Validation failed for the provided data."],
393
+ * ["timeout_error", "Request timeout. The server took too long to respond."],
394
+ * ["permission_denied", "Access denied. You don't have permission."]
395
+ * ]);
396
+ *
397
+ * const failedMessages = searchMapValues(errorMessages, "failed");
398
+ * // Returns: ["Authentication failed. Please check your credentials.",
399
+ * // "Network connection failed. Please try again.",
400
+ * // "Validation failed for the provided data."]
401
+ * ```
402
+ *
403
+ * @example
404
+ * ```typescript
405
+ * // Search configuration values for environment-specific settings
406
+ * const config = new Map<string, string>([
407
+ * ["api_url", "https://api.production.example.com"],
408
+ * ["db_host", "production-db.example.com"],
409
+ * ["cache_url", "redis://production-cache.example.com"],
410
+ * ["log_level", "error"],
411
+ * ["debug_mode", "false"]
412
+ * ]);
413
+ *
414
+ * const productionValues = searchMapValues(config, "production");
415
+ * // Returns: ["https://api.production.example.com",
416
+ * // "production-db.example.com",
417
+ * // "redis://production-cache.example.com"]
418
+ * ```
419
+ *
420
+ * @remarks
421
+ * - The search is case-sensitive; "John" will not match "john"
422
+ * - Returns an empty array if no matches are found
423
+ * - The function iterates through all Map entries, so performance scales with Map size
424
+ * - Only works with Maps that have string values; other value types will cause runtime errors
425
+ * - Values are returned in the order they appear in the Map iteration
426
+ *
427
+ * @see {@link getMapKeys} Function to get keys whose values contain a substring
428
+ */
429
+ const searchMapValues = (map, partialValue) => {
430
+ const values = [];
431
+ for (const [, value] of Array.from(map.entries())) {
432
+ if (value.includes(partialValue)) {
433
+ values.push(value);
434
+ }
435
+ }
436
+ return values;
437
+ };
438
+ /**
439
+ * Gets a value from a nested object using a dot-notation path string.
440
+ *
441
+ * This function safely traverses a deeply nested object structure using a string path
442
+ * with dot notation. It handles both object properties and array indices within the path.
443
+ *
444
+ * @template T - Type of the value to be returned.
445
+ * @param {InfiniteObject} obj - The object to retrieve the value from.
446
+ * @param {string} path - The dot-notation path to the desired value.
447
+ * - Use dots to navigate through nested objects: 'user.profile.address'
448
+ * - Use array notation for accessing array elements: 'items[0]' or 'users[2].name'
449
+ * @returns {T | undefined} - The value at the specified path, or undefined if:
450
+ * - Any part of the path doesn't exist
451
+ * - An array index is out of bounds
452
+ * - The path format is invalid
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * // Simple nested object property
457
+ * const user = {
458
+ * profile: {
459
+ * name: "John Doe",
460
+ * contact: { email: "john@example.com" }
461
+ * }
462
+ * };
463
+ * const email = getValueByPath<string>(user, "profile.contact.email");
464
+ * // Returns: "john@example.com"
465
+ * ```
466
+ *
467
+ * @example
468
+ * ```typescript
469
+ * // Accessing array elements
470
+ * const data = {
471
+ * users: [
472
+ * { id: 1, name: "Alice" },
473
+ * { id: 2, name: "Bob" }
474
+ * ]
475
+ * };
476
+ * const name = getValueByPath<string>(data, "users[1].name");
477
+ * // Returns: "Bob"
478
+ * ```
479
+ */
480
+ const getValueByPath = (obj, path) => {
481
+ const keys = path.split("."); // Split the path into an array of keys
482
+ let value = obj;
483
+ for (const key of keys) {
484
+ // noinspection RegExpRedundantEscape
485
+ const regExp = /^(\w+)\[(\d+)\]$/;
486
+ const isArrayIndex = regExp.exec(key); // Check if the key is an array index
487
+ if (isArrayIndex) {
488
+ const arrayKey = isArrayIndex[1];
489
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
490
+ const index = Number(isArrayIndex[2]);
491
+ if (arrayKey && Array.isArray(value[arrayKey]) && index >= 0 && index < value[arrayKey].length) {
492
+ value = value[arrayKey][index]; // Update the value to the array element
493
+ } else {
494
+ return undefined; // Return undefined if the array index is invalid
495
+ }
496
+ } else if (value && typeof value === "object" && key in value) {
497
+ value = value[key]; // Update the value to the nested property
498
+ } else {
499
+ return undefined; // Return undefined if any key is not found
500
+ }
501
+ }
502
+ return value;
503
+ };
504
+ /**
505
+ * Converts a nested object into a flattened DottedPathValueObject representation.
506
+ *
507
+ * This function transforms a hierarchical object structure into a flat key-value map
508
+ * where keys represent paths to values in the original object using dot notation.
509
+ *
510
+ * The function recursively traverses the object and flattens nested properties,
511
+ * converting object hierarchies like `{ user: { name: 'John' } }` into
512
+ * path-based entries like `{ 'user.name': 'John' }`.
513
+ *
514
+ * @param {LiteralObject} obj - The nested object to flatten
515
+ * @returns {DottedPathValueObject} - A flattened representation where:
516
+ * - Keys are dot-notation paths to values in the original object
517
+ * - Values are primitive values (strings, numbers, booleans) from the original object
518
+ *
519
+ * @remarks
520
+ * - Array values will be preserved as-is (not flattened into separate paths)
521
+ * - Only primitive values (string, number, boolean) are supported as leaf values
522
+ * - Circular references are not handled and will cause a stack overflow
523
+ *
524
+ * @see {@link dottedPathObjectToNested} The inverse operation to convert a DottedPathValueObject back to a nested object
525
+ * @see {@link DottedPathValueObject} The interface for the returned flattened object
526
+ *
527
+ * @example
528
+ * ```typescript
529
+ * // Flatten a nested user object
530
+ * const user = {
531
+ * id: 123,
532
+ * name: "John Doe",
533
+ * isActive: true,
534
+ * profile: {
535
+ * age: 30,
536
+ * address: {
537
+ * city: "New York",
538
+ * zip: "10001"
539
+ * }
540
+ * }
541
+ * };
542
+ *
543
+ * const flattened = objectToDottedPathValueObject(user);
544
+ *
545
+ * // Result:
546
+ * // {
547
+ * // "id": 123,
548
+ * // "name": "John Doe",
549
+ * // "isActive": true,
550
+ * // "profile.age": 30,
551
+ * // "profile.address.city": "New York",
552
+ * // "profile.address.zip": "10001"
553
+ * // }
554
+ * ```
555
+ */
556
+ function objectToDottedPathValueObject(obj) {
557
+ const result = {};
558
+ function traverse(obj, path = []) {
559
+ for (const key in obj) {
560
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
561
+ const value = obj[key];
562
+ if (typeof value === "object" && !Array.isArray(value)) {
563
+ traverse(value, [...path, key]);
564
+ } else {
565
+ result[[...path, key].join(".")] = value;
566
+ }
567
+ }
568
+ }
569
+ }
570
+ traverse(obj);
571
+ return result;
572
+ }
573
+ /**
574
+ * Converts a flattened DottedPathValueObject back into a nested object structure.
575
+ *
576
+ * This function is the inverse of `objectToDottedPathValueObject`. It takes a flat map of
577
+ * dot-notation paths to values and reconstructs a hierarchical object structure.
578
+ *
579
+ * Each key in the input DottedPathValueObject represents a path through the object hierarchy,
580
+ * with dots separating each level. The function builds a nested object structure by
581
+ * parsing these paths and placing values at the appropriate locations.
582
+ *
583
+ * @template R - The type of the returned object (defaults to object)
584
+ * @param {DottedPathValueObject} pathValueSet - A flattened object with dot-notation path keys
585
+ * @returns {R} - A reconstructed nested object with the original hierarchy
586
+ *
587
+ * @remarks
588
+ * - Paths are validated for safety to prevent injection attacks
589
+ * - Invalid paths are silently skipped (not included in the result)
590
+ * - Path components should contain only alphanumeric characters, underscores, hyphens, and dots
591
+ *
592
+ * @see {@link objectToDottedPathValueObject} The inverse operation to convert an object to DottedPathValueObject
593
+ * @see {@link DottedPathValueObject} The interface for the input flattened object
594
+ *
595
+ * @example
596
+ * ```typescript
597
+ * // Convert a flat DottedPathValueObject to a nested object
598
+ * const flatData = {
599
+ * "id": 123,
600
+ * "name": "John Doe",
601
+ * "profile.age": 30,
602
+ * "profile.address.city": "New York",
603
+ * "profile.address.zip": "10001"
604
+ * };
605
+ *
606
+ * const nestedObject = dottedPathObjectToNested(flatData);
607
+ *
608
+ * Result:
609
+ * // {
610
+ * // id: 123,
611
+ * // name: "John Doe",
612
+ * // profile: {
613
+ * // age: 30,
614
+ * // address: {
615
+ * // city: "New York",
616
+ * // zip: "10001"
617
+ * // }
618
+ * // }
619
+ * // }
620
+ * ```
621
+ *
622
+ * @example
623
+ * ```typescript
624
+ * // Typed return value
625
+ * interface User {
626
+ * id: number;
627
+ * name: string;
628
+ * profile: {
629
+ * age: number;
630
+ * address: {
631
+ * city: string;
632
+ * zip: string;
633
+ * };
634
+ * };
635
+ * }
636
+ *
637
+ * const userData = dottedPathObjectToNested<User>(flatData);
638
+ * // Returns object with User type
639
+ * ```
640
+ */
641
+ function dottedPathObjectToNested(pathValueSet) {
642
+ const object = {};
643
+ // Helper function to validate paths
644
+ const isValidPath = path => {
645
+ const regex = /^[a-zA-Z0-9_.-]+$/;
646
+ return path.split(".").every(part => regex.test(part));
647
+ };
648
+ // Helper function to set nested properties
649
+ const setObjectValue = (obj, keys, value) => {
650
+ const [firstKey, ...remainingKeys] = keys;
651
+ if (remainingKeys.length === 0) {
652
+ obj[firstKey] = value; // Set value at the final key
653
+ return;
654
+ }
655
+ // Initialize the key if it doesn't exist
656
+ obj[firstKey] = obj[firstKey] || {};
657
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
658
+ setObjectValue(obj[firstKey], remainingKeys, value); // Recurse for the rest of the keys
659
+ // TODO: v2.0 Fix type for above
660
+ };
661
+ for (const path in pathValueSet) {
662
+ if (Object.prototype.hasOwnProperty.call(pathValueSet, path)) {
663
+ if (!isValidPath(path)) {
664
+ continue; // Skip invalid paths
665
+ }
666
+ const value = pathValueSet[path];
667
+ const keys = path.split("."); // Split path into keys
668
+ setObjectValue(object, keys, value); // Use helper to populate the object
669
+ }
670
+ }
671
+ return object;
672
+ }
673
+ /**
674
+ * Creates a new object by omitting specified properties and undefined values from the original object.
675
+ *
676
+ * This utility function performs a selective copy of an object, excluding both explicitly
677
+ * specified properties and any properties with undefined values. The function modifies
678
+ * the original object in-place by deleting the unwanted properties, making it useful for
679
+ * cleaning up objects before serialization, API calls, or data processing.
680
+ *
681
+ * This is particularly useful for preparing data for API requests where you need to remove
682
+ * sensitive fields, clean up form data, or exclude certain properties from being sent to
683
+ * external services. It's also helpful for removing undefined values that might cause
684
+ * issues in JSON serialization or database operations.
685
+ *
686
+ * @template T - The type of the object being processed
687
+ * @param {Partial<T>} obj - The object to process and remove properties from.
688
+ * This object will be modified in-place.
689
+ * @param {(keyof T)[]} [keys] - Optional array of property keys to omit from the object.
690
+ * If not provided, only undefined properties will be removed.
691
+ * @returns {Partial<T>} The same object reference with specified properties and undefined values removed
692
+ *
693
+ * @example
694
+ * ```typescript
695
+ * // Remove specific properties and undefined values
696
+ * interface User {
697
+ * id: number;
698
+ * name: string;
699
+ * email: string;
700
+ * role: string;
701
+ * address?: string;
702
+ * phone?: string;
703
+ * }
704
+ *
705
+ * const user: Partial<User> = {
706
+ * id: 123,
707
+ * name: "John Doe",
708
+ * email: "john@example.com",
709
+ * role: "admin",
710
+ * address: undefined,
711
+ * phone: "555-0123"
712
+ * };
713
+ *
714
+ * const cleanUser = omit(user, ["role"]);
715
+ * // Returns: { id: 123, name: "John Doe", email: "john@example.com", phone: "555-0123" }
716
+ * // Note: 'role' and 'address' (undefined) are removed
717
+ * ```
718
+ *
719
+ * @example
720
+ * ```typescript
721
+ * // Remove only undefined values (no specific keys)
722
+ * const formData = {
723
+ * firstName: "John",
724
+ * lastName: "Doe",
725
+ * middleName: undefined,
726
+ * email: "john@example.com",
727
+ * phone: undefined
728
+ * };
729
+ *
730
+ * const cleanFormData = omit(formData);
731
+ * // Returns: { firstName: "John", lastName: "Doe", email: "john@example.com" }
732
+ * ```
733
+ *
734
+ * @example
735
+ * ```typescript
736
+ * // Prepare data for API request by removing sensitive fields
737
+ * interface UserProfile {
738
+ * id: number;
739
+ * username: string;
740
+ * email: string;
741
+ * password: string;
742
+ * internalNotes: string;
743
+ * createdAt: Date;
744
+ * }
745
+ *
746
+ * const userProfile: Partial<UserProfile> = {
747
+ * id: 1,
748
+ * username: "johndoe",
749
+ * email: "john@example.com",
750
+ * password: "secret123",
751
+ * internalNotes: "VIP customer",
752
+ * createdAt: new Date()
753
+ * };
754
+ *
755
+ * const publicProfile = omit(userProfile, ["password", "internalNotes"]);
756
+ * // Safe to send to client: { id: 1, username: "johndoe", email: "john@example.com", createdAt: Date }
757
+ * ```
758
+ *
759
+ * @remarks
760
+ * - This function modifies the original object in-place rather than creating a copy
761
+ * - Both explicitly specified keys and undefined properties are removed
762
+ * - If the input object is falsy (null, undefined), it's returned as-is
763
+ * - The function uses delete operator, which may affect object performance in some JavaScript engines
764
+ * - Properties with null values are preserved (only undefined values are automatically removed)
765
+ *
766
+ * @see {@link prune} Function for more comprehensive object cleaning including null and empty values
767
+ */
768
+ const omit = (obj, keys) => {
769
+ if (obj) {
770
+ Object.keys(obj).forEach(key => {
771
+ return (obj[key] === undefined || keys?.includes(key)) && delete obj[key];
772
+ });
773
+ }
774
+ return obj;
775
+ };
776
+ /**
777
+ * Creates a deep copy of an object with all empty, null, undefined, and optionally prototype properties removed.
778
+ *
779
+ * This utility function recursively traverses an object and creates a clean copy by excluding
780
+ * properties that are null, undefined, or empty strings. It performs a deep cleaning operation,
781
+ * processing nested objects recursively to ensure that the entire object hierarchy is pruned
782
+ * of unwanted values.
783
+ *
784
+ * This is particularly useful for preparing data for serialization, cleaning up API responses,
785
+ * removing empty form fields, or ensuring that only meaningful data is processed or stored.
786
+ * The function is especially valuable when working with deeply nested objects that may contain
787
+ * sparse data or when you need to eliminate all traces of empty values from complex data structures.
788
+ *
789
+ * @template T - The type of the object being pruned
790
+ * @param {PartialWithNull<T>} obj - The object to prune. Can contain null, undefined, or empty values.
791
+ * @param {boolean} [omitPrototype=false] - Whether to exclude inherited properties from the prototype chain.
792
+ * If true, only own properties will be included in the result.
793
+ * If false (default), inherited enumerable properties will be processed.
794
+ * @returns {T} A new object with all empty, null, and undefined properties recursively removed
795
+ *
796
+ * @example
797
+ * ```typescript
798
+ * // Clean up a user profile with nested empty values
799
+ * interface UserProfile {
800
+ * id: number;
801
+ * name: string;
802
+ * contact: {
803
+ * email: string;
804
+ * phone?: string;
805
+ * address?: {
806
+ * street: string;
807
+ * city: string;
808
+ * zip?: string;
809
+ * };
810
+ * };
811
+ * preferences: {
812
+ * theme: string;
813
+ * notifications?: boolean;
814
+ * };
815
+ * }
816
+ *
817
+ * const userProfile = {
818
+ * id: 123,
819
+ * name: "John Doe",
820
+ * contact: {
821
+ * email: "john@example.com",
822
+ * phone: "",
823
+ * address: {
824
+ * street: "123 Main St",
825
+ * city: "New York",
826
+ * zip: null
827
+ * }
828
+ * },
829
+ * preferences: {
830
+ * theme: "dark",
831
+ * notifications: undefined
832
+ * }
833
+ * };
834
+ *
835
+ * const cleanProfile = prune(userProfile);
836
+ * // Returns:
837
+ * // {
838
+ * // id: 123,
839
+ * // name: "John Doe",
840
+ * // contact: {
841
+ * // email: "john@example.com",
842
+ * // address: {
843
+ * // street: "123 Main St",
844
+ * // city: "New York"
845
+ * // }
846
+ * // },
847
+ * // preferences: {
848
+ * // theme: "dark"
849
+ * // }
850
+ * // }
851
+ * ```
852
+ *
853
+ * @example
854
+ * ```typescript
855
+ * // Clean up form data before submission
856
+ * const formData = {
857
+ * firstName: "John",
858
+ * lastName: "Doe",
859
+ * middleName: "",
860
+ * email: "john@example.com",
861
+ * phone: null,
862
+ * address: {
863
+ * street: "123 Main St",
864
+ * apartment: "",
865
+ * city: "New York",
866
+ * state: undefined,
867
+ * zip: "10001"
868
+ * },
869
+ * emergencyContact: {
870
+ * name: "",
871
+ * phone: null
872
+ * }
873
+ * };
874
+ *
875
+ * const cleanFormData = prune(formData);
876
+ * // Returns only fields with meaningful values:
877
+ * // {
878
+ * // firstName: "John",
879
+ * // lastName: "Doe",
880
+ * // email: "john@example.com",
881
+ * // address: {
882
+ * // street: "123 Main St",
883
+ * // city: "New York",
884
+ * // zip: "10001"
885
+ * // }
886
+ * // }
887
+ * // Note: emergencyContact object is completely removed as all its properties were empty
888
+ * ```
889
+ *
890
+ * @example
891
+ * ```typescript
892
+ * // Control prototype property inclusion
893
+ * class BaseUser {
894
+ * role = "user";
895
+ * }
896
+ *
897
+ * class ExtendedUser extends BaseUser {
898
+ * name = "John";
899
+ * email = "";
900
+ * }
901
+ *
902
+ * const user = new ExtendedUser();
903
+ *
904
+ * const prunedWithPrototype = prune(user, false);
905
+ * // Includes inherited 'role' property: { role: "user", name: "John" }
906
+ *
907
+ * const prunedOwnOnly = prune(user, true);
908
+ * // Only own properties: { name: "John" }
909
+ * ```
910
+ *
911
+ * @remarks
912
+ * - Creates a new object rather than modifying the original (unlike the omit function)
913
+ * - Recursively processes nested objects to ensure deep cleaning
914
+ * - Removes properties with values: null, undefined, or empty string ("")
915
+ * - Preserves properties with falsy but meaningful values like 0, false
916
+ * - If a nested object becomes empty after pruning, it will be excluded from the result
917
+ * - Non-object types are returned as an empty object of the target type
918
+ * - The function preserves the type structure while removing empty values
919
+ *
920
+ * @see {@link omit} Function for selective property removal without deep cleaning
921
+ */
922
+ const prune = (obj, omitPrototype) => {
923
+ const objClone = {};
924
+ if (typeof obj !== "object") {
925
+ return objClone;
926
+ }
927
+ for (const key in obj) {
928
+ if (!omitPrototype || Object.prototype.hasOwnProperty.call(obj, key)) {
929
+ if (obj[key] !== null && typeof obj[key] === "object") {
930
+ objClone[key] = prune(obj[key], omitPrototype);
931
+ } else if (obj[key] !== null && obj[key] !== undefined && obj[key] !== "") {
932
+ objClone[key] = obj[key];
933
+ }
934
+ }
935
+ }
936
+ return objClone;
937
+ };
938
+ /**
939
+ * Checks if an object has all specified properties as its own properties (not inherited).
940
+ *
941
+ * This utility function verifies that an object contains all the properties listed in the
942
+ * provided array as direct (own) properties, not inherited from the prototype chain.
943
+ * It uses the modern Object.hasOwn() method for reliable property existence checking,
944
+ * making it safer than using hasOwnProperty() directly.
945
+ *
946
+ * This is particularly useful for validating object structures, ensuring required properties
947
+ * exist before processing, implementing type guards, or verifying that objects conform to
948
+ * expected interfaces. It's commonly used in data validation, API request validation,
949
+ * and ensuring objects have all necessary properties before performing operations.
950
+ *
951
+ * @param {Record<PropertyKey, unknown>} obj - The object to check for property existence
952
+ * @param {PropertyKey[]} props - Array of property keys to verify exist on the object.
953
+ * PropertyKey includes string, number, and symbol types.
954
+ * @returns {boolean} True if the object has all specified properties as own properties, false otherwise
955
+ *
956
+ * @example
957
+ * ```typescript
958
+ * // Validate that a user object has required properties
959
+ * interface User {
960
+ * id: number;
961
+ * name: string;
962
+ * email: string;
963
+ * }
964
+ *
965
+ * const user = {
966
+ * id: 123,
967
+ * name: "John Doe",
968
+ * email: "john@example.com",
969
+ * role: "admin"
970
+ * };
971
+ *
972
+ * const hasRequiredProps = hasOwnAll(user, ["id", "name", "email"]);
973
+ * // Returns: true
974
+ *
975
+ * const hasAllProps = hasOwnAll(user, ["id", "name", "email", "phone"]);
976
+ * // Returns: false (missing 'phone' property)
977
+ * ```
978
+ *
979
+ * @example
980
+ * ```typescript
981
+ * // Validate API request payload
982
+ * function processUserUpdate(payload: unknown): User | null {
983
+ * if (typeof payload === 'object' && payload !== null) {
984
+ * const requiredFields = ['id', 'name', 'email'];
985
+ *
986
+ * if (hasOwnAll(payload as Record<string, unknown>, requiredFields)) {
987
+ * // Safe to process as we know all required fields exist
988
+ * return updateUser(payload as User);
989
+ * }
990
+ * }
991
+ *
992
+ * throw new Error('Invalid payload: missing required fields');
993
+ * }
994
+ * ```
995
+ *
996
+ * @example
997
+ * ```typescript
998
+ * // Check configuration object completeness
999
+ * interface DatabaseConfig {
1000
+ * host: string;
1001
+ * port: number;
1002
+ * database: string;
1003
+ * username: string;
1004
+ * password: string;
1005
+ * }
1006
+ *
1007
+ * const config = {
1008
+ * host: "localhost",
1009
+ * port: 5432,
1010
+ * database: "myapp",
1011
+ * username: "admin"
1012
+ * // password is missing
1013
+ * };
1014
+ *
1015
+ * const requiredConfigKeys = ['host', 'port', 'database', 'username', 'password'];
1016
+ * const isConfigComplete = hasOwnAll(config, requiredConfigKeys);
1017
+ * // Returns: false
1018
+ *
1019
+ * if (!isConfigComplete) {
1020
+ * throw new Error('Database configuration is incomplete');
1021
+ * }
1022
+ * ```
1023
+ *
1024
+ * @example
1025
+ * ```typescript
1026
+ * // Work with symbol properties
1027
+ * const symbolKey = Symbol('secret');
1028
+ * const obj = {
1029
+ * name: "test",
1030
+ * [symbolKey]: "hidden value"
1031
+ * };
1032
+ *
1033
+ * const hasSymbolProp = hasOwnAll(obj, ['name', symbolKey]);
1034
+ * // Returns: true
1035
+ * ```
1036
+ *
1037
+ * @remarks
1038
+ * - Uses Object.hasOwn() which is more reliable than hasOwnProperty()
1039
+ * - Only checks for own properties, not inherited properties from the prototype chain
1040
+ * - Returns false immediately if any property is missing (short-circuit evaluation)
1041
+ * - Works with string, number, and symbol property keys
1042
+ * - Returns true for an empty properties array (vacuous truth)
1043
+ * - Does not check property values, only existence
1044
+ *
1045
+ * @see {@link Object.hasOwn} The underlying method used for property checking
1046
+ */
1047
+ function hasOwnAll(obj, props) {
1048
+ return props.every(prop => Object.hasOwn(obj, prop));
1049
+ }
1050
+ /**
1051
+ * Extracts all values from a TypeScript enum, handling both string and numeric enum types correctly.
1052
+ *
1053
+ * This utility function safely retrieves the actual values from TypeScript enums, accounting for
1054
+ * the different internal representations of string and numeric enums. For numeric enums, TypeScript
1055
+ * creates reverse mappings (value → key), so this function filters out the reverse mapping entries
1056
+ * to return only the actual enum values. For string enums, it returns all values directly.
1057
+ *
1058
+ * This is particularly useful when you need to iterate over enum values, validate input against
1059
+ * enum values, create dropdown options from enums, or perform any operation that requires access
1060
+ * to the actual enum values rather than the keys.
1061
+ *
1062
+ * @template T - The enum type extending object
1063
+ * @param {T} e - The enum object to extract values from
1064
+ * @returns {T[keyof T][]} An array containing all the actual values of the enum
1065
+ *
1066
+ * @example
1067
+ * ```typescript
1068
+ * // String enum example
1069
+ * enum Color {
1070
+ * RED = "red",
1071
+ * GREEN = "green",
1072
+ * BLUE = "blue"
1073
+ * }
1074
+ *
1075
+ * const colorValues = getEnumValues(Color);
1076
+ * // Returns: ["red", "green", "blue"]
1077
+ *
1078
+ * // Use for validation
1079
+ * function isValidColor(value: string): value is Color {
1080
+ * return getEnumValues(Color).includes(value as Color);
1081
+ * }
1082
+ * ```
1083
+ *
1084
+ * @example
1085
+ * ```typescript
1086
+ * // Numeric enum example
1087
+ * enum Status {
1088
+ * PENDING, // 0
1089
+ * APPROVED, // 1
1090
+ * REJECTED // 2
1091
+ * }
1092
+ *
1093
+ * const statusValues = getEnumValues(Status);
1094
+ * // Returns: [0, 1, 2] (not ["PENDING", "APPROVED", "REJECTED", 0, 1, 2])
1095
+ *
1096
+ * // Use for creating select options
1097
+ * const statusOptions = getEnumValues(Status).map(value => ({
1098
+ * value,
1099
+ * label: Status[value] // Get the key name for display
1100
+ * }));
1101
+ * ```
1102
+ *
1103
+ * @example
1104
+ * ```typescript
1105
+ * // Mixed numeric enum example
1106
+ * enum HttpStatus {
1107
+ * OK = 200,
1108
+ * NOT_FOUND = 404,
1109
+ * SERVER_ERROR = 500
1110
+ * }
1111
+ *
1112
+ * const httpStatusValues = getEnumValues(HttpStatus);
1113
+ * // Returns: [200, 404, 500]
1114
+ *
1115
+ * // Use for status code validation
1116
+ * function isValidHttpStatus(code: number): code is HttpStatus {
1117
+ * return getEnumValues(HttpStatus).includes(code as HttpStatus);
1118
+ * }
1119
+ * ```
1120
+ *
1121
+ * @example
1122
+ * ```typescript
1123
+ * // Creating dropdown options from enum
1124
+ * enum UserRole {
1125
+ * ADMIN = "admin",
1126
+ * USER = "user",
1127
+ * MODERATOR = "moderator"
1128
+ * }
1129
+ *
1130
+ * const roleOptions = getEnumValues(UserRole).map(role => ({
1131
+ * value: role,
1132
+ * label: role.charAt(0).toUpperCase() + role.slice(1)
1133
+ * }));
1134
+ * // Returns: [
1135
+ * // { value: "admin", label: "Admin" },
1136
+ * // { value: "user", label: "User" },
1137
+ * // { value: "moderator", label: "Moderator" }
1138
+ * // ]
1139
+ * ```
1140
+ *
1141
+ * @remarks
1142
+ * - Automatically detects numeric vs string enums and handles them appropriately
1143
+ * - For numeric enums, filters out reverse mapping entries that TypeScript automatically creates
1144
+ * - For string enums, returns all values as-is since there are no reverse mappings
1145
+ * - Works with mixed numeric enums (enums with explicit numeric values)
1146
+ * - The returned array maintains the order of enum declaration
1147
+ * - Type-safe: the return type is correctly inferred as an array of the enum's value types
1148
+ *
1149
+ * @see {@link Object.values} The underlying method used to extract enum entries
1150
+ */
1151
+ function getEnumValues(e) {
1152
+ const values = Object.values(e);
1153
+ // In numeric enums, values can appear as keys (reverse mapping)
1154
+ const isNumericEnum = values.some(v => typeof v === "number");
1155
+ return isNumericEnum ? values.filter(v => typeof v !== "string") : values;
1156
+ }
1157
+ /**
1158
+ * Filters items by recursively matching nested property values.
1159
+ */
1160
+ function filterByObject(items, filters) {
1161
+ function matchesObject(item, filter) {
1162
+ if (typeof filter !== "object" || filter === null) {
1163
+ return item === filter;
1164
+ }
1165
+ if (typeof item !== "object" || item === null) {
1166
+ return false;
1167
+ }
1168
+ const itemObj = item;
1169
+ const filterObj = filter;
1170
+ for (const key in filterObj) {
1171
+ if (!matchesObject(itemObj[key], filterObj[key])) {
1172
+ return false;
1173
+ }
1174
+ }
1175
+ return true;
1176
+ }
1177
+ return items.filter(item => matchesObject(item, filters));
1178
+ }
1179
+
1180
+ // noinspection JSUnusedGlobalSymbols
1181
+ /**
1182
+ * Comprehensive mapping of file extensions to their corresponding MIME types.
1183
+ *
1184
+ * This map provides a bidirectional mapping between file extensions and MIME types,
1185
+ * enabling applications to determine the appropriate content type for files or
1186
+ * to identify the likely file extension for a given MIME type.
1187
+ *
1188
+ * The map includes entries for common file formats across various categories:
1189
+ * - Documents (PDF, DOC, DOCX, XLS, XLSX, etc.)
1190
+ * - Images (JPG, PNG, GIF, SVG, WebP, etc.)
1191
+ * - Audio (MP3, WAV, OGG, etc.)
1192
+ * - Video (MP4, WebM, AVI, etc.)
1193
+ * - Archives (ZIP, RAR, 7Z, etc.)
1194
+ * - Web resources (HTML, CSS, JS, etc.)
1195
+ * - And many more specialized formats
1196
+ *
1197
+ * @remarks
1198
+ * The map is indexed by file extension (without the dot prefix) and contains
1199
+ * the corresponding MIME type as the value. Use utility functions like
1200
+ * getMimeTypeFromExtension and getExtensionFromMimeType to perform lookups
1201
+ * rather than accessing this map directly.
1202
+ *
1203
+ * Extensions are stored in lowercase to ensure case-insensitive matching.
1204
+ *
1205
+ * @example
1206
+ * ```typescript
1207
+ * // Get MIME type for a file extension
1208
+ * const mimeType = getMimeTypeFromExtension('pdf'); // 'application/pdf'
1209
+ *
1210
+ * // Get file extension for a MIME type
1211
+ * const extension = getExtensionFromMimeType('image/jpeg'); // 'jpg'
1212
+ * ```
1213
+ */
1214
+ 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"]]);
1215
+ /**
1216
+ * Get the file extension of the given mime type.
1217
+ * @param {string} mimeType - Mime type.
1218
+ * @param {Map<string, string>} [allowedMimeTypes] - Allowed mime types. If not provided, the default mime types map will be used.
1219
+ * @returns {string | undefined} File extension or undefined if the mime type is not found.
1220
+ *
1221
+ * @example
1222
+ * ```TypeScript
1223
+ * // Using the default mime types map
1224
+ * const extension = getFileExt('application/pdf');
1225
+ * // Output: 'pdf'
1226
+ * ```
1227
+ *
1228
+ * @example
1229
+ * ```TypeScript
1230
+ * // Using a custom mime types map
1231
+ * const customMimeTypes = new Map([
1232
+ * ['jpg', 'image/jpeg'],
1233
+ * ['png', 'image/png']
1234
+ * ]);
1235
+ * const extension = getFileExt('image/jpeg', customMimeTypes);
1236
+ * // Output: 'jpg'
1237
+ * ```
1238
+ */
1239
+ const getFileExt = (mimeType, allowedMimeTypes) => {
1240
+ return getMapKey(allowedMimeTypes ?? mimeTypes, mimeType);
1241
+ };
1242
+ /**
1243
+ * Get file size in human-readable format.
1244
+ * @param {number} size - File size in bytes.
1245
+ * @param {boolean} [round] - Whether to round the size to whole numbers. If true, decimals will be removed.
1246
+ * @returns {string} File size in human-readable format (B, KB, MB, or GB).
1247
+ *
1248
+ * @example
1249
+ * ```TypeScript
1250
+ * // Get file size with decimals
1251
+ * const readableSize = getFileSize(1536);
1252
+ * // Output: '1.50 KB'
1253
+ * ```
1254
+ *
1255
+ * @example
1256
+ * ```TypeScript
1257
+ * // Get rounded file size
1258
+ * const roundedSize = getFileSize(1536, true);
1259
+ * // Output: '2 KB'
1260
+ * ```
1261
+ *
1262
+ * @example
1263
+ * ```TypeScript
1264
+ * // Get size for larger files
1265
+ * const largeFileSize = getFileSize(1073741824);
1266
+ * // Output: '1.00 GB'
1267
+ * ```
1268
+ */
1269
+ const getFileSize = (size, round) => {
1270
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1271
+ if (size < 1024) {
1272
+ return `${Math.round(size)} B`;
1273
+ }
1274
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1275
+ if (size < 1024 * 1024) {
1276
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1277
+ return `${(size / 1024).toFixed(round ? 0 : 2)} KB`;
1278
+ }
1279
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1280
+ if (size < 1024 * 1024 * 1024) {
1281
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1282
+ return `${(size / 1024 / 1024).toFixed(round ? 0 : 2)} MB`;
1283
+ }
1284
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
1285
+ return `${(size / 1024 / 1024 / 1024).toFixed(round ? 0 : 2)} GB`;
1286
+ };
1287
+
1288
+ /**
1289
+ * Base for hexadecimal number conversion
1290
+ *
1291
+ * This constant defines the radix (base) used for hexadecimal number conversion.
1292
+ * The value 16 represents the standard hexadecimal numbering system (0-9, A-F).
1293
+ * It's used in toString() and parseInt() operations involving hex values.
1294
+ *
1295
+ * @example
1296
+ * ```typescript
1297
+ * // Converting a number to hexadecimal string
1298
+ * const hexString = number.toString(HEX_RADIX);
1299
+ *
1300
+ * // Converting a hexadecimal string to a number
1301
+ * const number = parseInt(hexString, HEX_RADIX);
1302
+ * ```
1303
+ *
1304
+ * @see {@link HEX_PADDING_LENGTH} Related constant for hex string formatting
1305
+ * @see {@link HEX_PADDING_CHAR} Related constant for hex string padding
1306
+ */
1307
+ const HEX_RADIX = 16;
1308
+ /**
1309
+ * Minimum length for padded hexadecimal strings
1310
+ *
1311
+ * This constant defines the minimum length for hexadecimal strings after padding.
1312
+ * It ensures that hex values have a consistent length (e.g., "0A" instead of "A").
1313
+ * The value 2 ensures that single-digit hex values are padded with a leading zero.
1314
+ *
1315
+ * @example
1316
+ * ```typescript
1317
+ * // Padding a hex string to ensure consistent length
1318
+ * const paddedHex = hexString.padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR);
1319
+ *
1320
+ * // Full example with conversion and padding
1321
+ * const byteValue = 10;
1322
+ * const hexByte = byteValue.toString(HEX_RADIX).padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR);
1323
+ * // hexByte = "0A"
1324
+ * ```
1325
+ *
1326
+ * @see {@link HEX_RADIX} Related constant for hex conversion
1327
+ * @see {@link HEX_PADDING_CHAR} Related constant for the padding character
1328
+ */
1329
+ const HEX_PADDING_LENGTH = 2;
1330
+ /**
1331
+ * Character used for padding hexadecimal strings
1332
+ *
1333
+ * This constant defines the character used to pad hexadecimal strings to reach
1334
+ * the minimum length defined by HEX_PADDING_LENGTH. The value "0" is the standard
1335
+ * padding character for hexadecimal values.
1336
+ *
1337
+ * @example
1338
+ * ```typescript
1339
+ * // Padding a hex string with zeros
1340
+ * const paddedHex = hexString.padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR);
1341
+ *
1342
+ * // Example with a single-digit hex value
1343
+ * const hexValue = "F";
1344
+ * const paddedHex = hexValue.padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR);
1345
+ * // paddedHex = "0F"
1346
+ * ```
1347
+ *
1348
+ * @see {@link HEX_RADIX} Related constant for hex conversion
1349
+ * @see {@link HEX_PADDING_LENGTH} Related constant for the minimum length
1350
+ */
1351
+ const HEX_PADDING_CHAR = "0";
1352
+ /**
1353
+ * Number of characters to remove in string truncation operations
1354
+ *
1355
+ * This constant defines the default number of characters to remove when
1356
+ * truncating strings in various utility functions. The value 2 is commonly
1357
+ * used when removing the last two characters of a string (e.g., removing
1358
+ * a trailing comma and space).
1359
+ *
1360
+ * @example
1361
+ * ```typescript
1362
+ * // Removing trailing characters from a string
1363
+ * const items = ["apple", "banana", "cherry"];
1364
+ * let result = "";
1365
+ *
1366
+ * for (const item of items) {
1367
+ * result += item + ", ";
1368
+ * }
1369
+ *
1370
+ * // Remove trailing comma and space
1371
+ * if (result.length > 0) {
1372
+ * result = result.slice(0, -CHARACTERS_TO_REMOVE);
1373
+ * }
1374
+ * // result = "apple, banana, cherry"
1375
+ * ```
1376
+ */
1377
+ const CHARACTERS_TO_REMOVE = 2;
1378
+ /**
1379
+ * Default maximum line length for word wrapping
1380
+ *
1381
+ * This constant defines the default maximum length of a line when performing
1382
+ * word wrapping operations. The value 80 is a common standard for line length
1383
+ * in many text formats and terminal displays.
1384
+ *
1385
+ * @example
1386
+ * ```typescript
1387
+ * // Wrapping a long text to the default line length
1388
+ * function wrapText(text: string, maxLength = DEFAULT_LINE_LENGTH, breakChar = DEFAULT_BREAK_CHAR): string {
1389
+ * // Word wrapping implementation
1390
+ * const result = [];
1391
+ * let line = "";
1392
+ *
1393
+ * for (const word of text.split(" ")) {
1394
+ * if ((line + word).length > maxLength) {
1395
+ * result.push(line);
1396
+ * line = word;
1397
+ * } else {
1398
+ * line += (line ? " " : "") + word;
1399
+ * }
1400
+ * }
1401
+ *
1402
+ * if (line) {
1403
+ * result.push(line);
1404
+ * }
1405
+ *
1406
+ * return result.join(breakChar);
1407
+ * }
1408
+ * ```
1409
+ *
1410
+ * @see {@link DEFAULT_BREAK_CHAR} Related constant for line break character
1411
+ */
1412
+ const DEFAULT_LINE_LENGTH = 80;
1413
+ /**
1414
+ * Default line break character for word wrapping
1415
+ *
1416
+ * This constant defines the default character or string used to separate lines
1417
+ * when performing word wrapping operations. The value "\n" represents a standard
1418
+ * line feed character used for line breaks in most text formats.
1419
+ *
1420
+ * @example
1421
+ * ```typescript
1422
+ * // Using the default break character in word wrapping
1423
+ * const wrappedText = longText.match(new RegExp(`.{1,${DEFAULT_LINE_LENGTH}}(\\s|$)`, 'g'))
1424
+ * ?.join(DEFAULT_BREAK_CHAR) || longText;
1425
+ * ```
1426
+ *
1427
+ * @see {@link DEFAULT_LINE_LENGTH} Related constant for maximum line length
1428
+ */
1429
+ const DEFAULT_BREAK_CHAR = "\n";
1430
+ /**
1431
+ * Default context length for text excerpts
1432
+ *
1433
+ * This constant defines the default number of characters to include before and after
1434
+ * a search term when generating text excerpts or snippets. The value 40 provides
1435
+ * enough context to understand the meaning while keeping the excerpt concise.
1436
+ *
1437
+ * @example
1438
+ * ```typescript
1439
+ * // Generating an excerpt around a search term
1440
+ * function generateExcerpt(text: string, searchTerm: string): string {
1441
+ * const index = text.toLowerCase().indexOf(searchTerm.toLowerCase());
1442
+ * if (index === -1) return text.substring(0, DEFAULT_CONTEXT_LENGTH * 2) + DEFAULT_ELLIPSIS;
1443
+ *
1444
+ * const start = Math.max(0, index - DEFAULT_CONTEXT_LENGTH);
1445
+ * const end = Math.min(text.length, index + searchTerm.length + DEFAULT_CONTEXT_LENGTH);
1446
+ *
1447
+ * let excerpt = text.substring(start, end);
1448
+ * if (start > 0) excerpt = DEFAULT_ELLIPSIS + excerpt;
1449
+ * if (end < text.length) excerpt = excerpt + DEFAULT_ELLIPSIS;
1450
+ *
1451
+ * return excerpt;
1452
+ * }
1453
+ * ```
1454
+ *
1455
+ * @see {@link DEFAULT_ELLIPSIS} Related constant for indicating truncated text
1456
+ * @see {@link CONTEXT_MULTIPLIER} Related constant for adjusting context length
1457
+ */
1458
+ const DEFAULT_CONTEXT_LENGTH = 40;
1459
+ /**
1460
+ * Default ellipsis string for indicating truncated text
1461
+ *
1462
+ * This constant defines the default string used to indicate that text has been
1463
+ * truncated or omitted in excerpts and summaries. The value "..." is the standard
1464
+ * ellipsis used to represent omitted content.
1465
+ *
1466
+ * @example
1467
+ * ```typescript
1468
+ * // Truncating a long string with ellipsis
1469
+ * function truncate(text: string, maxLength: number): string {
1470
+ * if (text.length <= maxLength) return text;
1471
+ * return text.substring(0, maxLength - DEFAULT_ELLIPSIS.length) + DEFAULT_ELLIPSIS;
1472
+ * }
1473
+ *
1474
+ * const longText = "This is a very long text that needs to be truncated";
1475
+ * const truncated = truncate(longText, 20);
1476
+ * // truncated = "This is a very lo..."
1477
+ * ```
1478
+ *
1479
+ * @see {@link DEFAULT_CONTEXT_LENGTH} Related constant for excerpt generation
1480
+ */
1481
+ const DEFAULT_ELLIPSIS = "...";
1482
+ /**
1483
+ * Multiplier for adjusting context length in excerpts
1484
+ *
1485
+ * This constant defines a multiplier used to adjust the context length when
1486
+ * generating excerpts under different conditions. The value 2 is typically
1487
+ * used to double the context length for certain scenarios, such as when no
1488
+ * search term is found.
1489
+ *
1490
+ * @example
1491
+ * ```typescript
1492
+ * // Using the multiplier to adjust context length
1493
+ * function getContextLength(hasSearchTerm: boolean): number {
1494
+ * return hasSearchTerm
1495
+ * ? DEFAULT_CONTEXT_LENGTH
1496
+ * : DEFAULT_CONTEXT_LENGTH * CONTEXT_MULTIPLIER;
1497
+ * }
1498
+ *
1499
+ * // Getting the first part of a text when no search term is found
1500
+ * function getFirstPart(text: string): string {
1501
+ * const length = DEFAULT_CONTEXT_LENGTH * CONTEXT_MULTIPLIER;
1502
+ * return text.length > length
1503
+ * ? text.substring(0, length) + DEFAULT_ELLIPSIS
1504
+ * : text;
1505
+ * }
1506
+ * ```
1507
+ *
1508
+ * @see {@link DEFAULT_CONTEXT_LENGTH} Related constant for base context length
1509
+ */
1510
+ const CONTEXT_MULTIPLIER = 2;
1511
+
1512
+ /**
1513
+ * Comprehensive rules for English word inflection (singular/plural transformations)
1514
+ *
1515
+ * This module provides a structured collection of rules for transforming English words
1516
+ * between singular and plural forms. The rules are organized into categories based on
1517
+ * linguistic patterns and exceptions, ensuring accurate transformations for various
1518
+ * word types.
1519
+ *
1520
+ * Key features:
1521
+ * - Invariant words that are identical in singular and plural forms
1522
+ * - Irregular plural forms that don't follow standard patterns
1523
+ * - Latin and Greek origin words with their specific plural formations
1524
+ * - Pattern-based rules for common English word endings
1525
+ * - Default fallback rules for standard English pluralization
1526
+ *
1527
+ * The rules are applied in a specific priority order, from most specific to most general,
1528
+ * to ensure correct handling of special cases. Each rule consists of a regular expression
1529
+ * pattern and a replacement string or function.
1530
+ *
1531
+ * @example
1532
+ * ```typescript
1533
+ * // Using the rules with the plural() function
1534
+ * import { EnglishInflectionRules } from '@hichchi/utils/constants';
1535
+ * import { InflectionRule } from '@hichchi/utils/interfaces';
1536
+ *
1537
+ * function plural(word: string): string {
1538
+ * // Combine all rules in priority order
1539
+ * const rules: InflectionRule[] = [
1540
+ * ...EnglishInflectionRules.INVARIANT,
1541
+ * ...EnglishInflectionRules.IRREGULAR.toPlural,
1542
+ * ...EnglishInflectionRules.LATIN_GREEK.toPlural,
1543
+ * ...EnglishInflectionRules.PATTERN_RULES.toPlural,
1544
+ * ...EnglishInflectionRules.DEFAULT.toPlural,
1545
+ * ];
1546
+ *
1547
+ * // Apply the first matching rule
1548
+ * for (const rule of rules) {
1549
+ * if (rule.regex.test(word)) {
1550
+ * return word.replace(rule.regex, rule.replacement as string);
1551
+ * }
1552
+ * }
1553
+ *
1554
+ * return word;
1555
+ * }
1556
+ * ```
1557
+ *
1558
+ * @see {@link plural} Function that uses these rules to convert words to plural form
1559
+ * @see {@link singular} Function that uses these rules to convert words to singular form
1560
+ * @see {@link InflectionRuleCategories} Interface defining the structure of the rules
1561
+ * @see {@link InflectionRule} Interface for individual transformation rules
1562
+ */
1563
+ const EnglishInflectionRules = {
1564
+ /**
1565
+ * Words that are the same in singular and plural form
1566
+ *
1567
+ * This category contains rules for words that don't change between singular and plural forms.
1568
+ * These invariant words (also called "zero plurals") have identical singular and plural forms
1569
+ * in English. The rule simply returns the word unchanged.
1570
+ *
1571
+ * Common examples include:
1572
+ * - Animal names: "fish", "deer", "sheep"
1573
+ * - Scientific terms: "species", "series"
1574
+ * - Uncountable nouns: "equipment"
1575
+ *
1576
+ * @example
1577
+ * ```typescript
1578
+ * // These words remain unchanged in plural form
1579
+ * plural("fish"); // "fish"
1580
+ * plural("deer"); // "deer"
1581
+ * plural("sheep"); // "sheep"
1582
+ * plural("species"); // "species"
1583
+ * plural("equipment"); // "equipment"
1584
+ * ```
1585
+ *
1586
+ * @see {@link plural} Function that applies these rules
1587
+ * @see {@link singular} Function that applies these rules
1588
+ */
1589
+ INVARIANT: [{
1590
+ regex: /^(fish|deer|sheep|species|series|equipment)$/i,
1591
+ replacement: "$1"
1592
+ }],
1593
+ /**
1594
+ * Irregular plural forms
1595
+ *
1596
+ * This category contains rules for words with irregular plural forms that don't
1597
+ * follow standard English pluralization patterns. These words have unique
1598
+ * transformations between singular and plural forms that must be handled as
1599
+ * special cases.
1600
+ *
1601
+ * The category is divided into two rule sets:
1602
+ * - toPlural: Rules for converting singular forms to their irregular plural forms
1603
+ * - toSingular: Rules for converting irregular plural forms back to singular
1604
+ *
1605
+ * These rules handle common irregular English plurals like "child/children",
1606
+ * "man/men", "tooth/teeth", etc. Many of these irregular forms have historical
1607
+ * origins in Old English and other Germanic languages.
1608
+ *
1609
+ * @example
1610
+ * ```typescript
1611
+ * // Singular to plural transformations
1612
+ * plural("child"); // "children"
1613
+ * plural("man"); // "men"
1614
+ * plural("tooth"); // "teeth"
1615
+ * plural("person"); // "people"
1616
+ *
1617
+ * // Plural to singular transformations
1618
+ * singular("children"); // "child"
1619
+ * singular("men"); // "man"
1620
+ * singular("teeth"); // "tooth"
1621
+ * singular("people"); // "person"
1622
+ * ```
1623
+ *
1624
+ * @see {@link plural} Function that applies these rules for pluralization
1625
+ * @see {@link singular} Function that applies these rules for singularization
1626
+ */
1627
+ IRREGULAR: {
1628
+ // Singular to plural transformations
1629
+ toPlural: [{
1630
+ regex: /^child$/i,
1631
+ replacement: "children"
1632
+ }, {
1633
+ regex: /^man$/i,
1634
+ replacement: "men"
1635
+ }, {
1636
+ regex: /^woman$/i,
1637
+ replacement: "women"
1638
+ }, {
1639
+ regex: /^tooth$/i,
1640
+ replacement: "teeth"
1641
+ }, {
1642
+ regex: /^foot$/i,
1643
+ replacement: "feet"
1644
+ }, {
1645
+ regex: /^goose$/i,
1646
+ replacement: "geese"
1647
+ }, {
1648
+ regex: /^mouse$/i,
1649
+ replacement: "mice"
1650
+ }, {
1651
+ regex: /^person$/i,
1652
+ replacement: "people"
1653
+ }, {
1654
+ regex: /^ox$/i,
1655
+ replacement: "oxen"
1656
+ }],
1657
+ // Plural to singular transformations
1658
+ toSingular: [{
1659
+ regex: /children$/i,
1660
+ replacement: "child"
1661
+ }, {
1662
+ regex: /men$/i,
1663
+ replacement: "man"
1664
+ }, {
1665
+ regex: /women$/i,
1666
+ replacement: "woman"
1667
+ }, {
1668
+ regex: /teeth$/i,
1669
+ replacement: "tooth"
1670
+ }, {
1671
+ regex: /feet$/i,
1672
+ replacement: "foot"
1673
+ }, {
1674
+ regex: /geese$/i,
1675
+ replacement: "goose"
1676
+ }, {
1677
+ regex: /mice$/i,
1678
+ replacement: "mouse"
1679
+ }, {
1680
+ regex: /people$/i,
1681
+ replacement: "person"
1682
+ }, {
1683
+ regex: /oxen$/i,
1684
+ replacement: "ox"
1685
+ }]
1686
+ },
1687
+ /**
1688
+ * Latin/Greek origin words
1689
+ *
1690
+ * This category contains rules for words borrowed from Latin and Greek that
1691
+ * retain their original pluralization patterns. These words follow different
1692
+ * rules than native English words and often have distinctive ending changes.
1693
+ *
1694
+ * The category is divided into two rule sets:
1695
+ * - toPlural: Rules for converting Latin/Greek singular forms to their plural forms
1696
+ * - toSingular: Rules for converting Latin/Greek plural forms back to singular
1697
+ *
1698
+ * Common patterns include:
1699
+ * - "-us" → "-i" (cactus/cacti, fungus/fungi)
1700
+ * - "-is" → "-es" (analysis/analyses, thesis/theses)
1701
+ * - "-on"/"-um" → "-a" (criterion/criteria, datum/data)
1702
+ * - "-ex"/"-ix" → "-ices" (index/indices, matrix/matrices)
1703
+ *
1704
+ * These words are often found in academic, scientific, and technical contexts.
1705
+ * Note that some of these words have alternative anglicized plural forms that
1706
+ * are also acceptable in modern English (e.g., "cactuses" alongside "cacti").
1707
+ *
1708
+ * @example
1709
+ * ```typescript
1710
+ * // Singular to plural transformations
1711
+ * plural("cactus"); // "cacti"
1712
+ * plural("analysis"); // "analyses"
1713
+ * plural("criterion"); // "criteria"
1714
+ * plural("index"); // "indices"
1715
+ * plural("matrix"); // "matrices"
1716
+ *
1717
+ * // Plural to singular transformations
1718
+ * singular("cacti"); // "cactus"
1719
+ * singular("analyses"); // "analysis"
1720
+ * singular("criteria"); // "criterion"
1721
+ * singular("indices"); // "index"
1722
+ * singular("matrices"); // "matrix"
1723
+ * ```
1724
+ *
1725
+ * @see {@link plural} Function that applies these rules for pluralization
1726
+ * @see {@link singular} Function that applies these rules for singularization
1727
+ */
1728
+ LATIN_GREEK: {
1729
+ // Singular to plural transformations
1730
+ toPlural: [{
1731
+ regex: /^cactus$/i,
1732
+ replacement: "cacti"
1733
+ }, {
1734
+ regex: /^focus$/i,
1735
+ replacement: "foci"
1736
+ }, {
1737
+ regex: /^fungus$/i,
1738
+ replacement: "fungi"
1739
+ }, {
1740
+ regex: /^nucleus$/i,
1741
+ replacement: "nuclei"
1742
+ }, {
1743
+ regex: /^syllabus$/i,
1744
+ replacement: "syllabi"
1745
+ }, {
1746
+ regex: /^analysis$/i,
1747
+ replacement: "analyses"
1748
+ }, {
1749
+ regex: /^diagnosis$/i,
1750
+ replacement: "diagnoses"
1751
+ }, {
1752
+ regex: /^thesis$/i,
1753
+ replacement: "theses"
1754
+ }, {
1755
+ regex: /^criterion$/i,
1756
+ replacement: "criteria"
1757
+ }, {
1758
+ regex: /^datum$/i,
1759
+ replacement: "data"
1760
+ }, {
1761
+ regex: /^phenomenon$/i,
1762
+ replacement: "phenomena"
1763
+ }, {
1764
+ regex: /^index$/i,
1765
+ replacement: "indices"
1766
+ }, {
1767
+ regex: /^matrix$/i,
1768
+ replacement: "matrices"
1769
+ }, {
1770
+ regex: /^vertex$/i,
1771
+ replacement: "vertices"
1772
+ }, {
1773
+ regex: /^appendix$/i,
1774
+ replacement: "appendices"
1775
+ }],
1776
+ // Plural to singular transformations
1777
+ toSingular: [{
1778
+ regex: /matrices$/i,
1779
+ replacement: "matrix"
1780
+ }, {
1781
+ regex: /indices$/i,
1782
+ replacement: "index"
1783
+ }, {
1784
+ regex: /vertices$/i,
1785
+ replacement: "vertex"
1786
+ }, {
1787
+ regex: /appendices$/i,
1788
+ replacement: "appendix"
1789
+ }, {
1790
+ regex: /cacti$/i,
1791
+ replacement: "cactus"
1792
+ }, {
1793
+ regex: /foci$/i,
1794
+ replacement: "focus"
1795
+ }, {
1796
+ regex: /fungi$/i,
1797
+ replacement: "fungus"
1798
+ }, {
1799
+ regex: /nuclei$/i,
1800
+ replacement: "nucleus"
1801
+ }, {
1802
+ regex: /syllabi$/i,
1803
+ replacement: "syllabus"
1804
+ }, {
1805
+ regex: /analyses$/i,
1806
+ replacement: "analysis"
1807
+ }, {
1808
+ regex: /diagnoses$/i,
1809
+ replacement: "diagnosis"
1810
+ }, {
1811
+ regex: /theses$/i,
1812
+ replacement: "thesis"
1813
+ }, {
1814
+ regex: /criteria$/i,
1815
+ replacement: "criterion"
1816
+ }, {
1817
+ regex: /data$/i,
1818
+ replacement: "datum"
1819
+ }, {
1820
+ regex: /phenomena$/i,
1821
+ replacement: "phenomenon"
1822
+ }]
1823
+ },
1824
+ /**
1825
+ * Words with specific ending patterns
1826
+ *
1827
+ * This category contains rules for words that follow regular patterns based on
1828
+ * their endings. These patterns represent the most common rules for English
1829
+ * pluralization and singularization, covering a wide range of everyday words.
1830
+ *
1831
+ * The category is divided into two rule sets:
1832
+ * - toPlural: Rules for converting singular forms to plural based on word endings
1833
+ * - toSingular: Rules for converting plural forms back to singular
1834
+ *
1835
+ * Key pattern groups include:
1836
+ *
1837
+ * 1. Words ending in -f or -fe:
1838
+ * - "knife" → "knives", "life" → "lives", "wolf" → "wolves"
1839
+ *
1840
+ * 2. Words ending in -y:
1841
+ * - After consonant: "city" → "cities", "baby" → "babies"
1842
+ * - After vowel: "boy" → "boys", "key" → "keys"
1843
+ *
1844
+ * 3. Words ending in -o:
1845
+ * - Some specific words: "hero" → "heroes", "potato" → "potatoes"
1846
+ * - After consonant: "echo" → "echoes", "tomato" → "tomatoes"
1847
+ *
1848
+ * 4. Words ending in -s, -ss, -sh, -ch, -x, -z:
1849
+ * - "bus" → "buses", "box" → "boxes", "dish" → "dishes"
1850
+ *
1851
+ * These patterns cover the majority of regular English pluralization rules
1852
+ * and are applied after checking for invariant words, irregular forms, and
1853
+ * Latin/Greek words.
1854
+ *
1855
+ * @example
1856
+ * ```typescript
1857
+ * // Words ending in -f or -fe
1858
+ * plural("knife"); // "knives"
1859
+ * plural("life"); // "lives"
1860
+ * plural("wolf"); // "wolves"
1861
+ *
1862
+ * // Words ending in -y
1863
+ * plural("city"); // "cities"
1864
+ * plural("boy"); // "boys"
1865
+ *
1866
+ * // Words ending in -o
1867
+ * plural("hero"); // "heroes"
1868
+ * plural("photo"); // "photos"
1869
+ *
1870
+ * // Words ending in -s, -ss, -sh, -ch, -x, -z
1871
+ * plural("bus"); // "buses"
1872
+ * plural("box"); // "boxes"
1873
+ * plural("dish"); // "dishes"
1874
+ *
1875
+ * // Plural to singular
1876
+ * singular("knives"); // "knife"
1877
+ * singular("cities"); // "city"
1878
+ * singular("boxes"); // "box"
1879
+ * ```
1880
+ *
1881
+ * @see {@link plural} Function that applies these rules for pluralization
1882
+ * @see {@link singular} Function that applies these rules for singularization
1883
+ * @see {@link CHARACTERS_TO_REMOVE} Constant used in some replacement functions
1884
+ */
1885
+ PATTERN_RULES: {
1886
+ // Singular to plural transformations
1887
+ toPlural: [
1888
+ // Words ending in -f or -fe
1889
+ {
1890
+ regex: /^((?:wi|li)fe)$/i,
1891
+ replacement: "$1ves"
1892
+ }, {
1893
+ regex: /(fe)$/i,
1894
+ replacement: "ves"
1895
+ }, {
1896
+ regex: /([^f])f$/i,
1897
+ replacement: "$1ves"
1898
+ },
1899
+ // Words ending in -y
1900
+ {
1901
+ regex: /([^aeiou])y$/i,
1902
+ replacement: "$1ies"
1903
+ }, {
1904
+ regex: /([aeiou]y)$/i,
1905
+ replacement: "$1s"
1906
+ },
1907
+ // Words ending in -o
1908
+ {
1909
+ regex: /(hero|potato|tomato|torpedo|veto)$/i,
1910
+ replacement: "$1es"
1911
+ }, {
1912
+ regex: /([^aeiou])o$/i,
1913
+ replacement: "$1oes"
1914
+ },
1915
+ // Words ending in -is
1916
+ {
1917
+ regex: /(is)$/i,
1918
+ replacement: "es"
1919
+ },
1920
+ // Words ending in -us
1921
+ {
1922
+ regex: /(us)$/i,
1923
+ replacement: "uses"
1924
+ },
1925
+ // Words ending in -s, -ss, -sh, -ch, -x, -z
1926
+ {
1927
+ regex: /(s|ss|sh|ch|x|z)$/i,
1928
+ replacement: "$1es"
1929
+ }],
1930
+ // Plural to singular transformations
1931
+ toSingular: [
1932
+ // Special spelling changes
1933
+ {
1934
+ regex: /knives$/i,
1935
+ replacement: "knife"
1936
+ }, {
1937
+ regex: /lives$/i,
1938
+ replacement: "life"
1939
+ }, {
1940
+ regex: /shelves$/i,
1941
+ replacement: "shelf"
1942
+ },
1943
+ // Common word patterns
1944
+ {
1945
+ regex: /(buses|dishes|matches)$/i,
1946
+ replacement: match => match.slice(0, -CHARACTERS_TO_REMOVE)
1947
+ }, {
1948
+ regex: /([^aeiouy]|qu)ies$/i,
1949
+ replacement: "$1y"
1950
+ }, {
1951
+ regex: /(x|ch|ss|sh)es$/i,
1952
+ replacement: "$1"
1953
+ }, {
1954
+ regex: /oes$/i,
1955
+ replacement: "o"
1956
+ }, {
1957
+ regex: /ves$/i,
1958
+ replacement: "f"
1959
+ }]
1960
+ },
1961
+ /**
1962
+ * Default rules for standard transformations
1963
+ *
1964
+ * This category contains fallback rules that are applied when no other rules match.
1965
+ * These represent the most basic and common English pluralization pattern: adding
1966
+ * an "s" to form plurals and removing an "s" to form singulars.
1967
+ *
1968
+ * The category is divided into two rule sets:
1969
+ * - toPlural: Adds "s" to the end of any word to form its plural
1970
+ * - toSingular: Removes a trailing "s" from any word to form its singular
1971
+ *
1972
+ * These rules are applied as a last resort after all other more specific rules
1973
+ * have been checked. They handle the majority of regular English words that don't
1974
+ * fall into any of the special categories or patterns.
1975
+ *
1976
+ * While simple, these rules can sometimes produce incorrect results for words that
1977
+ * should follow more specific patterns but weren't included in the other rule sets.
1978
+ * They're designed as a reasonable fallback rather than a comprehensive solution.
1979
+ *
1980
+ * @example
1981
+ * ```typescript
1982
+ * // Singular to plural with default rule
1983
+ * plural("book"); // "books"
1984
+ * plural("car"); // "cars"
1985
+ * plural("apple"); // "apples"
1986
+ *
1987
+ * // Plural to singular with default rule
1988
+ * singular("books"); // "book"
1989
+ * singular("cars"); // "car"
1990
+ * singular("apples"); // "apple"
1991
+ * ```
1992
+ *
1993
+ * @see {@link plural} Function that applies these rules for pluralization
1994
+ * @see {@link singular} Function that applies these rules for singularization
1995
+ */
1996
+ DEFAULT: {
1997
+ toPlural: [{
1998
+ regex: /(.*)$/i,
1999
+ replacement: "$1s"
2000
+ }],
2001
+ toSingular: [{
2002
+ regex: /s$/i,
2003
+ replacement: ""
2004
+ }]
2005
+ }
2006
+ };
2007
+
2008
+ // noinspection JSUnusedGlobalSymbols
2009
+ function breakToWords(str, format) {
2010
+ if (!str) {
2011
+ return format ? "" : [];
2012
+ }
2013
+ try {
2014
+ 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()) ?? [];
2015
+ return format ? words.map(format).join(" ") : words;
2016
+ } catch {
2017
+ return format ? "" : [];
2018
+ }
2019
+ }
2020
+ /**
2021
+ * Converts a string to lowercase with safe handling of undefined or null values.
2022
+ *
2023
+ * This utility function provides a safe way to convert strings to lowercase, automatically
2024
+ * handling edge cases where the input might be undefined, null, or empty. Unlike the native
2025
+ * String.prototype.toLowerCase() method, this function won't throw an error when called
2026
+ * with undefined or null values, instead returning an empty string.
2027
+ *
2028
+ * This is particularly useful when working with user input, API responses, or any scenario
2029
+ * where string values might be optional or potentially undefined. It's commonly used for
2030
+ * case-insensitive comparisons, search functionality, or normalizing text data.
2031
+ *
2032
+ * @param {string} [str] - The string to convert to lowercase. Can be undefined or null.
2033
+ * @returns {string} The lowercase version of the input string, or an empty string if input is falsy
2034
+ *
2035
+ * @example
2036
+ * ```typescript
2037
+ * // Basic string conversion
2038
+ * const result1 = toLowerCase("Hello World");
2039
+ * // Returns: "hello world"
2040
+ *
2041
+ * const result2 = toLowerCase("JAVASCRIPT");
2042
+ * // Returns: "javascript"
2043
+ * ```
2044
+ *
2045
+ * @example
2046
+ * ```typescript
2047
+ * // Safe handling of undefined/null values
2048
+ * const result1 = toLowerCase(undefined);
2049
+ * // Returns: ""
2050
+ *
2051
+ * const result2 = toLowerCase(null);
2052
+ * // Returns: ""
2053
+ *
2054
+ * const result3 = toLowerCase("");
2055
+ * // Returns: ""
2056
+ * ```
2057
+ *
2058
+ * @example
2059
+ * ```typescript
2060
+ * // Use in case-insensitive search
2061
+ * function searchUsers(users: User[], searchTerm: string): User[] {
2062
+ * const normalizedSearch = toLowerCase(searchTerm);
2063
+ * return users.filter(user =>
2064
+ * toLowerCase(user.name).includes(normalizedSearch) ||
2065
+ * toLowerCase(user.email).includes(normalizedSearch)
2066
+ * );
2067
+ * }
2068
+ * ```
2069
+ *
2070
+ * @example
2071
+ * ```typescript
2072
+ * // Use in form validation
2073
+ * function validateEmail(email?: string): boolean {
2074
+ * const normalizedEmail = toLowerCase(email);
2075
+ * const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
2076
+ * return emailRegex.test(normalizedEmail);
2077
+ * }
2078
+ * ```
2079
+ *
2080
+ * @remarks
2081
+ * - Returns an empty string for any falsy input (undefined, null, empty string)
2082
+ * - Uses the native String.prototype.toLowerCase() method for actual conversion
2083
+ * - Does not modify the original string (strings are immutable in JavaScript)
2084
+ * - Handles all Unicode characters correctly, following locale-independent rules
2085
+ * - More robust than direct .toLowerCase() calls when input might be undefined
2086
+ *
2087
+ * @see {@link toUpperCase} Function for converting strings to uppercase
2088
+ * @see {@link toLowerCaseBreak} Function for converting to lowercase and breaking into words
2089
+ */
2090
+ function toLowerCase(str) {
2091
+ if (!str) {
2092
+ return "";
2093
+ }
2094
+ return str.toLowerCase();
2095
+ }
2096
+ /**
2097
+ * Converts a string to lowercase and breaks it into words, joining them with a specified separator.
2098
+ *
2099
+ * This utility function combines word breaking and case conversion in a single operation.
2100
+ * It first breaks the input string into individual words using intelligent pattern recognition
2101
+ * (handling camelCase, snake_case, kebab-case, etc.), converts each word to lowercase,
2102
+ * and then joins them with the specified separator or a space by default.
2103
+ *
2104
+ * This is particularly useful for converting various naming conventions to a consistent
2105
+ * lowercase format, creating readable labels from variable names, generating search-friendly
2106
+ * text, or preparing strings for further processing where consistent word separation is needed.
2107
+ *
2108
+ * @param {string} [str] - The string to convert and break into words. Can be undefined or null.
2109
+ * @param {string} [join=" "] - The separator to use when joining the words. Defaults to a single space.
2110
+ * @returns {string} A lowercase string with words separated by the specified join character,
2111
+ * or an empty string if input is falsy
2112
+ *
2113
+ * @example
2114
+ * ```typescript
2115
+ * // Basic camelCase conversion
2116
+ * const result1 = toLowerCaseBreak("HelloWorld");
2117
+ * // Returns: "hello world"
2118
+ *
2119
+ * const result2 = toLowerCaseBreak("getUserProfile");
2120
+ * // Returns: "get user profile"
2121
+ * ```
2122
+ *
2123
+ * @example
2124
+ * ```typescript
2125
+ * // Custom separators
2126
+ * const result1 = toLowerCaseBreak("HelloWorld", "-");
2127
+ * // Returns: "hello-world"
2128
+ *
2129
+ * const result2 = toLowerCaseBreak("APIResponseHandler", "_");
2130
+ * // Returns: "api_response_handler"
2131
+ *
2132
+ * const result3 = toLowerCaseBreak("userAccountSettings", " | ");
2133
+ * // Returns: "user | account | settings"
2134
+ * ```
2135
+ *
2136
+ * @example
2137
+ * ```typescript
2138
+ * // Various input formats
2139
+ * const result1 = toLowerCaseBreak("snake_case_example");
2140
+ * // Returns: "snake case example"
2141
+ *
2142
+ * const result2 = toLowerCaseBreak("kebab-case-example");
2143
+ * // Returns: "kebab case example"
2144
+ *
2145
+ * const result3 = toLowerCaseBreak("CONSTANT_VALUE");
2146
+ * // Returns: "constant value"
2147
+ * ```
2148
+ *
2149
+ * @example
2150
+ * ```typescript
2151
+ * // Creating user-friendly labels
2152
+ * function createLabel(fieldName: string): string {
2153
+ * return toLowerCaseBreak(fieldName)
2154
+ * .split(' ')
2155
+ * .map(word => word.charAt(0).toUpperCase() + word.slice(1))
2156
+ * .join(' ');
2157
+ * }
2158
+ *
2159
+ * createLabel("firstName"); // "First Name"
2160
+ * createLabel("emailAddress"); // "Email Address"
2161
+ * ```
2162
+ *
2163
+ * @example
2164
+ * ```typescript
2165
+ * // Safe handling of edge cases
2166
+ * const result1 = toLowerCaseBreak(undefined);
2167
+ * // Returns: ""
2168
+ *
2169
+ * const result2 = toLowerCaseBreak("", "-");
2170
+ * // Returns: ""
2171
+ *
2172
+ * const result3 = toLowerCaseBreak("singleword");
2173
+ * // Returns: "singleword"
2174
+ * ```
2175
+ *
2176
+ * @remarks
2177
+ * - Returns an empty string for any falsy input (undefined, null, empty string)
2178
+ * - Uses the same intelligent word-breaking algorithm as the breakToWords function
2179
+ * - Handles camelCase, PascalCase, snake_case, kebab-case, and mixed formats
2180
+ * - Preserves numbers as separate words when they appear in the string
2181
+ * - The join parameter can be any string, including empty string for concatenation
2182
+ * - More efficient than calling breakToWords and toLowerCase separately
2183
+ *
2184
+ * @see {@link breakToWords} Function for breaking strings into word arrays
2185
+ * @see {@link toLowerCase} Function for simple lowercase conversion
2186
+ * @see {@link toUpperCaseBreak} Function for uppercase word breaking
2187
+ */
2188
+ function toLowerCaseBreak(str, join) {
2189
+ if (!str) {
2190
+ return "";
2191
+ }
2192
+ return breakToWords(str).map(s => s.toLowerCase()).join(join ?? " ");
2193
+ }
2194
+ /**
2195
+ * Converts a string to uppercase with safe handling of undefined or null values.
2196
+ *
2197
+ * This utility function provides a safe way to convert strings to uppercase, automatically
2198
+ * handling edge cases where the input might be undefined, null, or empty. Unlike the native
2199
+ * String.prototype.toUpperCase() method, this function won't throw an error when called
2200
+ * with undefined or null values, instead returning an empty string.
2201
+ *
2202
+ * This is particularly useful when working with user input, API responses, or any scenario
2203
+ * where string values might be optional or potentially undefined. It's commonly used for
2204
+ * creating constants, formatting display text, generating identifiers, or normalizing text
2205
+ * data that needs to be in uppercase format.
2206
+ *
2207
+ * @param {string} [str] - The string to convert to uppercase. Can be undefined or null.
2208
+ * @returns {string} The uppercase version of the input string, or an empty string if input is falsy
2209
+ *
2210
+ * @example
2211
+ * ```typescript
2212
+ * // Basic string conversion
2213
+ * const result1 = toUpperCase("hello world");
2214
+ * // Returns: "HELLO WORLD"
2215
+ *
2216
+ * const result2 = toUpperCase("javascript");
2217
+ * // Returns: "JAVASCRIPT"
2218
+ * ```
2219
+ *
2220
+ * @example
2221
+ * ```typescript
2222
+ * // Safe handling of undefined/null values
2223
+ * const result1 = toUpperCase(undefined);
2224
+ * // Returns: ""
2225
+ *
2226
+ * const result2 = toUpperCase(null);
2227
+ * // Returns: ""
2228
+ *
2229
+ * const result3 = toUpperCase("");
2230
+ * // Returns: ""
2231
+ * ```
2232
+ *
2233
+ * @example
2234
+ * ```typescript
2235
+ * // Creating constants or identifiers
2236
+ * function generateConstantName(variableName: string): string {
2237
+ * return toUpperCase(variableName.replace(/[^a-zA-Z0-9]/g, '_'));
2238
+ * }
2239
+ *
2240
+ * generateConstantName("user-profile"); // "USER_PROFILE"
2241
+ * generateConstantName("api.endpoint"); // "API_ENDPOINT"
2242
+ * ```
2243
+ *
2244
+ * @example
2245
+ * ```typescript
2246
+ * // Use in text formatting
2247
+ * function formatTitle(title?: string): string {
2248
+ * if (!title) return "UNTITLED";
2249
+ * return toUpperCase(title.trim());
2250
+ * }
2251
+ *
2252
+ * formatTitle("my document"); // "MY DOCUMENT"
2253
+ * formatTitle(undefined); // "UNTITLED"
2254
+ * ```
2255
+ *
2256
+ * @example
2257
+ * ```typescript
2258
+ * // Use in case-insensitive comparisons
2259
+ * function compareIgnoreCase(str1?: string, str2?: string): boolean {
2260
+ * return toUpperCase(str1) === toUpperCase(str2);
2261
+ * }
2262
+ *
2263
+ * compareIgnoreCase("Hello", "HELLO"); // true
2264
+ * compareIgnoreCase("Test", "test"); // true
2265
+ * ```
2266
+ *
2267
+ * @remarks
2268
+ * - Returns an empty string for any falsy input (undefined, null, empty string)
2269
+ * - Uses the native String.prototype.toUpperCase() method for actual conversion
2270
+ * - Does not modify the original string (strings are immutable in JavaScript)
2271
+ * - Handles all Unicode characters correctly, following locale-independent rules
2272
+ * - More robust than direct .toUpperCase() calls when input might be undefined
2273
+ * - Particularly useful for creating constants, headers, or display text
2274
+ *
2275
+ * @see {@link toLowerCase} Function for converting strings to lowercase
2276
+ * @see {@link toUpperCaseBreak} Function for converting to uppercase and breaking into words
2277
+ */
2278
+ function toUpperCase(str) {
2279
+ if (!str) {
2280
+ return "";
2281
+ }
2282
+ return str.toUpperCase();
2283
+ }
2284
+ /**
2285
+ * Compares two strings for similarity using Levenshtein distance algorithm.
2286
+ *
2287
+ * The Levenshtein distance is a measure of the difference between two strings
2288
+ * by counting the minimum number of single-character edits (insertions, deletions,
2289
+ * or substitutions) required to change one string into another.
2290
+ *
2291
+ * @param {string} str1 - First string to compare
2292
+ * @param {string} str2 - Second string to compare
2293
+ * @returns {number} - A value between 0 and 1, where 1 means identical strings
2294
+ *
2295
+ * @example
2296
+ * ```typescript
2297
+ * stringSimilarity("hello", "hallo"); // 0.8 (4/5 characters match)
2298
+ * stringSimilarity("kitten", "sitting"); // ~0.57 (higher distance)
2299
+ * stringSimilarity("same", "same"); // 1.0 (identical)
2300
+ * ```
2301
+ */
2302
+ function stringSimilarity(str1, str2) {
2303
+ if (str1 === str2) return 1.0;
2304
+ if (!str1 || !str2) return 0.0;
2305
+ const len1 = str1.length;
2306
+ const len2 = str2.length;
2307
+ // Initialize the distance matrix
2308
+ const matrix = Array(len1 + 1).fill(null).map(() => Array(len2 + 1).fill(0));
2309
+ // Fill the first row and column
2310
+ for (let i = 0; i <= len1; i++) matrix[i][0] = i;
2311
+ for (let j = 0; j <= len2; j++) matrix[0][j] = j;
2312
+ // Fill the rest of the matrix
2313
+ for (let i = 1; i <= len1; i++) {
2314
+ for (let j = 1; j <= len2; j++) {
2315
+ const cost = str1[i - 1] === str2[j - 1] ? 0 : 1;
2316
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1,
2317
+ // deletion
2318
+ matrix[i][j - 1] + 1,
2319
+ // insertion
2320
+ matrix[i - 1][j - 1] + cost);
2321
+ }
2322
+ }
2323
+ // Calculate similarity from Levenshtein distance
2324
+ const maxLen = Math.max(len1, len2);
2325
+ if (maxLen === 0) return 1.0;
2326
+ return 1.0 - matrix[len1][len2] / maxLen;
2327
+ }
2328
+ /**
2329
+ * Generates a slug from a string, suitable for URLs or file names.
2330
+ *
2331
+ * @param {string} str - The string to convert to a slug
2332
+ * @param {string} [separator="-"] - The character to use as separator
2333
+ * @returns {string} - The slug string
2334
+ *
2335
+ * @example
2336
+ * ```typescript
2337
+ * slugify("Hello World"); // "hello-world"
2338
+ * slugify("10 Tips & Tricks!"); // "10-tips-tricks"
2339
+ * slugify("My Product (2023 Edition)"); // "my-product-2023-edition"
2340
+ * slugify("Über résumé"); // "uber-resume"
2341
+ * slugify("Blog Post", "_"); // "blog_post"
2342
+ * ```
2343
+ */
2344
+ function slugify(str, separator = "-") {
2345
+ if (!str) return "";
2346
+ return str.normalize("NFD") // Normalize to decomposed form for handling accents
2347
+ .replace(/[\u0300-\u036f]/g, "") // Remove accents/diacritics
2348
+ .toLowerCase() // Convert to lowercase
2349
+ .trim() // Trim whitespace
2350
+ .replace(/[^\w\s-]/g, "") // Remove non-word chars (except spaces and hyphens)
2351
+ .replace(/[\s_]+/g, separator) // Replace spaces and underscores with separator
2352
+ .replace(new RegExp(`${separator}+`, "g"), separator) // Replace multiple separators with single
2353
+ .replace(new RegExp(`^${separator}|${separator}$`, "g"), ""); // Remove leading/trailing separators
2354
+ }
2355
+ /**
2356
+ * Extracts all email addresses from a string.
2357
+ *
2358
+ * @param {string} str - The string to extract emails from
2359
+ * @returns {string[]} - Array of found email addresses
2360
+ *
2361
+ * @example
2362
+ * ```typescript
2363
+ * extractEmails("Contact us at support@example.com or sales@example.com");
2364
+ * // ["support@example.com", "sales@example.com"]
2365
+ * ```
2366
+ */
2367
+ function extractEmails(str) {
2368
+ if (!str) return [];
2369
+ const emailRegex = /([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+)/gi;
2370
+ return str.match(emailRegex) || [];
2371
+ }
2372
+ /**
2373
+ * Extracts all URLs from a string.
2374
+ *
2375
+ * @param {string} str - The string to extract URLs from
2376
+ * @returns {string[]} - Array of found URLs
2377
+ *
2378
+ * @example
2379
+ * ```typescript
2380
+ * extractUrls("Visit https://example.com or http://test.org/page?id=5");
2381
+ * // ["https://example.com", "http://test.org/page?id=5"]
2382
+ * ```
2383
+ */
2384
+ function extractUrls(str) {
2385
+ if (!str) return [];
2386
+ // noinspection RegExpSimplifiable
2387
+ const urlRegex = /(https?:\/\/[^\s]+)/g;
2388
+ return str.match(urlRegex) || [];
2389
+ }
2390
+ /**
2391
+ * Converts a string to a secure hash using SHA-256.
2392
+ *
2393
+ * Note: This is a browser-compatible implementation using the Web Crypto API.
2394
+ * For Node.js environments, you might want to use the crypto module instead.
2395
+ *
2396
+ * @param {string} str - The string to hash
2397
+ * @returns {Promise<string>} - Promise resolving to the hex hash string
2398
+ *
2399
+ * @example
2400
+ * ```typescript
2401
+ * // Browser usage
2402
+ * await hashString("password123");
2403
+ * // "ef92b778bafe771e89245b89ecbc08a44a4e166c06659911881f383d4473e94f"
2404
+ * ```
2405
+ */
2406
+ async function hashString(str) {
2407
+ if (!str) return "";
2408
+ // Convert string to Uint8Array
2409
+ const encoder = new TextEncoder();
2410
+ const data = encoder.encode(str);
2411
+ // Hash the data
2412
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
2413
+ // Convert ArrayBuffer to hex string
2414
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
2415
+ return hashArray.map(b => b.toString(HEX_RADIX).padStart(HEX_PADDING_LENGTH, HEX_PADDING_CHAR)).join("");
2416
+ }
2417
+ /**
2418
+ * Generates a random string with specified length and character set.
2419
+ *
2420
+ * @param {number} length - The length of the random string
2421
+ * @param {string} [charset] - The characters to use (defaults to alphanumeric)
2422
+ * @returns {string} - The generated random string
2423
+ *
2424
+ * @example
2425
+ * ```typescript
2426
+ * // Default alphanumeric string
2427
+ * randomString(8);
2428
+ * // e.g. "A7bC9Df3"
2429
+ *
2430
+ * // Hex string
2431
+ * randomString(6, "0123456789abcdef");
2432
+ * // e.g. "a3f9d2"
2433
+ *
2434
+ * // PIN code
2435
+ * randomString(4, "0123456789");
2436
+ * // e.g. "8302"
2437
+ * ```
2438
+ */
2439
+ function randomString(length, charset) {
2440
+ if (length <= 0) return "";
2441
+ const chars = charset || "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
2442
+ let result = "";
2443
+ // Generate cryptographically secure random values if available
2444
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
2445
+ const values = new Uint32Array(length);
2446
+ crypto.getRandomValues(values);
2447
+ for (let i = 0; i < length; i++) {
2448
+ result += chars[values[i] % chars.length];
2449
+ }
2450
+ } else {
2451
+ // Fallback to Math.random (less secure)
2452
+ for (let i = 0; i < length; i++) {
2453
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
2454
+ }
2455
+ }
2456
+ return result;
2457
+ }
2458
+ /**
2459
+ * Masks a portion of a string, useful for displaying sensitive information.
2460
+ *
2461
+ * @param {string} str - The string to mask
2462
+ * @param {number} [visibleStart=0] - Number of characters to show at the beginning
2463
+ * @param {number} [visibleEnd=0] - Number of characters to show at the end
2464
+ * @param {string} [maskChar="*"] - Character to use for masking
2465
+ * @returns {string} - The masked string
2466
+ *
2467
+ * @example
2468
+ * ```typescript
2469
+ * // Mask credit card
2470
+ * maskString("4111111111111111", 4, 4);
2471
+ * // "4111********1111"
2472
+ *
2473
+ * // Mask email
2474
+ * maskString("user@example.com", 2, 4, "x");
2475
+ * // "usxxxxxx.com"
2476
+ *
2477
+ * // Mask phone number
2478
+ * maskString("+1-555-123-4567", 0, 4);
2479
+ * // "*********4567"
2480
+ * ```
2481
+ */
2482
+ function maskString(str, visibleStart = 0, visibleEnd = 0, maskChar = "*") {
2483
+ if (!str) return "";
2484
+ const length = str.length;
2485
+ if (visibleStart + visibleEnd >= length) return str;
2486
+ const start = str.substring(0, visibleStart);
2487
+ const middle = maskChar.repeat(length - visibleStart - visibleEnd);
2488
+ const end = str.substring(length - visibleEnd);
2489
+ return start + middle + end;
2490
+ }
2491
+ /**
2492
+ * Convert a string to first case (Capitalize first letter of the string).
2493
+ * @param {string} [str] Optional string to join the words with.
2494
+ * @returns {string} String in first case.
2495
+ *
2496
+ * @example
2497
+ * ```TypeScript
2498
+ * toFirstCase("hello world"); // "Hello world"
2499
+ * ```
2500
+ */
2501
+ function toFirstCase(str) {
2502
+ if (!str) {
2503
+ return "";
2504
+ }
2505
+ return (str[0]?.toUpperCase() || "") + str.slice(1).toLowerCase();
2506
+ }
2507
+ /**
2508
+ * Converts a string to a camelCase or PascalCase variable name.
2509
+ *
2510
+ * @param {string} str - The string to convert
2511
+ * @param {boolean} [pascalCase=false] - Whether to use PascalCase (true) or camelCase (false)
2512
+ * @returns {string} - The variable name
2513
+ *
2514
+ * @example
2515
+ * ```typescript
2516
+ * toVariableName("Hello World"); // "helloWorld"
2517
+ * toVariableName("API Response"); // "apiResponse"
2518
+ * toVariableName("first-name"); // "firstName"
2519
+ * toVariableName("user_id", true); // "UserId"
2520
+ * ```
2521
+ */
2522
+ function toVariableName(str, pascalCase = false) {
2523
+ if (!str) return "";
2524
+ // Remove special characters and replace with spaces
2525
+ const cleaned = str.replace(/[^\w\s]/g, " ");
2526
+ // Break into words and process
2527
+ return breakToWords(cleaned).map((word, index) => {
2528
+ // Skip first word for camelCase unless it's PascalCase
2529
+ if (index === 0 && !pascalCase) {
2530
+ return word.toLowerCase();
2531
+ }
2532
+ // Capitalize first letter
2533
+ return toFirstCase(word);
2534
+ }).join("");
2535
+ }
2536
+ /**
2537
+ * Wraps words in a string to ensure each line is no longer than a specified length.
2538
+ *
2539
+ * @param {string} str - The string to wrap
2540
+ * @param {number} [lineLength=80] - Maximum length of each line
2541
+ * @param {string} [breakChar="\n"] - Character(s) to insert at line breaks
2542
+ * @returns {string} - The wrapped string
2543
+ *
2544
+ * @example
2545
+ * ```typescript
2546
+ * const text = "This is a long sentence that needs to be wrapped to fit within a certain width.";
2547
+ *
2548
+ * wordWrap(text, 20);
2549
+ * // "This is a long\nsentence that needs\nto be wrapped to\nfit within a\ncertain width."
2550
+ *
2551
+ * wordWrap(text, 30, "<br>");
2552
+ * // "This is a long sentence that<br>needs to be wrapped to fit<br>within a certain width."
2553
+ * ```
2554
+ */
2555
+ function wordWrap(str, lineLength = DEFAULT_LINE_LENGTH, breakChar = DEFAULT_BREAK_CHAR) {
2556
+ if (!str) return "";
2557
+ if (str.length <= lineLength) return str;
2558
+ const words = str.split(" ");
2559
+ let result = "";
2560
+ let currentLine = "";
2561
+ for (const word of words) {
2562
+ // If adding this word exceeds line length, start a new line
2563
+ if (currentLine.length + word.length + 1 > lineLength) {
2564
+ result += currentLine.trim() + breakChar;
2565
+ currentLine = `${word} `;
2566
+ } else {
2567
+ currentLine += `${word} `;
2568
+ }
2569
+ }
2570
+ // Add the last line
2571
+ result += currentLine.trim();
2572
+ return result;
2573
+ }
2574
+ /**
2575
+ * Creates an excerpt from a longer text by extracting a portion around a search term.
2576
+ * Useful for search result highlighting or previews.
2577
+ *
2578
+ * @param {string} text - The full text to create an excerpt from
2579
+ * @param {string} searchTerm - The search term to find in the text
2580
+ * @param {number} [contextLength=40] - Number of characters to include before and after the search term
2581
+ * @param {string} [ellipsis="..."] - Characters to use for indicating truncated text
2582
+ * @returns {string} - The excerpt with the search term in context
2583
+ *
2584
+ * @example
2585
+ * ```typescript
2586
+ * const article = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam in dui mauris.";
2587
+ *
2588
+ * createExcerpt(article, "dolor", 10);
2589
+ * // "...ipsum dolor sit amet..."
2590
+ *
2591
+ * createExcerpt(article, "consectetur", 15, "[...]");
2592
+ * // "[...]sit amet, consectetur adipiscing[...]"
2593
+ * ```
2594
+ */
2595
+ function createExcerpt(text, searchTerm, contextLength = DEFAULT_CONTEXT_LENGTH, ellipsis = DEFAULT_ELLIPSIS) {
2596
+ if (!text || !searchTerm) return text || "";
2597
+ // Case-insensitive search
2598
+ const lowerText = text.toLowerCase();
2599
+ const lowerSearchTerm = searchTerm.toLowerCase();
2600
+ const index = lowerText.indexOf(lowerSearchTerm);
2601
+ if (index === -1) {
2602
+ // If search term not found, return beginning of text
2603
+ return text.length > contextLength * CONTEXT_MULTIPLIER ? text.substring(0, contextLength * CONTEXT_MULTIPLIER) + ellipsis : text;
2604
+ }
2605
+ // Calculate the start and end positions for the excerpt
2606
+ const startPos = Math.max(0, index - contextLength);
2607
+ const endPos = Math.min(text.length, index + searchTerm.length + contextLength);
2608
+ // Add ellipsis if we're not at the beginning or end
2609
+ const prefix = startPos > 0 ? ellipsis : "";
2610
+ const suffix = endPos < text.length ? ellipsis : "";
2611
+ // Extract the excerpt
2612
+ return prefix + text.substring(startPos, endPos) + suffix;
2613
+ }
2614
+ /**
2615
+ * Normalizes a string by removing accents, diacritics, and converting to lowercase.
2616
+ *
2617
+ * @param {string} str - The string to normalize
2618
+ * @returns {string} - The normalized string
2619
+ *
2620
+ * @example
2621
+ * ```typescript
2622
+ * normalizeString("Café"); // "cafe"
2623
+ * normalizeString("Über"); // "uber"
2624
+ * normalizeString("résumé"); // "resume"
2625
+ * normalizeString("ESPAÑA"); // "espana"
2626
+ * ```
2627
+ */
2628
+ function normalizeString(str) {
2629
+ if (!str) return "";
2630
+ return str.normalize("NFD") // Decompose accented characters
2631
+ .replace(/[\u0300-\u036f]/g, "") // Remove accent marks
2632
+ .toLowerCase(); // Convert to lowercase
2633
+ }
2634
+ /**
2635
+ * Convert a string to upper cases and break into words with optional join or space.
2636
+ * @param {string} [str] String to convert to upper cases and break into words.
2637
+ * @param {string} [join] Optional string to join the words with.
2638
+ * @returns {string} String in upper cases and broken into words.
2639
+ *
2640
+ * @example
2641
+ * ```TypeScript
2642
+ * toUpperCaseBreak("HelloWorld"); // "HELLO WORLD"
2643
+ * ```
2644
+ *
2645
+ * @example
2646
+ * ```TypeScript
2647
+ * toUpperCaseBreak("HelloWorld", "! "); // "HELLO! WORLD"
2648
+ */
2649
+ function toUpperCaseBreak(str, join) {
2650
+ if (!str) {
2651
+ return "";
2652
+ }
2653
+ return breakToWords(str).map(s => s.toUpperCase()).join(join ?? " ");
2654
+ }
2655
+ /**
2656
+ * Converts a string to Sentence case (first word capitalized, rest lowercase) with customizable word separators.
2657
+ *
2658
+ * This function breaks a string into words, capitalizes the first word, converts remaining
2659
+ * words to lowercase, and then joins them using a custom separator (or space by default).
2660
+ *
2661
+ * Unlike `toFirstCase()` which works on a single word, this function processes multi-word
2662
+ * strings from various formats (camelCase, snake_case, etc.) and gives control over how
2663
+ * the words are joined back together.
2664
+ *
2665
+ * @param {string} [str] - The input string to convert
2666
+ * @param {string} [join] - Optional separator to join words (defaults to space)
2667
+ * @returns {string} - The formatted string with first word capitalized and custom separators,
2668
+ * or empty string if input is falsy
2669
+ *
2670
+ * @example
2671
+ * ```typescript
2672
+ * // With default space separator
2673
+ * toFirstCaseBreak("helloWorld"); // "Hello world"
2674
+ * toFirstCaseBreak("SYSTEM_ERROR"); // "System error"
2675
+ * toFirstCaseBreak("api-request-log"); // "Api request log"
2676
+ *
2677
+ * // With custom separators
2678
+ * toFirstCaseBreak("hello_world", "-"); // "Hello-world"
2679
+ * toFirstCaseBreak("userAuthConfig", "."); // "User.auth.config"
2680
+ * toFirstCaseBreak("GET_USER_DATA", "/"); // "Get/user/data"
2681
+ * ```
2682
+ *
2683
+ * @see {@link toFirstCase} For capitalizing a single word
2684
+ * @see {@link toTitleCase} For capitalizing the first letter of every word
2685
+ * @see {@link breakToWords} The underlying function used to split input into words
2686
+ */
2687
+ function toFirstCaseBreak(str, join) {
2688
+ if (!str) {
2689
+ return "";
2690
+ }
2691
+ return breakToWords(str).map((s, i) => i === 0 ? toFirstCase(s) : s.toLowerCase()).join(join ?? " ");
2692
+ }
2693
+ /**
2694
+ * Convert a string to title case (Capitalize first letter of each word).
2695
+ * @param {string} [str] String to convert to title case.
2696
+ * @returns {string} String in title case.
2697
+ *
2698
+ * @example
2699
+ * ```TypeScript
2700
+ * toTitleCase("hello world"); // "Hello World"
2701
+ * ```
2702
+ */
2703
+ function toTitleCase(str) {
2704
+ if (!str) {
2705
+ return "";
2706
+ }
2707
+ return breakToWords(str).map(s => toFirstCase(s)).join(" ");
2708
+ }
2709
+ /**
2710
+ * Convert a string to camel case.
2711
+ * @param {string} [str] String to convert to camel case.
2712
+ * @returns {string} String in camel case.
2713
+ *
2714
+ * @example
2715
+ * ```TypeScript
2716
+ * toCamelCase("hello world"); // "helloWorld"
2717
+ * ```
2718
+ */
2719
+ function toCamelCase(str) {
2720
+ if (!str) {
2721
+ return "";
2722
+ }
2723
+ return breakToWords(str).map((s, i) => i === 0 ? s.toLowerCase() : toFirstCase(s)).join("");
2724
+ }
2725
+ /**
2726
+ * Convert a string to pascal case.
2727
+ * @param {string} [str] String to convert to pascal case.
2728
+ * @returns {string} String in pascal case.
2729
+ *
2730
+ * @example
2731
+ * ```TypeScript
2732
+ * toPascalCase("hello world"); // "HelloWorld"
2733
+ * ```
2734
+ */
2735
+ function toPascalCase(str) {
2736
+ if (!str) {
2737
+ return "";
2738
+ }
2739
+ return breakToWords(str).map(s => toFirstCase(s)).join("");
2740
+ }
2741
+ /**
2742
+ * Convert a string to sentence case. (Capitalize first letter of every sentence).
2743
+ * @param {string} [str] String to convert to sentence case.
2744
+ * @returns {string} String in sentence case.
2745
+ *
2746
+ * @example
2747
+ * ```TypeScript
2748
+ * toSentenceCase("hello world. how are you?"); // "Hello world. How are you?"
2749
+ * ```
2750
+ */
2751
+ function toSentenceCase(str) {
2752
+ return str.split(".").map(s => toFirstCase(s.trim())).join(". ");
2753
+ }
2754
+ /**
2755
+ * Converts a string to snake_case format.
2756
+ *
2757
+ * This function transforms a string from any common case format (camel, pascal, kebab, etc.)
2758
+ * into snake_case, where words are lowercase and separated by underscores.
2759
+ *
2760
+ * When the optional `caps` parameter is set to true, the result will be UPPER_SNAKE_CASE
2761
+ * (also known as SCREAMING_SNAKE_CASE or CONSTANT_CASE).
2762
+ *
2763
+ * @param {string} [str] - The input string to convert
2764
+ * @param {boolean} [caps=false] - When true, converts to UPPER_SNAKE_CASE
2765
+ * @returns {string} - The converted snake_case string, or empty string if input is falsy
2766
+ *
2767
+ * @example
2768
+ * ```typescript
2769
+ * // From various formats to snake_case
2770
+ * toSnakeCase("hello world"); // "hello_world"
2771
+ * toSnakeCase("HelloWorld"); // "hello_world"
2772
+ * toSnakeCase("hello-world"); // "hello_world"
2773
+ * toSnakeCase("HELLO WORLD"); // "hello_world"
2774
+ *
2775
+ * // To UPPER_SNAKE_CASE (CONSTANT_CASE)
2776
+ * toSnakeCase("hello world", true); // "HELLO_WORLD"
2777
+ * toSnakeCase("helloWorld", true); // "HELLO_WORLD"
2778
+ * ```
2779
+ *
2780
+ * @remarks
2781
+ * This function first splits the string into words using `breakToWords()`,
2782
+ * then joins them with underscores, applying the requested case transformation.
2783
+ */
2784
+ function toSnakeCase(str, caps) {
2785
+ if (!str) {
2786
+ return "";
2787
+ }
2788
+ const snake = breakToWords(str).join("_");
2789
+ return caps ? snake.toUpperCase() : snake.toLowerCase();
2790
+ }
2791
+ /**
2792
+ * Convert a string to kebab case.
2793
+ * @param {string} [str] String to convert to kebab case.
2794
+ * @param {boolean} [caps] Whether to convert to upper case.
2795
+ * @returns {string} String in kebab case.
2796
+ *
2797
+ * @example
2798
+ * ```TypeScript
2799
+ * toKebabCase("hello world"); // "hello-world"
2800
+ * ```
2801
+ *
2802
+ * @example
2803
+ * ```TypeScript
2804
+ * toKebabCase("hello world", true); // "HELLO-WORLD"
2805
+ * ```
2806
+ */
2807
+ function toKebabCase(str, caps) {
2808
+ if (!str) {
2809
+ return "";
2810
+ }
2811
+ const kebab = breakToWords(str).join("-");
2812
+ return caps ? kebab.toUpperCase() : kebab.toLowerCase();
2813
+ }
2814
+ /**
2815
+ * Converts a string to a number.
2816
+ * @param {number|string} str String to convert to a number.
2817
+ * @returns {number|undefined} Number or undefined.
2818
+ *
2819
+ * @example
2820
+ * ```TypeScript
2821
+ * toNumber("123"); // 123
2822
+ * ```
2823
+ */
2824
+ function toNumber(str) {
2825
+ return !isNaN(Number(str)) ? Number(str) : undefined;
2826
+ }
2827
+ /**
2828
+ * Remove HTML tags from a string and return plain text.
2829
+ * @param {string} str String to remove HTML tags from.
2830
+ * @returns {string} Plain text.
2831
+ *
2832
+ * @example
2833
+ * ```TypeScript
2834
+ * htmlToText("<h1>Hello World</h1>"); // "Hello World"
2835
+ * ```
2836
+ */
2837
+ function htmlToText(str) {
2838
+ return str.replace(/<[^>]*>?/gm, "");
2839
+ }
2840
+ /**
2841
+ * Converts a singular English word to its plural form.
2842
+ *
2843
+ * This function applies common English pluralization rules to transform
2844
+ * singular words into their plural equivalents. It handles regular patterns
2845
+ * as well as many irregular cases and special rules for different word endings.
2846
+ *
2847
+ * @param {string} str - The singular word to convert to plural form
2848
+ * @returns {string} - The plural form of the word, or the original string if:
2849
+ * - Input is empty/falsy
2850
+ * - The word is already plural
2851
+ *
2852
+ * @example
2853
+ * ```typescript
2854
+ * // Regular plurals
2855
+ * plural("cat"); // "cats"
2856
+ * plural("dog"); // "dogs"
2857
+ *
2858
+ * // Words ending in -y
2859
+ * plural("party"); // "parties"
2860
+ * plural("day"); // "days"
2861
+ *
2862
+ * // Words ending in -f or -fe
2863
+ * plural("wolf"); // "wolves"
2864
+ * plural("knife"); // "knives"
2865
+ *
2866
+ * // Words ending in -o
2867
+ * plural("potato"); // "potatoes"
2868
+ * plural("photo"); // "photos"
2869
+ *
2870
+ * // Irregular plurals
2871
+ * plural("child"); // "children"
2872
+ * plural("man"); // "men"
2873
+ * plural("foot"); // "feet"
2874
+ *
2875
+ * // Latin/Greek words
2876
+ * plural("cactus"); // "cacti"
2877
+ * plural("analysis"); // "analyses"
2878
+ * ```
2879
+ *
2880
+ * @remarks
2881
+ * - This function is designed for English language words only
2882
+ * - It pairs well with the `singular()` function for round-trip conversion
2883
+ */
2884
+ function plural(str) {
2885
+ if (!str) return str;
2886
+ // Combine all rules in priority order
2887
+ const rules = [...EnglishInflectionRules.INVARIANT, ...EnglishInflectionRules.IRREGULAR.toPlural, ...EnglishInflectionRules.LATIN_GREEK.toPlural, ...EnglishInflectionRules.PATTERN_RULES.toPlural, ...EnglishInflectionRules.DEFAULT.toPlural];
2888
+ // Apply the first matching rule
2889
+ for (const rule of rules) {
2890
+ if (rule.regex.test(str)) {
2891
+ return str.replace(rule.regex, typeof rule.replacement === "string" ? rule.replacement : rule.replacement(str));
2892
+ }
2893
+ }
2894
+ return str;
2895
+ }
2896
+ /**
2897
+ * Truncates a string to a specified length and adds an ellipsis if needed.
2898
+ *
2899
+ * @param {string} str - The string to truncate
2900
+ * @param {number} length - Maximum length of the truncated string (excluding ellipsis)
2901
+ * @param {string} [ellipsis="..."] - The ellipsis to add to truncated strings
2902
+ * @returns {string} - The truncated string with ellipsis if needed
2903
+ *
2904
+ * @example
2905
+ * ```typescript
2906
+ * truncate("This is a long sentence", 10); // "This is a..."
2907
+ * truncate("Short", 10); // "Short"
2908
+ * truncate("Custom ellipsis", 6, " [more]"); // "Custom [more]"
2909
+ * ```
2910
+ */
2911
+ function truncate(str, length, ellipsis = "...") {
2912
+ if (!str) return "";
2913
+ if (str.length <= length) return str;
2914
+ return str.substring(0, length) + ellipsis;
2915
+ }
2916
+ /**
2917
+ * Pads a string to a specified length with a specified character.
2918
+ *
2919
+ * @param {string} str - The string to pad
2920
+ * @param {number} length - The target length of the resulting string
2921
+ * @param {string} [char=" "] - The character to pad with (defaults to space)
2922
+ * @param {boolean} [padEnd=true] - Whether to pad at the end (true) or beginning (false)
2923
+ * @returns {string} - The padded string
2924
+ *
2925
+ * @example
2926
+ * ```typescript
2927
+ * padString("Hello", 10); // "Hello "
2928
+ * padString("Hello", 10, "*"); // "Hello*****"
2929
+ * padString("Hello", 10, "0", false); // "00000Hello"
2930
+ * ```
2931
+ */
2932
+ function padString(str, length, char = " ", padEnd = true) {
2933
+ if (!str) return "";
2934
+ if (str.length >= length) return str;
2935
+ const padding = char.repeat(length - str.length);
2936
+ return padEnd ? str + padding : padding + str;
2937
+ }
2938
+ /**
2939
+ * Escapes special characters in a string for use in regular expressions.
2940
+ *
2941
+ * @param {string} str - The string to escape
2942
+ * @returns {string} - The escaped string safe for use in RegExp
2943
+ *
2944
+ * @example
2945
+ * ```typescript
2946
+ * escapeRegExp("hello.world*"); // "hello\.world\*"
2947
+ *
2948
+ * // Usage in RegExp:
2949
+ * const userInput = "hello.world*";
2950
+ * const regex = new RegExp(escapeRegExp(userInput));
2951
+ * ```
2952
+ */
2953
+ function escapeRegExp(str) {
2954
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2955
+ }
2956
+ /**
2957
+ * Capitalizes each word in a string according to title case rules.
2958
+ *
2959
+ * This function implements proper title case rules, where:
2960
+ * - The first and last words are always capitalized
2961
+ * - All other major words are capitalized
2962
+ * - Minor words (articles, conjunctions, short prepositions) are not capitalized
2963
+ * unless they are the first or last word
2964
+ *
2965
+ * @param {string} str - The string to convert to title case
2966
+ * @returns {string} - The string in proper title case format
2967
+ *
2968
+ * @example
2969
+ * ```typescript
2970
+ * toProperTitleCase("the quick brown fox jumps over the lazy dog");
2971
+ * // "The Quick Brown Fox Jumps over the Lazy Dog"
2972
+ *
2973
+ * toProperTitleCase("a tale of two cities");
2974
+ * // "A Tale of Two Cities"
2975
+ * ```
2976
+ */
2977
+ function toProperTitleCase(str) {
2978
+ if (!str) return "";
2979
+ const minorWords = ["a", "an", "and", "as", "at", "but", "by", "for", "in", "nor", "of", "on", "or", "per", "the", "to", "via", "vs"];
2980
+ const words = str.toLowerCase().split(/\s+/);
2981
+ const result = words.map((word, index) => {
2982
+ // Always capitalize first and last word
2983
+ if (index === 0 || index === words.length - 1) {
2984
+ return toFirstCase(word);
2985
+ }
2986
+ // Don't capitalize minor words
2987
+ if (minorWords.includes(word.toLowerCase())) {
2988
+ return word.toLowerCase();
2989
+ }
2990
+ // Capitalize major words
2991
+ return toFirstCase(word);
2992
+ });
2993
+ return result.join(" ");
2994
+ }
2995
+ /**
2996
+ * Implementation of the format function that handles both overloads.
2997
+ */
2998
+ function format(template, ...values) {
2999
+ if (!template) return "";
3000
+ // If the first value is an object (for named placeholders), use it directly
3001
+ if (values.length === 1 && typeof values[0] === "object" && values[0] !== null && !Array.isArray(values[0])) {
3002
+ const replacements = values[0];
3003
+ return template.replace(/{([^{}]+)}/g, (match, key) => {
3004
+ const value = replacements[key];
3005
+ return value !== undefined ? String(value) : match;
3006
+ });
3007
+ }
3008
+ // Otherwise use indexed placeholders
3009
+ return template.replace(/{(\d+)}/g, (match, index) => {
3010
+ const value = values[Number(index)];
3011
+ return value !== undefined ? String(value) : match;
3012
+ });
3013
+ }
3014
+ /**
3015
+ * Counts the occurrences of a substring within a string.
3016
+ *
3017
+ * @param {string} str - The string to search within
3018
+ * @param {string} searchValue - The substring to search for
3019
+ * @param {boolean} [caseSensitive=true] - Whether the search should be case-sensitive
3020
+ * @returns {number} - The number of occurrences
3021
+ *
3022
+ * @example
3023
+ * ```typescript
3024
+ * countOccurrences("hello hello world", "hello"); // 2
3025
+ * countOccurrences("Hello hello", "hello", false); // 2
3026
+ * countOccurrences("Hello hello", "hello", true); // 1
3027
+ * ```
3028
+ */
3029
+ function countOccurrences(str, searchValue, caseSensitive = true) {
3030
+ if (!str || !searchValue) return 0;
3031
+ const regex = new RegExp(escapeRegExp(searchValue), caseSensitive ? "g" : "gi");
3032
+ const matches = str.match(regex);
3033
+ return matches ? matches.length : 0;
3034
+ }
3035
+ /**
3036
+ * Reverses a string.
3037
+ *
3038
+ * @param {string} str - The string to reverse
3039
+ * @returns {string} - The reversed string
3040
+ *
3041
+ * @example
3042
+ * ```typescript
3043
+ * reverse("hello"); // "olleh"
3044
+ * reverse("12345"); // "54321"
3045
+ * ```
3046
+ */
3047
+ function reverse(str) {
3048
+ if (!str) return "";
3049
+ return str.split("").reverse().join("");
3050
+ }
3051
+ /**
3052
+ * Checks if a string contains only alphanumeric characters.
3053
+ *
3054
+ * @param {string} str - The string to check
3055
+ * @returns {boolean} - True if the string contains only alphanumeric characters
3056
+ *
3057
+ * @example
3058
+ * ```typescript
3059
+ * isAlphanumeric("abc123"); // true
3060
+ * isAlphanumeric("abc-123"); // false
3061
+ * ```
3062
+ */
3063
+ function isAlphanumeric(str) {
3064
+ if (!str) return false;
3065
+ return /^[a-zA-Z0-9]+$/.test(str);
3066
+ }
3067
+ /**
3068
+ * Removes all whitespace from a string.
3069
+ *
3070
+ * @param {string} str - The string to process
3071
+ * @returns {string} - The string with all whitespace removed
3072
+ *
3073
+ * @example
3074
+ * ```typescript
3075
+ * removeWhitespace("hello world"); // "helloworld"
3076
+ * removeWhitespace(" spaces tabs "); // "spacestabs"
3077
+ * ```
3078
+ */
3079
+ function removeWhitespace(str) {
3080
+ if (!str) return "";
3081
+ return str.replace(/\s+/g, "");
3082
+ }
3083
+ /**
3084
+ * Converts plural English words to their singular form.
3085
+ *
3086
+ * This function applies a series of linguistic rules and exceptions to transform
3087
+ * plural English words into their singular equivalents. It handles irregular plurals
3088
+ * (e.g., "children" → "child"), common suffix patterns (e.g., "parties" → "party"),
3089
+ * and Latin-derived words (e.g., "cacti" → "cactus").
3090
+ *
3091
+ * The function applies rules in priority order, from most specific to most general,
3092
+ * to ensure correct handling of special cases.
3093
+ *
3094
+ * @param {string} str - The plural word to convert to singular form
3095
+ * @returns {string} - The singular form of the word, or the original string if:
3096
+ * - Input is empty/falsy
3097
+ * - No singular form is recognized
3098
+ * - The word is already singular
3099
+ *
3100
+ * @example
3101
+ * ```typescript
3102
+ * // Irregular plurals
3103
+ * singular("children"); // "child"
3104
+ * singular("men"); // "man"
3105
+ * singular("feet"); // "foot"
3106
+ *
3107
+ * // Common patterns
3108
+ * singular("cats"); // "cat"
3109
+ * singular("boxes"); // "box"
3110
+ * singular("parties"); // "party"
3111
+ * singular("wolves"); // "wolf"
3112
+ *
3113
+ * // Latin-derived words
3114
+ * singular("cacti"); // "cactus"
3115
+ * singular("phenomena"); // "phenomenon"
3116
+ * ```
3117
+ *
3118
+ * @remarks
3119
+ * - This function is designed for English language words only
3120
+ * - It handles many common cases but isn't exhaustive for all English plurals
3121
+ * - Words that are identical in singular and plural form (e.g., "sheep") are returned unchanged
3122
+ */
3123
+ function singular(str) {
3124
+ if (!str) return str;
3125
+ // Combine all rules in priority order
3126
+ const rules = [...EnglishInflectionRules.INVARIANT, ...EnglishInflectionRules.IRREGULAR.toSingular, ...EnglishInflectionRules.LATIN_GREEK.toSingular, ...EnglishInflectionRules.PATTERN_RULES.toSingular, ...EnglishInflectionRules.DEFAULT.toSingular];
3127
+ // Iterate over rules and apply the first matching one
3128
+ for (const rule of rules) {
3129
+ if (rule.regex.test(str)) {
3130
+ return str.replace(rule.regex, typeof rule.replacement === "string" ? rule.replacement : rule.replacement(str));
3131
+ }
3132
+ }
3133
+ // Return the original string if no rules match
3134
+ return str;
3135
+ }
3136
+
3137
+ // noinspection JSUnusedGlobalSymbols
3138
+ /**
3139
+ * Type-safe utility to check if a value is an array of a specific type.
3140
+ *
3141
+ * This function acts as a type guard that not only checks if the provided value
3142
+ * is an array (using the native Array.isArray method), but also narrows the TypeScript
3143
+ * type to an array of the generic type T. This enables safer handling of potentially
3144
+ * array-typed values with full type inference in the conditional branches.
3145
+ *
3146
+ * @template T The expected element type of the array
3147
+ * @param {T | T[] | undefined} value The value to check, which could be a single item,
3148
+ * an array of items, or undefined
3149
+ * @returns {value is T[]} Type predicate that narrows the type to T[] when true
3150
+ *
3151
+ * @remarks
3152
+ * While this function essentially wraps Array.isArray, its value comes from the
3153
+ * TypeScript type narrowing it provides, making it especially useful in code that
3154
+ * needs to handle both single items and collections of items with type safety.
3155
+ *
3156
+ * The function properly handles undefined values by returning false, making it safe
3157
+ * to use with optional parameters or potentially undefined values.
3158
+ *
3159
+ * @example
3160
+ * ```typescript
3161
+ * // Function that can accept either a single user or multiple users
3162
+ * async function createUser(userOrUsers: UserDto | UserDto[] | undefined): Promise<User | User[]> {
3163
+ * // TypeScript knows userOrUsers is UserDto[] in this branch
3164
+ * if (isArray<UserDto>(userOrUsers)) {
3165
+ * return Promise.all(userOrUsers.map(user => userService.createUser(user)));
3166
+ * }
3167
+ * // TypeScript knows userOrUsers is UserDto or undefined in this branch
3168
+ * else if (userOrUsers) {
3169
+ * return userService.createUser(userOrUsers);
3170
+ * }
3171
+ * else {
3172
+ * throw new BadRequestException('User data is required');
3173
+ * }
3174
+ * }
3175
+ * ```
3176
+ */
3177
+ function isArray(value) {
3178
+ return Array.isArray(value);
3179
+ }
3180
+ /**
3181
+ * Type-safe utility to check if a value is a non-array object of a specific type.
3182
+ *
3183
+ * This function serves as a type guard that checks if the provided value is an object
3184
+ * (but not an array) and narrows the TypeScript type to the generic type T. It properly
3185
+ * excludes null, arrays, and primitive values, focusing only on object instances.
3186
+ *
3187
+ * @template T The expected object type to narrow to when the check passes
3188
+ * @param {T | T[] | undefined} [value] The value to check, which could be a single object,
3189
+ * an array of objects, or undefined
3190
+ * @returns {value is T} Type predicate that narrows the type to T when true
3191
+ *
3192
+ * @remarks
3193
+ * This function performs three checks to ensure the value is a proper object:
3194
+ * 1. It's not an array (using Array.isArray)
3195
+ * 2. It's of type "object" according to JavaScript's typeof operator
3196
+ * 3. It's not null (which would pass the typeof check but isn't a valid object)
3197
+ *
3198
+ * This is particularly useful when handling parameters that could be either an object
3199
+ * or a simpler identifier (like an ID), allowing for type-safe conditional logic.
3200
+ *
3201
+ * @example
3202
+ * ```typescript
3203
+ * // Function that can accept either a user ID or a user object
3204
+ * async function getUserInfo(userIdOrUser: number | User | undefined): Promise<UserInfo> {
3205
+ * // TypeScript knows userIdOrUser is User in this branch
3206
+ * if (isObject<User>(userIdOrUser)) {
3207
+ * // Can safely access object properties
3208
+ * return await userService.getUserInfo(userIdOrUser.id);
3209
+ * }
3210
+ * // TypeScript knows userIdOrUser is number or undefined in this branch
3211
+ * else {
3212
+ * return await userService.getUserInfo(userIdOrUser);
3213
+ * }
3214
+ * }
3215
+ *
3216
+ * // Works with optional parameters too
3217
+ * function formatUserName(user?: User | string): string {
3218
+ * if (isObject<User>(user)) {
3219
+ * return `${user.firstName} ${user.lastName}`;
3220
+ * }
3221
+ * return user || 'Guest';
3222
+ * }
3223
+ * ```
3224
+ */
3225
+ function isObject(value) {
3226
+ return !Array.isArray(value) && typeof value === "object" && value !== null;
3227
+ }
3228
+ /**
3229
+ * Type-safe utility to check if a value is an object with a specific property.
3230
+ *
3231
+ * This function acts as a type guard that determines if an unknown value is an object
3232
+ * that contains a specified property, and narrows the TypeScript type to the generic
3233
+ * type T when true. This provides a robust way to implement duck typing in TypeScript,
3234
+ * allowing for safe property access in the conditional branches.
3235
+ *
3236
+ * @template T The expected object type that should contain the property
3237
+ * @param {unknown} value Any value to check, with no type constraints
3238
+ * @param {keyof T} propertyName The name of the property that should exist on the object
3239
+ * @returns {value is T} Type predicate that narrows the type to T when true
3240
+ *
3241
+ * @remarks
3242
+ * This function uses Object.prototype.hasOwnProperty.call to safely check for property
3243
+ * existence, even if the object has a custom implementation of hasOwnProperty or if the
3244
+ * property is named 'hasOwnProperty'.
3245
+ *
3246
+ * Unlike the simpler isObject utility, this function can distinguish between different
3247
+ * object types based on their properties, making it ideal for discriminating between
3248
+ * different interface implementations or handling union types.
3249
+ *
3250
+ * @example
3251
+ * ```typescript
3252
+ * // Check if an object has the properties of a User interface
3253
+ * interface User {
3254
+ * id: number;
3255
+ * name: string;
3256
+ * }
3257
+ *
3258
+ * interface Order {
3259
+ * orderNumber: string;
3260
+ * items: string[];
3261
+ * }
3262
+ *
3263
+ * // Function that can handle different object types
3264
+ * function processEntity(entity: unknown): void {
3265
+ * // Check if entity has 'id' property, indicating it's a User
3266
+ * if (isObjectWith<User>(entity, 'id')) {
3267
+ * // TypeScript knows entity is User in this branch
3268
+ * console.log(`Processing user: ${entity.name}`);
3269
+ * }
3270
+ * // Check if entity has 'orderNumber' property, indicating it's an Order
3271
+ * else if (isObjectWith<Order>(entity, 'orderNumber')) {
3272
+ * // TypeScript knows entity is Order in this branch
3273
+ * console.log(`Processing order: ${entity.orderNumber} with ${entity.items.length} items`);
3274
+ * }
3275
+ * else {
3276
+ * console.log('Unknown entity type');
3277
+ * }
3278
+ * }
3279
+ *
3280
+ * // Useful for API responses where types may vary
3281
+ * async function handleResponse(response: unknown): Promise<void> {
3282
+ * if (isObjectWith<ErrorResponse>(response, 'errorCode')) {
3283
+ * throw new ApiException(response.errorCode, response.message);
3284
+ * }
3285
+ * else if (isObjectWith<SuccessResponse>(response, 'data')) {
3286
+ * await processData(response.data);
3287
+ * }
3288
+ * }
3289
+ * ```
3290
+ */
3291
+ function isObjectWith(value, propertyName) {
3292
+ return Object.prototype.hasOwnProperty.call(value, propertyName);
3293
+ }
3294
+
3295
+ /**
3296
+ * Template tags for string transformations
3297
+ *
3298
+ * This enum defines all available template tags that can be used with the `applyTemplate`
3299
+ * and `applyTemplates` functions. Each tag corresponds to a specific string transformation
3300
+ * function from the string.utils module.
3301
+ *
3302
+ * Template tags are represented as strings in the format `#{transformationName}`. When these
3303
+ * tags appear in template strings, they are replaced with the result of applying the
3304
+ * corresponding transformation to the provided value.
3305
+ *
3306
+ * @see {@link applyTemplate} For applying a single value to a template
3307
+ * @see {@link applyTemplates} For applying multiple values to a template
3308
+ */
3309
+ exports.TemplateTag = void 0;
3310
+ (function (TemplateTag) {
3311
+ TemplateTag["UPPER_CASE"] = "#{upperCase}";
3312
+ TemplateTag["SNAKE_CASE"] = "#{snakeCase}";
3313
+ TemplateTag["UPPER_SNAKE_CASE"] = "#{upperSnakeCase}";
3314
+ TemplateTag["LOWER_CASE"] = "#{lowerCase}";
3315
+ TemplateTag["SENTENCE_CASE"] = "#{sentenceCase}";
3316
+ TemplateTag["FIRST_CASE"] = "#{firstCase}";
3317
+ TemplateTag["CAMEL_CASE"] = "#{camelCase}";
3318
+ TemplateTag["PASCAL_CASE"] = "#{pascalCase}";
3319
+ TemplateTag["KEBAB_CASE"] = "#{kebabCase}";
3320
+ TemplateTag["UPPER_KEBAB_CASE"] = "#{upperKebabCase}";
3321
+ TemplateTag["TITLE_CASE"] = "#{titleCase}";
3322
+ TemplateTag["LOWER_CASE_BREAK"] = "#{lowerCaseBreak}";
3323
+ TemplateTag["UPPER_CASE_BREAK"] = "#{upperCaseBreak}";
3324
+ TemplateTag["FIRST_CASE_BREAK"] = "#{firstCaseBreak}";
3325
+ TemplateTag["SINGULAR"] = "#{singular}";
3326
+ TemplateTag["NUMBER"] = "#{number}";
3327
+ TemplateTag["HTML_TO_TEXT"] = "#{htmlToText}";
3328
+ // FORMAT = "#{format}",
3329
+ })(exports.TemplateTag || (exports.TemplateTag = {}));
3330
+
3331
+ // noinspection JSUnusedGlobalSymbols
3332
+ /**
3333
+ * Applies a value to a template string containing transformation tags.
3334
+ *
3335
+ * This function replaces template tags in a string with transformed versions of a provided value.
3336
+ * It's particularly useful for creating dynamic messages, error notifications, or any text
3337
+ * that needs to include the same value in different formats.
3338
+ *
3339
+ * Each template tag is replaced with the result of applying the corresponding transformation
3340
+ * function to the provided value. If a tag isn't present in the template string, that
3341
+ * transformation is skipped.
3342
+ *
3343
+ * Available template tags:
3344
+ * - `#{upperCase}` - Converts to UPPERCASE ("USER")
3345
+ * - `#{snakeCase}` - Converts to snake_case ("user_profile")
3346
+ * - `#{upperSnakeCase}` - Converts to UPPER_SNAKE_CASE ("USER_PROFILE")
3347
+ * - `#{lowerCase}` - Converts to lowercase ("user")
3348
+ * - `#{sentenceCase}` - Converts to Sentence case ("User profile")
3349
+ * - `#{firstCase}` - Converts to First case ("User")
3350
+ * - `#{camelCase}` - Converts to camelCase ("userProfile")
3351
+ * - `#{pascalCase}` - Converts to PascalCase ("UserProfile")
3352
+ * - `#{kebabCase}` - Converts to kebab-case ("user-profile")
3353
+ * - `#{upperKebabCase}` - Converts to KEBAB-CASE ("USER-PROFILE")
3354
+ * - `#{titleCase}` - Converts to Title Case ("User Profile")
3355
+ * - `#{lowerCaseBreak}` - Breaks words and converts to lowercase ("user profile")
3356
+ * - `#{upperCaseBreak}` - Breaks words and converts to UPPERCASE ("USER PROFILE")
3357
+ * - `#{firstCaseBreak}` - Breaks words and applies First case ("User profile")
3358
+ * - `#{singular}` - Converts plural to singular form ("users" → "user")
3359
+ * - `#{number}` - Converts to a number if possible ("123" → 123)
3360
+ * - `#{htmlToText}` - Removes HTML tags ("<b>User</b>" → "User")
3361
+ * - `#{format}` - Formats a string with placeholders (used with additional parameters)
3362
+ *
3363
+ * @example
3364
+ * ```typescript
3365
+ * // Error message with different versions of the same term
3366
+ * applyTemplate(
3367
+ * 'Cannot create a #{lowerCase} with this email. #{sentenceCase} already exists.',
3368
+ * 'User'
3369
+ * );
3370
+ * // Output: "Cannot create a user with this email. User already exists."
3371
+ * ```
3372
+ *
3373
+ * @example
3374
+ * ```typescript
3375
+ * // Using multiple transformations of the same value
3376
+ * applyTemplate(
3377
+ * 'Model: #{pascalCase}\nTable: #{snakeCase}\nAPI Path: #{kebabCase}',
3378
+ * 'blogPost'
3379
+ * );
3380
+ * // Output:
3381
+ * // Model: BlogPost
3382
+ * // Table: blog_post
3383
+ * // API Path: blog-post
3384
+ * ```
3385
+ *
3386
+ * @param {string} str - Template string containing transformation tags
3387
+ * @param {string} prefix - The prefix to update with
3388
+ * @returns {string} - The template with all tags replaced by transformed values
3389
+ *
3390
+ * @see {@link TemplateTag} For the full list of available template tags
3391
+ * @see {@link applyTemplates} For applying multiple different values to a template
3392
+ */
3393
+ function applyTemplate(str, prefix) {
3394
+ // Apply all available template tags
3395
+ return str.replace(exports.TemplateTag.UPPER_CASE, toUpperCase(prefix)).replace(exports.TemplateTag.SNAKE_CASE, toSnakeCase(prefix)).replace(exports.TemplateTag.UPPER_SNAKE_CASE, toSnakeCase(prefix, true)).replace(exports.TemplateTag.LOWER_CASE, toLowerCase(prefix)).replace(exports.TemplateTag.SENTENCE_CASE, toSentenceCase(prefix)).replace(exports.TemplateTag.FIRST_CASE, toFirstCase(prefix)).replace(exports.TemplateTag.CAMEL_CASE, toCamelCase(prefix)).replace(exports.TemplateTag.PASCAL_CASE, toPascalCase(prefix)).replace(exports.TemplateTag.KEBAB_CASE, toKebabCase(prefix)).replace(exports.TemplateTag.UPPER_KEBAB_CASE, toKebabCase(prefix, true)).replace(exports.TemplateTag.TITLE_CASE, toTitleCase(prefix)).replace(exports.TemplateTag.LOWER_CASE_BREAK, toLowerCaseBreak(prefix)).replace(exports.TemplateTag.UPPER_CASE_BREAK, toUpperCaseBreak(prefix)).replace(exports.TemplateTag.FIRST_CASE_BREAK, toFirstCaseBreak(prefix)).replace(exports.TemplateTag.SINGULAR, singular(prefix)).replace(exports.TemplateTag.NUMBER, String(toNumber(prefix) ?? prefix)).replace(exports.TemplateTag.HTML_TO_TEXT, htmlToText(prefix));
3396
+ }
3397
+ /**
3398
+ * Applies multiple named template transformations to a string with different values for each prefix.
3399
+ *
3400
+ * This advanced templating function allows you to use multiple different values within a single
3401
+ * template string, each with their own set of transformation tags. Unlike `applyTemplate` which
3402
+ * applies one value to all tags, this function lets you specify different values for different
3403
+ * prefixes, enabling complex template scenarios with multiple entities.
3404
+ *
3405
+ * Each prefix in the template is identified by a namespace (e.g., `user`, `post`) followed by
3406
+ * a dot and the transformation tag (e.g., `#{user.lowerCase}`, `#{post.titleCase}`). This allows
3407
+ * for sophisticated template generation where different parts of the text need different source
3408
+ * values with various transformations applied.
3409
+ *
3410
+ * This is particularly useful for generating complex messages, code templates, documentation,
3411
+ * or any text that involves multiple entities that need to be formatted differently within
3412
+ * the same template.
3413
+ *
3414
+ * @param {string} str - Template string containing namespaced transformation tags in the format
3415
+ * `#{prefix.transformationType}` where prefix matches keys in the prefixes object
3416
+ * @param {Record<string, string>} prefixes - Object mapping prefix names to their corresponding values.
3417
+ * Each key becomes a namespace in the template, and the value
3418
+ * is the string that will be transformed according to the tags.
3419
+ * @returns {string} The template string with all namespaced tags replaced by their transformed values
3420
+ *
3421
+ * @example
3422
+ * ```typescript
3423
+ * // Multi-entity message generation
3424
+ * const message = applyTemplates(
3425
+ * 'User #{user.lowerCase} created a new #{entity.sentenceCase} titled "#{title.titleCase}"',
3426
+ * {
3427
+ * user: 'JohnDoe',
3428
+ * entity: 'blogPost',
3429
+ * title: 'my first programming tutorial'
3430
+ * }
3431
+ * );
3432
+ * // Returns: "User johndoe created a new Blog post titled "My First Programming Tutorial""
3433
+ * ```
3434
+ *
3435
+ * @example
3436
+ * ```typescript
3437
+ * // Code generation with multiple entities
3438
+ * const codeTemplate = applyTemplates(
3439
+ * `class #{model.pascalCase} {
3440
+ * constructor(private #{service.camelCase}: #{service.pascalCase}Service) {}
3441
+ *
3442
+ * async create#{model.pascalCase}(data: #{model.pascalCase}Data): Promise<#{model.pascalCase}> {
3443
+ * return this.#{service.camelCase}.create(data);
3444
+ * }
3445
+ * }`,
3446
+ * {
3447
+ * model: 'user-profile',
3448
+ * service: 'database'
3449
+ * }
3450
+ * );
3451
+ * // Generates a complete class with proper naming conventions
3452
+ * ```
3453
+ *
3454
+ * @example
3455
+ * ```typescript
3456
+ * // API documentation generation
3457
+ * const apiDoc = applyTemplates(
3458
+ * `## #{endpoint.titleCase}
3459
+ *
3460
+ * **URL:** \`/api/#{endpoint.kebabCase}\`
3461
+ * **Method:** #{method.upperCase}
3462
+ * **Model:** #{model.pascalCase}
3463
+ *
3464
+ * Creates a new #{model.lowerCaseBreak} in the system.`,
3465
+ * {
3466
+ * endpoint: 'userProfiles',
3467
+ * method: 'post',
3468
+ * model: 'UserProfile'
3469
+ * }
3470
+ * );
3471
+ * ```
3472
+ *
3473
+ * @example
3474
+ * ```typescript
3475
+ * // Database migration script generation
3476
+ * const migration = applyTemplates(
3477
+ * `CREATE TABLE #{table.snakeCase} (
3478
+ * id SERIAL PRIMARY KEY,
3479
+ * #{field.snakeCase} VARCHAR(255) NOT NULL,
3480
+ * created_at TIMESTAMP DEFAULT NOW()
3481
+ * );
3482
+ *
3483
+ * CREATE INDEX idx_#{table.snakeCase}_#{field.snakeCase} ON #{table.snakeCase}(#{field.snakeCase});`,
3484
+ * {
3485
+ * table: 'UserProfiles',
3486
+ * field: 'emailAddress'
3487
+ * }
3488
+ * );
3489
+ * ```
3490
+ *
3491
+ * @remarks
3492
+ * - Each prefix in the prefixes object becomes a namespace in the template
3493
+ * - Template tags must follow the format `#{prefix.transformationType}`
3494
+ * - All transformation types available in `applyTemplate` are supported
3495
+ * - If a prefixed tag is not found in the template, it's simply ignored
3496
+ * - The function processes all prefixes and their transformations in the order they appear
3497
+ * - Supports the same transformation types as the TemplateTag enum
3498
+ * - More flexible than `applyTemplate` but with slightly more complex syntax
3499
+ *
3500
+ * @see {@link applyTemplate} For applying a single value to multiple transformation tags
3501
+ * @see {@link TemplateTag} For the complete list of available transformation types
3502
+ */
3503
+ function applyTemplates(str, prefixes) {
3504
+ let result = str;
3505
+ // Process each prefix
3506
+ for (const [key, prefix] of Object.entries(prefixes)) {
3507
+ // Replace all possible template tags for this prefix
3508
+ // eslint-disable-next-line no-loop-func
3509
+ Object.values(exports.TemplateTag).forEach(tag => {
3510
+ // Convert #{tag} to #{prefix.tag}
3511
+ const prefixedTag = tag.replace("#{", `#{${key}.`);
3512
+ if (result.includes(prefixedTag)) {
3513
+ // Get the transformation function name
3514
+ const templateFunction = tag.replace(/#{(.+)}/, "$1");
3515
+ // Apply the appropriate transformation based on the tag
3516
+ let replacement = prefix;
3517
+ switch (templateFunction) {
3518
+ case exports.TemplateTag.UPPER_CASE:
3519
+ replacement = toUpperCase(prefix);
3520
+ break;
3521
+ case exports.TemplateTag.SNAKE_CASE:
3522
+ replacement = toSnakeCase(prefix);
3523
+ break;
3524
+ case exports.TemplateTag.UPPER_SNAKE_CASE:
3525
+ replacement = toSnakeCase(prefix, true);
3526
+ break;
3527
+ case exports.TemplateTag.LOWER_CASE:
3528
+ replacement = toLowerCase(prefix);
3529
+ break;
3530
+ case exports.TemplateTag.SENTENCE_CASE:
3531
+ replacement = toSentenceCase(prefix);
3532
+ break;
3533
+ case exports.TemplateTag.FIRST_CASE:
3534
+ replacement = toFirstCase(prefix);
3535
+ break;
3536
+ case exports.TemplateTag.CAMEL_CASE:
3537
+ replacement = toCamelCase(prefix);
3538
+ break;
3539
+ case exports.TemplateTag.PASCAL_CASE:
3540
+ replacement = toPascalCase(prefix);
3541
+ break;
3542
+ case exports.TemplateTag.KEBAB_CASE:
3543
+ replacement = toKebabCase(prefix);
3544
+ break;
3545
+ case exports.TemplateTag.UPPER_KEBAB_CASE:
3546
+ replacement = toKebabCase(prefix, true);
3547
+ break;
3548
+ case exports.TemplateTag.TITLE_CASE:
3549
+ replacement = toTitleCase(prefix);
3550
+ break;
3551
+ case exports.TemplateTag.LOWER_CASE_BREAK:
3552
+ replacement = toLowerCaseBreak(prefix);
3553
+ break;
3554
+ case exports.TemplateTag.UPPER_CASE_BREAK:
3555
+ replacement = toUpperCaseBreak(prefix);
3556
+ break;
3557
+ case exports.TemplateTag.FIRST_CASE_BREAK:
3558
+ replacement = toFirstCaseBreak(prefix);
3559
+ break;
3560
+ case exports.TemplateTag.SINGULAR:
3561
+ replacement = singular(prefix);
3562
+ break;
3563
+ case exports.TemplateTag.NUMBER:
3564
+ replacement = String(toNumber(prefix) ?? prefix);
3565
+ break;
3566
+ case exports.TemplateTag.HTML_TO_TEXT:
3567
+ replacement = htmlToText(prefix);
3568
+ break;
3569
+ }
3570
+ result = result.replace(prefixedTag, replacement);
3571
+ }
3572
+ });
3573
+ }
3574
+ return result;
3575
+ }
3576
+
3577
+ exports.CHARACTERS_TO_REMOVE = CHARACTERS_TO_REMOVE;
3578
+ exports.CONTEXT_MULTIPLIER = CONTEXT_MULTIPLIER;
3579
+ exports.DEFAULT_BREAK_CHAR = DEFAULT_BREAK_CHAR;
3580
+ exports.DEFAULT_CONTEXT_LENGTH = DEFAULT_CONTEXT_LENGTH;
3581
+ exports.DEFAULT_ELLIPSIS = DEFAULT_ELLIPSIS;
3582
+ exports.DEFAULT_LINE_LENGTH = DEFAULT_LINE_LENGTH;
3583
+ exports.EnglishInflectionRules = EnglishInflectionRules;
3584
+ exports.HEX_PADDING_CHAR = HEX_PADDING_CHAR;
3585
+ exports.HEX_PADDING_LENGTH = HEX_PADDING_LENGTH;
3586
+ exports.HEX_RADIX = HEX_RADIX;
3587
+ exports.applyTemplate = applyTemplate;
3588
+ exports.applyTemplates = applyTemplates;
3589
+ exports.breakToWords = breakToWords;
3590
+ exports.countOccurrences = countOccurrences;
3591
+ exports.createExcerpt = createExcerpt;
3592
+ exports.deepCopy = deepCopy;
3593
+ exports.dottedPathObjectToNested = dottedPathObjectToNested;
3594
+ exports.escapeRegExp = escapeRegExp;
3595
+ exports.extractEmails = extractEmails;
3596
+ exports.extractUrls = extractUrls;
3597
+ exports.filterByObject = filterByObject;
3598
+ exports.format = format;
3599
+ exports.getEnumValues = getEnumValues;
3600
+ exports.getFileExt = getFileExt;
3601
+ exports.getFileSize = getFileSize;
3602
+ exports.getMapKey = getMapKey;
3603
+ exports.getMapKeys = getMapKeys;
3604
+ exports.getValueByPath = getValueByPath;
3605
+ exports.groupBy = groupBy;
3606
+ exports.hasOwnAll = hasOwnAll;
3607
+ exports.hashString = hashString;
3608
+ exports.htmlToText = htmlToText;
3609
+ exports.isAlphanumeric = isAlphanumeric;
3610
+ exports.isArray = isArray;
3611
+ exports.isObject = isObject;
3612
+ exports.isObjectWith = isObjectWith;
3613
+ exports.isValidRedirectUrl = isValidRedirectUrl;
3614
+ exports.maskString = maskString;
3615
+ exports.mimeTypes = mimeTypes;
3616
+ exports.normalizeString = normalizeString;
3617
+ exports.objectToDottedPathValueObject = objectToDottedPathValueObject;
3618
+ exports.omit = omit;
3619
+ exports.padString = padString;
3620
+ exports.plural = plural;
3621
+ exports.prune = prune;
3622
+ exports.randomString = randomString;
3623
+ exports.removeWhitespace = removeWhitespace;
3624
+ exports.reverse = reverse;
3625
+ exports.searchMapValues = searchMapValues;
3626
+ exports.singular = singular;
3627
+ exports.slugify = slugify;
3628
+ exports.stringSimilarity = stringSimilarity;
3629
+ exports.toCamelCase = toCamelCase;
3630
+ exports.toFirstCase = toFirstCase;
3631
+ exports.toFirstCaseBreak = toFirstCaseBreak;
3632
+ exports.toKebabCase = toKebabCase;
3633
+ exports.toLowerCase = toLowerCase;
3634
+ exports.toLowerCaseBreak = toLowerCaseBreak;
3635
+ exports.toNumber = toNumber;
3636
+ exports.toPascalCase = toPascalCase;
3637
+ exports.toProperTitleCase = toProperTitleCase;
3638
+ exports.toSentenceCase = toSentenceCase;
3639
+ exports.toSnakeCase = toSnakeCase;
3640
+ exports.toTitleCase = toTitleCase;
3641
+ exports.toUpperCase = toUpperCase;
3642
+ exports.toUpperCaseBreak = toUpperCaseBreak;
3643
+ exports.toVariableName = toVariableName;
3644
+ exports.truncate = truncate;
3645
+ exports.wordWrap = wordWrap;