@gzl10/ts-helpers 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +320 -0
- package/README.md +233 -0
- package/USAGE-GUIDE.md +800 -0
- package/dist/browser/async.js +15 -0
- package/dist/browser/async.js.map +1 -0
- package/dist/browser/chunk-4O7ZPIJN.js +383 -0
- package/dist/browser/chunk-4O7ZPIJN.js.map +1 -0
- package/dist/browser/chunk-75XNTC34.js +60 -0
- package/dist/browser/chunk-75XNTC34.js.map +1 -0
- package/dist/browser/chunk-C3D7YZVE.js +299 -0
- package/dist/browser/chunk-C3D7YZVE.js.map +1 -0
- package/dist/browser/chunk-CZL6C2EI.js +452 -0
- package/dist/browser/chunk-CZL6C2EI.js.map +1 -0
- package/dist/browser/chunk-D4FZFIVA.js +240 -0
- package/dist/browser/chunk-D4FZFIVA.js.map +1 -0
- package/dist/browser/chunk-IL7NG7IC.js +72 -0
- package/dist/browser/chunk-IL7NG7IC.js.map +1 -0
- package/dist/browser/chunk-NSBPE2FW.js +17 -0
- package/dist/browser/chunk-NSBPE2FW.js.map +1 -0
- package/dist/browser/chunk-SLQVNPTH.js +27 -0
- package/dist/browser/chunk-SLQVNPTH.js.map +1 -0
- package/dist/browser/chunk-WG7ILCUB.js +195 -0
- package/dist/browser/chunk-WG7ILCUB.js.map +1 -0
- package/dist/browser/chunk-WJA4JDMZ.js +278 -0
- package/dist/browser/chunk-WJA4JDMZ.js.map +1 -0
- package/dist/browser/chunk-ZFVYLUTT.js +65 -0
- package/dist/browser/chunk-ZFVYLUTT.js.map +1 -0
- package/dist/browser/chunk-ZYTSVMTI.js +263 -0
- package/dist/browser/chunk-ZYTSVMTI.js.map +1 -0
- package/dist/browser/dates.js +78 -0
- package/dist/browser/dates.js.map +1 -0
- package/dist/browser/environment-detection.js +21 -0
- package/dist/browser/environment-detection.js.map +1 -0
- package/dist/browser/environment.js +34 -0
- package/dist/browser/environment.js.map +1 -0
- package/dist/browser/errors.js +18 -0
- package/dist/browser/errors.js.map +1 -0
- package/dist/browser/index.js +412 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/math.js +51 -0
- package/dist/browser/math.js.map +1 -0
- package/dist/browser/number.js +10 -0
- package/dist/browser/number.js.map +1 -0
- package/dist/browser/objects.js +31 -0
- package/dist/browser/objects.js.map +1 -0
- package/dist/browser/strings.js +80 -0
- package/dist/browser/strings.js.map +1 -0
- package/dist/browser/validation-core.js +54 -0
- package/dist/browser/validation-core.js.map +1 -0
- package/dist/browser/validation-crypto.js +28 -0
- package/dist/browser/validation-crypto.js.map +1 -0
- package/dist/browser/validators.js +98 -0
- package/dist/browser/validators.js.map +1 -0
- package/dist/cjs/async.js +86 -0
- package/dist/cjs/async.js.map +1 -0
- package/dist/cjs/dates.js +285 -0
- package/dist/cjs/dates.js.map +1 -0
- package/dist/cjs/environment-detection.js +84 -0
- package/dist/cjs/environment-detection.js.map +1 -0
- package/dist/cjs/environment.js +261 -0
- package/dist/cjs/environment.js.map +1 -0
- package/dist/cjs/errors.js +80 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.js +2035 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/math.js +388 -0
- package/dist/cjs/math.js.map +1 -0
- package/dist/cjs/number.js +37 -0
- package/dist/cjs/number.js.map +1 -0
- package/dist/cjs/objects.js +249 -0
- package/dist/cjs/objects.js.map +1 -0
- package/dist/cjs/strings.js +253 -0
- package/dist/cjs/strings.js.map +1 -0
- package/dist/cjs/validation.js +450 -0
- package/dist/cjs/validation.js.map +1 -0
- package/dist/esm/async.js +15 -0
- package/dist/esm/async.js.map +1 -0
- package/dist/esm/chunk-4O7ZPIJN.js +383 -0
- package/dist/esm/chunk-4O7ZPIJN.js.map +1 -0
- package/dist/esm/chunk-75XNTC34.js +60 -0
- package/dist/esm/chunk-75XNTC34.js.map +1 -0
- package/dist/esm/chunk-BDOBKBKA.js +72 -0
- package/dist/esm/chunk-BDOBKBKA.js.map +1 -0
- package/dist/esm/chunk-C3D7YZVE.js +299 -0
- package/dist/esm/chunk-C3D7YZVE.js.map +1 -0
- package/dist/esm/chunk-CZL6C2EI.js +452 -0
- package/dist/esm/chunk-CZL6C2EI.js.map +1 -0
- package/dist/esm/chunk-EBLSTOEC.js +263 -0
- package/dist/esm/chunk-EBLSTOEC.js.map +1 -0
- package/dist/esm/chunk-NSBPE2FW.js +17 -0
- package/dist/esm/chunk-NSBPE2FW.js.map +1 -0
- package/dist/esm/chunk-SLQVNPTH.js +27 -0
- package/dist/esm/chunk-SLQVNPTH.js.map +1 -0
- package/dist/esm/chunk-WG7ILCUB.js +195 -0
- package/dist/esm/chunk-WG7ILCUB.js.map +1 -0
- package/dist/esm/chunk-WJA4JDMZ.js +278 -0
- package/dist/esm/chunk-WJA4JDMZ.js.map +1 -0
- package/dist/esm/chunk-ZFVYLUTT.js +65 -0
- package/dist/esm/chunk-ZFVYLUTT.js.map +1 -0
- package/dist/esm/dates.js +78 -0
- package/dist/esm/dates.js.map +1 -0
- package/dist/esm/environment-detection.js +21 -0
- package/dist/esm/environment-detection.js.map +1 -0
- package/dist/esm/environment.js +34 -0
- package/dist/esm/environment.js.map +1 -0
- package/dist/esm/errors.js +18 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.js +380 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/math.js +51 -0
- package/dist/esm/math.js.map +1 -0
- package/dist/esm/number.js +10 -0
- package/dist/esm/number.js.map +1 -0
- package/dist/esm/objects.js +31 -0
- package/dist/esm/objects.js.map +1 -0
- package/dist/esm/strings.js +80 -0
- package/dist/esm/strings.js.map +1 -0
- package/dist/esm/validation.js +54 -0
- package/dist/esm/validation.js.map +1 -0
- package/dist/node/async.js +93 -0
- package/dist/node/async.js.map +1 -0
- package/dist/node/csv.js +102 -0
- package/dist/node/csv.js.map +1 -0
- package/dist/node/data.js +880 -0
- package/dist/node/data.js.map +1 -0
- package/dist/node/dates.js +324 -0
- package/dist/node/dates.js.map +1 -0
- package/dist/node/environment.js +278 -0
- package/dist/node/environment.js.map +1 -0
- package/dist/node/errors.js +89 -0
- package/dist/node/errors.js.map +1 -0
- package/dist/node/index.js +3151 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/json.js +107 -0
- package/dist/node/json.js.map +1 -0
- package/dist/node/math.js +413 -0
- package/dist/node/math.js.map +1 -0
- package/dist/node/number.js +42 -0
- package/dist/node/number.js.map +1 -0
- package/dist/node/objects.js +264 -0
- package/dist/node/objects.js.map +1 -0
- package/dist/node/strings.js +293 -0
- package/dist/node/strings.js.map +1 -0
- package/dist/node/tree.js +89 -0
- package/dist/node/tree.js.map +1 -0
- package/dist/node/validation-core.js +477 -0
- package/dist/node/validation-core.js.map +1 -0
- package/dist/node/validation-crypto.js +179 -0
- package/dist/node/validation-crypto.js.map +1 -0
- package/dist/node/validation.js +677 -0
- package/dist/node/validation.js.map +1 -0
- package/dist/node/validators.js +123 -0
- package/dist/node/validators.js.map +1 -0
- package/dist/node-esm/async.js +15 -0
- package/dist/node-esm/async.js.map +1 -0
- package/dist/node-esm/chunk-3YOF7NPT.js +299 -0
- package/dist/node-esm/chunk-3YOF7NPT.js.map +1 -0
- package/dist/node-esm/chunk-64TBXJQS.js +263 -0
- package/dist/node-esm/chunk-64TBXJQS.js.map +1 -0
- package/dist/node-esm/chunk-75XNTC34.js +60 -0
- package/dist/node-esm/chunk-75XNTC34.js.map +1 -0
- package/dist/node-esm/chunk-C4PKXIPB.js +278 -0
- package/dist/node-esm/chunk-C4PKXIPB.js.map +1 -0
- package/dist/node-esm/chunk-CMDFZME3.js +452 -0
- package/dist/node-esm/chunk-CMDFZME3.js.map +1 -0
- package/dist/node-esm/chunk-DZZPUYMP.js +74 -0
- package/dist/node-esm/chunk-DZZPUYMP.js.map +1 -0
- package/dist/node-esm/chunk-HTSEHRHI.js +195 -0
- package/dist/node-esm/chunk-HTSEHRHI.js.map +1 -0
- package/dist/node-esm/chunk-JCAUVOPH.js +27 -0
- package/dist/node-esm/chunk-JCAUVOPH.js.map +1 -0
- package/dist/node-esm/chunk-KBHE3K2F.js +505 -0
- package/dist/node-esm/chunk-KBHE3K2F.js.map +1 -0
- package/dist/node-esm/chunk-LYTET5NX.js +65 -0
- package/dist/node-esm/chunk-LYTET5NX.js.map +1 -0
- package/dist/node-esm/chunk-PZ5AY32C.js +10 -0
- package/dist/node-esm/chunk-PZ5AY32C.js.map +1 -0
- package/dist/node-esm/chunk-UKGXL2QO.js +383 -0
- package/dist/node-esm/chunk-UKGXL2QO.js.map +1 -0
- package/dist/node-esm/chunk-XAEYT23H.js +164 -0
- package/dist/node-esm/chunk-XAEYT23H.js.map +1 -0
- package/dist/node-esm/csv.js +63 -0
- package/dist/node-esm/csv.js.map +1 -0
- package/dist/node-esm/data.js +32 -0
- package/dist/node-esm/data.js.map +1 -0
- package/dist/node-esm/dates.js +78 -0
- package/dist/node-esm/dates.js.map +1 -0
- package/dist/node-esm/environment.js +34 -0
- package/dist/node-esm/environment.js.map +1 -0
- package/dist/node-esm/errors.js +18 -0
- package/dist/node-esm/errors.js.map +1 -0
- package/dist/node-esm/index.js +426 -0
- package/dist/node-esm/index.js.map +1 -0
- package/dist/node-esm/json.js +68 -0
- package/dist/node-esm/json.js.map +1 -0
- package/dist/node-esm/math.js +51 -0
- package/dist/node-esm/math.js.map +1 -0
- package/dist/node-esm/number.js +10 -0
- package/dist/node-esm/number.js.map +1 -0
- package/dist/node-esm/objects.js +31 -0
- package/dist/node-esm/objects.js.map +1 -0
- package/dist/node-esm/strings.js +80 -0
- package/dist/node-esm/strings.js.map +1 -0
- package/dist/node-esm/tree.js +8 -0
- package/dist/node-esm/tree.js.map +1 -0
- package/dist/node-esm/validation-core.js +54 -0
- package/dist/node-esm/validation-core.js.map +1 -0
- package/dist/node-esm/validation-crypto.js +26 -0
- package/dist/node-esm/validation-crypto.js.map +1 -0
- package/dist/node-esm/validation.js +606 -0
- package/dist/node-esm/validation.js.map +1 -0
- package/dist/node-esm/validators.js +98 -0
- package/dist/node-esm/validators.js.map +1 -0
- package/dist/types/async-C8gvbSG-.d.ts +453 -0
- package/dist/types/async.d.ts +1 -0
- package/dist/types/csv.d.ts +226 -0
- package/dist/types/data.d.ts +1561 -0
- package/dist/types/dates-hTiE0Z11.d.ts +298 -0
- package/dist/types/dates.d.ts +1 -0
- package/dist/types/environment-B8eLS7KT.d.ts +420 -0
- package/dist/types/environment-detection.d.ts +102 -0
- package/dist/types/environment.d.ts +1 -0
- package/dist/types/errors.d.ts +147 -0
- package/dist/types/index.d.ts +211 -0
- package/dist/types/json.d.ts +284 -0
- package/dist/types/math-BQ9Lwdp7.d.ts +2060 -0
- package/dist/types/math.d.ts +1 -0
- package/dist/types/number-CYnQfLWj.d.ts +44 -0
- package/dist/types/number.d.ts +1 -0
- package/dist/types/objects-BohS8GCS.d.ts +1185 -0
- package/dist/types/objects.d.ts +1 -0
- package/dist/types/strings-CiqRPYLL.d.ts +1349 -0
- package/dist/types/strings.d.ts +1 -0
- package/dist/types/tree.d.ts +284 -0
- package/dist/types/validation-core-DfHF8rCG.d.ts +238 -0
- package/dist/types/validation-crypto-browser.d.ts +56 -0
- package/dist/types/validation-crypto-node.d.ts +31 -0
- package/dist/types/validation.d.ts +1 -0
- package/dist/types/validators.d.ts +216 -0
- package/package.json +253 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/data.ts"],"sourcesContent":["/**\n * Data manipulation utilities for multiple formats with automatic detection\n * Consolidated from data/index module\n */\n\n/* eslint-disable max-lines-per-function */\n\nimport { DataError, TsHelpersErrorCode, createValidationError } from './errors'\nimport { isNode, isBrowser } from './environment'\n\n// =============================================================================\n// SHARED TYPES\n// =============================================================================\n\n/**\n * Type for data - array of objects or array of arrays (for CSV, etc.)\n */\nexport type ExportData = Record<string, any>[] | string[][]\n\n/**\n * Type for imported data - can include string for .tree files\n */\nexport type ImportData = ExportData | string | any\n\n/**\n * Type for CSV data (backward compatibility)\n */\nexport type CSVData = ExportData\n\n/**\n * Supported format types\n */\nexport type ExportFormat = 'csv' | 'json' | 'tree' | 'txt'\n\n/**\n * Universal file format detection - detects any file extension\n */\nexport type FileFormat = string\n\n// =============================================================================\n// ENVIRONMENT UTILITIES\n// =============================================================================\n\n/**\n * Reads a file as text in any environment (Node.js or Browser)\n *\n * Universal file reading utility that adapts to the runtime environment.\n * In Node.js, reads from filesystem. In Browser, reads from File object.\n *\n * Environment behavior:\n * - **Node.js**: Pass file path as string, reads from filesystem\n * - **Browser**: Pass File object (from input[type=\"file\"]), reads with FileReader API\n *\n * @param fileOrPath - File path string (Node.js) or File object (Browser)\n * @param encoding - Text encoding for Node.js filesystem read (default: 'utf8')\n * @returns Promise<string> File contents as text\n *\n * @example\n * ```typescript\n * // Node.js - Read from filesystem\n * const config = await readFileAsText('./config.json')\n * const data = JSON.parse(config)\n *\n * // Browser - Read from File input\n * // HTML: <input type=\"file\" id=\"fileInput\">\n * const input = document.getElementById('fileInput') as HTMLInputElement\n * input.addEventListener('change', async (e) => {\n * const file = e.target.files[0]\n * const content = await readFileAsText(file)\n * console.log('File content:', content)\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Load and parse CSV from user upload (Browser)\n * async function handleCSVUpload(file: File) {\n * const csvText = await readFileAsText(file)\n * const rows = csvText.split('\\n').map(row => row.split(';'))\n * console.log(`Loaded ${rows.length} rows`)\n * return rows\n * }\n * ```\n *\n * @throws {DataError} If called with invalid parameter for current environment\n * @see {@link importData} for format-aware file importing\n */\nexport const readFileAsText = async (\n fileOrPath: string | File,\n encoding: BufferEncoding = 'utf8'\n): Promise<string> => {\n if (isNode() && typeof fileOrPath === 'string') {\n // Node.js environment - read from filesystem\n const fs = await import('fs/promises')\n return fs.readFile(fileOrPath, encoding)\n } else if (isBrowser() && fileOrPath instanceof File) {\n // Browser environment - read File object\n return new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = e => resolve(e.target?.result as string)\n reader.onerror = () => reject(new Error('Error reading file'))\n reader.readAsText(fileOrPath)\n })\n } else {\n throw new DataError(\n 'Invalid parameter for current environment',\n TsHelpersErrorCode.ENVIRONMENT_NOT_SUPPORTED,\n {\n data: {\n isNodeEnv: isNode(),\n isBrowserEnv: isBrowser(),\n parameterType: typeof fileOrPath,\n },\n }\n )\n }\n}\n\n// =============================================================================\n// DATA VALIDATION\n// =============================================================================\n\n/**\n * Validates that data is exportable (array of objects or array of arrays)\n *\n * Ensures data structure is compatible with export formats (CSV, JSON).\n * Validates consistency: all elements must be same type, arrays must have equal length.\n *\n * Validation rules:\n * - Must be non-empty array\n * - If first element is object → all must be objects (no arrays/primitives)\n * - If first element is array → all must be arrays with equal length\n * - Null/undefined not allowed\n *\n * @param data - Data to validate for export\n * @returns `true` if data is valid (type guard for ExportData)\n *\n * @example\n * ```typescript\n * // Valid: Array of objects (typical use case)\n * const users = [\n * { id: 1, name: 'Alice', role: 'Admin' },\n * { id: 2, name: 'Bob', role: 'User' }\n * ]\n * validateExportData(users) // ✅ true\n * await exportData(users, 'users.csv')\n * ```\n *\n * @example\n * ```typescript\n * // Valid: Array of arrays (matrix/tabular data)\n * const matrix = [\n * ['Name', 'Age', 'City'],\n * ['Alice', 25, 'Madrid'],\n * ['Bob', 30, 'Barcelona']\n * ]\n * validateExportData(matrix) // ✅ true\n * await exportData(matrix, 'data.csv')\n * ```\n *\n * @example\n * ```typescript\n * // Invalid: Mixed types - throws ValidationError\n * const mixed = [\n * { id: 1, name: 'Alice' },\n * ['Bob', 30] // ❌ Array mixed with object\n * ]\n * try {\n * validateExportData(mixed)\n * } catch (error) {\n * console.error(error.message)\n * // 'All elements must be objects if first one is an object'\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Invalid: Inconsistent array lengths - throws ValidationError\n * const uneven = [\n * ['Alice', 25, 'Madrid'],\n * ['Bob', 30] // ❌ Missing city\n * ]\n * try {\n * validateExportData(uneven)\n * } catch (error) {\n * console.error(error.message)\n * // 'Row 1 has 2 columns, but first has 3'\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Pre-export validation with error handling\n * async function safeExportUsers(users: any[], filename: string) {\n * try {\n * // Validate before export\n * validateExportData(users)\n * await exportData(users, filename)\n * console.log(`✅ Exported ${users.length} users to ${filename}`)\n * } catch (error) {\n * console.error('Export failed:', error.message)\n * // Log validation errors for debugging\n * if (error.code === 'VALIDATION_ERROR') {\n * console.error('Invalid data structure:', error.details)\n * }\n * }\n * }\n * ```\n *\n * @throws {ValidationError} If data is not an array\n * @throws {ValidationError} If array is empty\n * @throws {ValidationError} If elements have inconsistent types\n * @throws {ValidationError} If array rows have different lengths\n *\n * @see {@link exportData} for universal export using validated data\n * @see {@link ExportData} for type definition\n */\nexport function validateExportData(data: any): data is ExportData {\n if (!Array.isArray(data)) {\n throw createValidationError('Data must be an array', 'data', typeof data)\n }\n\n if (data.length === 0) {\n throw createValidationError('Data array cannot be empty', 'data.length', data.length)\n }\n\n const firstItem = data[0]\n const isObjectArray =\n typeof firstItem === 'object' && firstItem !== null && !Array.isArray(firstItem)\n const isArrayOfArrays = Array.isArray(firstItem)\n\n if (!isObjectArray && !isArrayOfArrays) {\n throw createValidationError(\n 'Data must be an array of objects or an array of arrays',\n 'data[0]',\n typeof firstItem\n )\n }\n\n // Validate consistency according to first element type\n if (isObjectArray) {\n // If first element is object, all must be objects\n for (let i = 1; i < data.length; i++) {\n const item = data[i]\n if (typeof item !== 'object' || item === null || Array.isArray(item)) {\n throw createValidationError(\n 'All elements must be objects if first one is an object',\n `data[${i}]`,\n typeof item\n )\n }\n }\n } else if (isArrayOfArrays) {\n // If first element is array, all must be arrays with equal length\n const firstRowLength = firstItem.length\n for (let i = 1; i < data.length; i++) {\n if (!Array.isArray(data[i])) {\n throw createValidationError(`Row ${i} is not an array`, `data[${i}]`, typeof data[i])\n }\n if (data[i].length !== firstRowLength) {\n throw createValidationError(\n `Row ${i} has ${data[i].length} columns, but first has ${firstRowLength}`,\n `data[${i}].length`,\n { expected: firstRowLength, actual: data[i].length }\n )\n }\n }\n }\n\n return true\n}\n\n/**\n * Alias for validateExportData for CSV backward compatibility\n */\nexport const validateCSVData = validateExportData\n\n// =============================================================================\n// FORMAT DETECTION\n// =============================================================================\n\n/**\n * Detects file format based on filename extension\n *\n * Analyzes filename to determine export format for automatic format selection.\n * Supports CSV, JSON, Tree, and TXT formats. Case-insensitive.\n *\n * @param filename - Filename with extension\n * @returns Detected format ('csv' | 'json' | 'tree' | 'txt')\n *\n * @example\n * ```typescript\n * // CSV detection\n * detectFormatFromFilename('users.csv') // 'csv'\n * detectFormatFromFilename('DATA.CSV') // 'csv' (case-insensitive)\n * ```\n *\n * @example\n * ```typescript\n * // JSON detection\n * detectFormatFromFilename('config.json') // 'json'\n * detectFormatFromFilename('data.JSON') // 'json'\n * ```\n *\n * @example\n * ```typescript\n * // Tree structure detection\n * detectFormatFromFilename('structure.tree') // 'tree'\n * ```\n *\n * @example\n * ```typescript\n * // Plain text detection\n * detectFormatFromFilename('notes.txt') // 'txt'\n * detectFormatFromFilename('log.TXT') // 'txt'\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Dynamic export routing\n * async function smartExport(data: any[], filename: string) {\n * const format = detectFormatFromFilename(filename)\n * console.log(`Exporting as ${format.toUpperCase()}...`)\n *\n * await exportData(data, filename)\n * console.log(`✅ Export completed: ${filename}`)\n * }\n *\n * smartExport(users, 'users.csv') // Exports as CSV\n * smartExport(config, 'config.json') // Exports as JSON\n * ```\n *\n * @throws {DataError} If filename has no extension\n * @throws {DataError} If extension is not supported (.csv, .json, .tree, .txt)\n *\n * @see {@link detectFileExtension} for universal extension detection (any file type)\n * @see {@link detectUniversalFormat} for detailed format metadata\n * @see {@link exportData} for universal export using detected format\n *\n * @deprecated Use detectFileExtension for universal detection, or use exportData directly (auto-detects)\n */\nexport function detectFormatFromFilename(filename: string): ExportFormat {\n const parts = filename.toLowerCase().split('.')\n const extension = parts.length > 1 ? parts.pop() : undefined\n\n switch (extension) {\n case 'csv':\n return 'csv'\n case 'json':\n return 'json'\n case 'tree':\n return 'tree'\n case 'txt':\n return 'txt'\n default:\n if (!extension) {\n throw new DataError(\n 'Could not detect file format. Must have a valid extension (.csv, .json, .tree, .txt)',\n TsHelpersErrorCode.UNSUPPORTED_FORMAT,\n { data: { filename } }\n )\n }\n throw new DataError(\n `Unsupported file format: .${extension}. Use .csv, .json, .tree or .txt`,\n TsHelpersErrorCode.UNSUPPORTED_FORMAT,\n { data: { filename, extension } }\n )\n }\n}\n\n/**\n * Universal file extension detector - detects any file extension\n *\n * Extracts file extension from filename (without dot) for any file type.\n * Returns last extension for compound extensions (e.g., '.tar.gz' → 'gz').\n * Case-insensitive, trims whitespace, returns null if no extension found.\n *\n * @param filename - Filename with extension\n * @returns File extension (lowercase, without dot) or `null` if no extension\n *\n * @example\n * ```typescript\n * // Common file types\n * detectFileExtension('document.pdf') // 'pdf'\n * detectFileExtension('image.jpg') // 'jpg'\n * detectFileExtension('data.json') // 'json'\n * detectFileExtension('styles.css') // 'css'\n * ```\n *\n * @example\n * ```typescript\n * // Compound extensions (returns last extension)\n * detectFileExtension('archive.tar.gz') // 'gz'\n * detectFileExtension('backup.sql.bz2') // 'bz2'\n * detectFileExtension('file.test.ts') // 'ts'\n * ```\n *\n * @example\n * ```typescript\n * // Edge cases\n * detectFileExtension('noextension') // null\n * detectFileExtension('.hidden') // 'hidden' (Unix hidden files)\n * detectFileExtension('file.') // '' (empty string)\n * detectFileExtension(' file.txt ') // 'txt' (trims whitespace)\n * detectFileExtension('FILE.PDF') // 'pdf' (case-insensitive)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Conditional file processing\n * async function processUploadedFile(filename: string, data: any) {\n * const ext = detectFileExtension(filename)\n *\n * switch (ext) {\n * case 'csv':\n * return await importCSV(filename)\n * case 'json':\n * return await importJSON(filename)\n * case 'pdf':\n * return await extractPdfText(filename)\n * case null:\n * throw new Error('File must have an extension')\n * default:\n * throw new Error(`Unsupported file type: .${ext}`)\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: File type validation\n * function validateUpload(file: File) {\n * const ext = detectFileExtension(file.name)\n * const allowedExtensions = ['jpg', 'jpeg', 'png', 'gif']\n *\n * if (!ext) {\n * return { valid: false, error: 'File must have an extension' }\n * }\n *\n * if (!allowedExtensions.includes(ext)) {\n * return {\n * valid: false,\n * error: `Invalid file type .${ext}. Allowed: ${allowedExtensions.join(', ')}`\n * }\n * }\n *\n * return { valid: true }\n * }\n * ```\n *\n * @see {@link detectUniversalFormat} for detailed format metadata (category, MIME type)\n * @see {@link detectFormatFromFilename} for export format detection (csv/json/tree/txt only)\n */\nexport function detectFileExtension(filename: string): FileFormat | null {\n if (!filename || typeof filename !== 'string') {\n return null\n }\n\n const parts = filename.trim().split('.')\n\n if (parts.length < 2) {\n return null\n }\n\n const extension = parts.pop()?.toLowerCase()\n return extension || null\n}\n\n/**\n * Universal filename format detection with comprehensive extension support\n *\n * Analyzes filename extension and returns detailed format metadata including:\n * category, MIME type, text/binary classification. Supports 80+ file formats\n * across data, code, documents, images, audio, video, archives, and fonts.\n *\n * @param filename - Filename with extension\n * @returns Object with extension, category, isText, isBinary, mimeType properties\n *\n * @example\n * ```typescript\n * // Data formats\n * detectUniversalFormat('config.json')\n * // { extension: 'json', category: 'data', isText: true, isBinary: false, mimeType: 'application/json' }\n *\n * detectUniversalFormat('users.csv')\n * // { extension: 'csv', category: 'data', isText: true, isBinary: false, mimeType: 'text/csv' }\n * ```\n *\n * @example\n * ```typescript\n * // Programming languages\n * detectUniversalFormat('app.ts')\n * // { extension: 'ts', category: 'code', isText: true, isBinary: false, mimeType: 'application/typescript' }\n *\n * detectUniversalFormat('main.py')\n * // { extension: 'py', category: 'code', isText: true, isBinary: false, mimeType: 'text/x-python' }\n * ```\n *\n * @example\n * ```typescript\n * // Documents\n * detectUniversalFormat('report.pdf')\n * // { extension: 'pdf', category: 'document', isText: false, isBinary: true, mimeType: 'application/pdf' }\n *\n * detectUniversalFormat('doc.docx')\n * // { extension: 'docx', category: 'document', isText: false, isBinary: true, mimeType: '...' }\n * ```\n *\n * @example\n * ```typescript\n * // Images\n * detectUniversalFormat('photo.jpg')\n * // { extension: 'jpg', category: 'image', isText: false, isBinary: true, mimeType: 'image/jpeg' }\n *\n * detectUniversalFormat('icon.svg')\n * // { extension: 'svg', category: 'image', isText: true, isBinary: false, mimeType: 'image/svg+xml' }\n * ```\n *\n * @example\n * ```typescript\n * // Archives\n * detectUniversalFormat('backup.zip')\n * // { extension: 'zip', category: 'archive', isText: false, isBinary: true, mimeType: 'application/zip' }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Smart file processing routing\n * async function processFile(filename: string, content: any) {\n * const format = detectUniversalFormat(filename)\n *\n * console.log(`Processing ${format.extension} file (${format.category})`)\n *\n * if (format.isText) {\n * // Can read as text\n * const text = await readFileAsText(filename)\n * return parseTextFormat(text, format.extension)\n * } else if (format.isBinary) {\n * // Needs binary processing\n * return processBinaryFile(filename, format.mimeType)\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Content-Type header generation\n * function getResponseHeaders(filename: string) {\n * const format = detectUniversalFormat(filename)\n *\n * return {\n * 'Content-Type': format.mimeType || 'application/octet-stream',\n * 'Content-Disposition': `attachment; filename=\"${filename}\"`,\n * 'X-File-Category': format.category\n * }\n * }\n * ```\n *\n * @see {@link detectFileExtension} for simple extension extraction\n * @see {@link detectFormatFromFilename} for export format detection\n */\nexport function detectUniversalFormat(filename: string) {\n const extension = detectFileExtension(filename)\n\n if (!extension) {\n return {\n extension: null,\n category: 'unknown',\n isText: false,\n isBinary: false,\n mimeType: null,\n }\n }\n\n // Comprehensive file format database with 80+ common formats\n const formatInfo = {\n // Data formats\n json: { category: 'data', isText: true, isBinary: false, mimeType: 'application/json' },\n csv: { category: 'data', isText: true, isBinary: false, mimeType: 'text/csv' },\n xml: { category: 'data', isText: true, isBinary: false, mimeType: 'application/xml' },\n yaml: { category: 'data', isText: true, isBinary: false, mimeType: 'application/yaml' },\n yml: { category: 'data', isText: true, isBinary: false, mimeType: 'application/yaml' },\n tree: { category: 'data', isText: true, isBinary: false, mimeType: 'text/plain' },\n tsv: { category: 'data', isText: true, isBinary: false, mimeType: 'text/tab-separated-values' },\n sql: { category: 'data', isText: true, isBinary: false, mimeType: 'application/sql' },\n db: { category: 'data', isText: false, isBinary: true, mimeType: 'application/x-sqlite3' },\n sqlite: { category: 'data', isText: false, isBinary: true, mimeType: 'application/x-sqlite3' },\n\n // Text and code formats\n txt: { category: 'text', isText: true, isBinary: false, mimeType: 'text/plain' },\n md: { category: 'text', isText: true, isBinary: false, mimeType: 'text/markdown' },\n rst: { category: 'text', isText: true, isBinary: false, mimeType: 'text/x-rst' },\n rtf: { category: 'text', isText: true, isBinary: false, mimeType: 'application/rtf' },\n log: { category: 'text', isText: true, isBinary: false, mimeType: 'text/plain' },\n\n // Web formats\n html: { category: 'web', isText: true, isBinary: false, mimeType: 'text/html' },\n htm: { category: 'web', isText: true, isBinary: false, mimeType: 'text/html' },\n css: { category: 'web', isText: true, isBinary: false, mimeType: 'text/css' },\n scss: { category: 'web', isText: true, isBinary: false, mimeType: 'text/x-scss' },\n sass: { category: 'web', isText: true, isBinary: false, mimeType: 'text/x-sass' },\n less: { category: 'web', isText: true, isBinary: false, mimeType: 'text/x-less' },\n\n // Programming languages\n js: { category: 'code', isText: true, isBinary: false, mimeType: 'application/javascript' },\n mjs: { category: 'code', isText: true, isBinary: false, mimeType: 'application/javascript' },\n jsx: { category: 'code', isText: true, isBinary: false, mimeType: 'text/jsx' },\n ts: { category: 'code', isText: true, isBinary: false, mimeType: 'application/typescript' },\n tsx: { category: 'code', isText: true, isBinary: false, mimeType: 'text/tsx' },\n py: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-python' },\n java: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-java-source' },\n php: { category: 'code', isText: true, isBinary: false, mimeType: 'application/x-httpd-php' },\n rb: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-ruby' },\n go: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-go' },\n rs: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-rust' },\n c: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-c' },\n cpp: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-c++' },\n cc: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-c++' },\n h: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-c' },\n cs: { category: 'code', isText: true, isBinary: false, mimeType: 'text/x-csharp' },\n sh: { category: 'code', isText: true, isBinary: false, mimeType: 'application/x-sh' },\n bash: { category: 'code', isText: true, isBinary: false, mimeType: 'application/x-sh' },\n ps1: { category: 'code', isText: true, isBinary: false, mimeType: 'application/x-powershell' },\n\n // Microsoft Office documents\n doc: { category: 'document', isText: false, isBinary: true, mimeType: 'application/msword' },\n docx: {\n category: 'document',\n isText: false,\n isBinary: true,\n mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n },\n xls: {\n category: 'spreadsheet',\n isText: false,\n isBinary: true,\n mimeType: 'application/vnd.ms-excel',\n },\n xlsx: {\n category: 'spreadsheet',\n isText: false,\n isBinary: true,\n mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n },\n ppt: {\n category: 'presentation',\n isText: false,\n isBinary: true,\n mimeType: 'application/vnd.ms-powerpoint',\n },\n pptx: {\n category: 'presentation',\n isText: false,\n isBinary: true,\n mimeType: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n },\n\n // LibreOffice/OpenOffice\n odt: {\n category: 'document',\n isText: false,\n isBinary: true,\n mimeType: 'application/vnd.oasis.opendocument.text',\n },\n ods: {\n category: 'spreadsheet',\n isText: false,\n isBinary: true,\n mimeType: 'application/vnd.oasis.opendocument.spreadsheet',\n },\n odp: {\n category: 'presentation',\n isText: false,\n isBinary: true,\n mimeType: 'application/vnd.oasis.opendocument.presentation',\n },\n\n // Other document formats\n pdf: { category: 'document', isText: false, isBinary: true, mimeType: 'application/pdf' },\n epub: { category: 'document', isText: false, isBinary: true, mimeType: 'application/epub+zip' },\n mobi: {\n category: 'document',\n isText: false,\n isBinary: true,\n mimeType: 'application/x-mobipocket-ebook',\n },\n\n // Image formats\n jpg: { category: 'image', isText: false, isBinary: true, mimeType: 'image/jpeg' },\n jpeg: { category: 'image', isText: false, isBinary: true, mimeType: 'image/jpeg' },\n png: { category: 'image', isText: false, isBinary: true, mimeType: 'image/png' },\n gif: { category: 'image', isText: false, isBinary: true, mimeType: 'image/gif' },\n svg: { category: 'image', isText: true, isBinary: false, mimeType: 'image/svg+xml' },\n webp: { category: 'image', isText: false, isBinary: true, mimeType: 'image/webp' },\n avif: { category: 'image', isText: false, isBinary: true, mimeType: 'image/avif' },\n bmp: { category: 'image', isText: false, isBinary: true, mimeType: 'image/bmp' },\n tiff: { category: 'image', isText: false, isBinary: true, mimeType: 'image/tiff' },\n tif: { category: 'image', isText: false, isBinary: true, mimeType: 'image/tiff' },\n ico: { category: 'image', isText: false, isBinary: true, mimeType: 'image/x-icon' },\n psd: {\n category: 'image',\n isText: false,\n isBinary: true,\n mimeType: 'image/vnd.adobe.photoshop',\n },\n\n // Audio formats\n mp3: { category: 'audio', isText: false, isBinary: true, mimeType: 'audio/mpeg' },\n wav: { category: 'audio', isText: false, isBinary: true, mimeType: 'audio/wav' },\n flac: { category: 'audio', isText: false, isBinary: true, mimeType: 'audio/flac' },\n ogg: { category: 'audio', isText: false, isBinary: true, mimeType: 'audio/ogg' },\n aac: { category: 'audio', isText: false, isBinary: true, mimeType: 'audio/aac' },\n m4a: { category: 'audio', isText: false, isBinary: true, mimeType: 'audio/m4a' },\n wma: { category: 'audio', isText: false, isBinary: true, mimeType: 'audio/x-ms-wma' },\n\n // Video formats\n mp4: { category: 'video', isText: false, isBinary: true, mimeType: 'video/mp4' },\n avi: { category: 'video', isText: false, isBinary: true, mimeType: 'video/x-msvideo' },\n mov: { category: 'video', isText: false, isBinary: true, mimeType: 'video/quicktime' },\n wmv: { category: 'video', isText: false, isBinary: true, mimeType: 'video/x-ms-wmv' },\n flv: { category: 'video', isText: false, isBinary: true, mimeType: 'video/x-flv' },\n webm: { category: 'video', isText: false, isBinary: true, mimeType: 'video/webm' },\n mkv: { category: 'video', isText: false, isBinary: true, mimeType: 'video/x-matroska' },\n '3gp': { category: 'video', isText: false, isBinary: true, mimeType: 'video/3gpp' },\n\n // Archive formats\n zip: { category: 'archive', isText: false, isBinary: true, mimeType: 'application/zip' },\n rar: {\n category: 'archive',\n isText: false,\n isBinary: true,\n mimeType: 'application/x-rar-compressed',\n },\n '7z': {\n category: 'archive',\n isText: false,\n isBinary: true,\n mimeType: 'application/x-7z-compressed',\n },\n tar: { category: 'archive', isText: false, isBinary: true, mimeType: 'application/x-tar' },\n gz: { category: 'archive', isText: false, isBinary: true, mimeType: 'application/gzip' },\n bz2: { category: 'archive', isText: false, isBinary: true, mimeType: 'application/x-bzip2' },\n xz: { category: 'archive', isText: false, isBinary: true, mimeType: 'application/x-xz' },\n\n // Configuration files\n ini: { category: 'config', isText: true, isBinary: false, mimeType: 'text/plain' },\n cfg: { category: 'config', isText: true, isBinary: false, mimeType: 'text/plain' },\n conf: { category: 'config', isText: true, isBinary: false, mimeType: 'text/plain' },\n toml: { category: 'config', isText: true, isBinary: false, mimeType: 'application/toml' },\n env: { category: 'config', isText: true, isBinary: false, mimeType: 'text/plain' },\n\n // Font formats\n ttf: { category: 'font', isText: false, isBinary: true, mimeType: 'font/ttf' },\n otf: { category: 'font', isText: false, isBinary: true, mimeType: 'font/otf' },\n woff: { category: 'font', isText: false, isBinary: true, mimeType: 'font/woff' },\n woff2: { category: 'font', isText: false, isBinary: true, mimeType: 'font/woff2' },\n eot: {\n category: 'font',\n isText: false,\n isBinary: true,\n mimeType: 'application/vnd.ms-fontobject',\n },\n } as const\n\n const info = formatInfo[extension as keyof typeof formatInfo] || {\n category: 'unknown',\n isText: false,\n isBinary: false,\n mimeType: null,\n }\n\n return {\n extension,\n ...info,\n }\n}\n\n// =============================================================================\n// TREE EXPORT OPTIONS\n// =============================================================================\n\nexport interface TreeExportOptions {\n /** Property to display as node name (default: 'name') */\n labelField?: string\n /** Characters for vertical line (default: '│ ') */\n verticalLine?: string\n /** Characters for middle branch (default: '├── ') */\n middleBranch?: string\n /** Characters for last branch (default: '└── ') */\n lastBranch?: string\n /** Spacing for nodes without vertical line (default: ' ') */\n emptySpace?: string\n /** Custom function to get node label */\n labelFunction?: (node: any) => string\n}\n\n/**\n * Exports a tree structure as a text file with visual format\n *\n * Converts hierarchical data (nodes with `children` arrays) into ASCII/Unicode tree\n * visualization and saves as .tree file. Supports Node.js (filesystem) and Browser (download).\n *\n * @param data - Array of root nodes (each with optional `children` property)\n * @param filePath - Output file path (Node.js) or filename (Browser)\n * @param options - Tree rendering options (labelField, box-drawing characters, labelFunction)\n *\n * @example\n * ```typescript\n * // Basic tree export - File structure\n * const fileTree = [\n * {\n * name: 'src',\n * children: [\n * { name: 'index.ts' },\n * { name: 'utils.ts' },\n * {\n * name: 'components',\n * children: [\n * { name: 'Button.tsx' },\n * { name: 'Input.tsx' }\n * ]\n * }\n * ]\n * }\n * ]\n *\n * await exportTree(fileTree, 'structure.tree')\n * // Creates file:\n * // └── src\n * // ├── index.ts\n * // ├── utils.ts\n * // └── components\n * // ├── Button.tsx\n * // └── Input.tsx\n * ```\n *\n * @example\n * ```typescript\n * // Custom label field - Organization chart\n * const orgChart = [\n * {\n * title: 'CEO',\n * children: [\n * {\n * title: 'CTO',\n * children: [\n * { title: 'Dev Lead' },\n * { title: 'QA Lead' }\n * ]\n * },\n * { title: 'CFO' }\n * ]\n * }\n * ]\n *\n * await exportTree(orgChart, 'org-chart.tree', { labelField: 'title' })\n * ```\n *\n * @example\n * ```typescript\n * // ASCII characters - Better terminal compatibility\n * await exportTree(data, 'structure.tree', {\n * verticalLine: '| ',\n * middleBranch: '+-- ',\n * lastBranch: '`-- ',\n * emptySpace: ' '\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Custom label function - Rich formatting\n * const tasks = [\n * {\n * name: 'Backend',\n * status: 'in-progress',\n * assignee: 'Alice',\n * children: [\n * { name: 'API', status: 'done', assignee: 'Bob' },\n * { name: 'Database', status: 'pending', assignee: 'Charlie' }\n * ]\n * }\n * ]\n *\n * await exportTree(tasks, 'tasks.tree', {\n * labelFunction: (node) => `[${node.status}] ${node.name} (@${node.assignee})`\n * })\n * // Output:\n * // └── [in-progress] Backend (@Alice)\n * // ├── [done] API (@Bob)\n * // └── [pending] Database (@Charlie)\n * ```\n *\n * @throws {ValidationError} If data is not an array of nodes\n *\n * @see {@link importTree} for importing .tree files as text\n * @see {@link renderTreeAsText} for tree rendering without file export\n * @see {@link TreeExportOptions} for configuration options\n */\nexport async function exportTree(\n data: any[],\n filePath: string,\n options?: TreeExportOptions\n): Promise<void> {\n if (!Array.isArray(data)) {\n throw createValidationError(\n 'Data for tree format must be an array of nodes',\n 'data',\n typeof data\n )\n }\n\n // Import render function from specialized/tree\n const { renderTreeAsText } = await import('./tree')\n\n // Render tree as text\n const treeText = renderTreeAsText(data, options)\n\n // Write file according to environment\n if (isNode()) {\n // Node.js - write file to system\n const fs = await import('fs/promises')\n await fs.writeFile(filePath, treeText, { encoding: 'utf-8' })\n } else {\n // Browser - download as file\n const blob = new Blob([treeText], { type: 'text/plain;charset=utf-8' })\n const url = URL.createObjectURL(blob)\n\n const link = document.createElement('a')\n link.href = url\n link.download = filePath.split('/').pop() || 'tree.tree'\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n\n URL.revokeObjectURL(url)\n }\n}\n\n/**\n * Imports a .tree file as plain text (Node.js only)\n *\n * Reads tree structure visualization files created with exportTree() as plain text.\n * Returns the ASCII/Unicode tree diagram as string. Only supported in Node.js environment.\n *\n * @param filePath - Path to .tree file (Node.js only)\n * @param _options - Reserved for future use\n * @returns Promise<string> Tree structure as text\n *\n * @example\n * ```typescript\n * // Import tree structure\n * const treeText = await importTree('./structure.tree')\n * console.log(treeText)\n * // Output:\n * // └── src\n * // ├── index.ts\n * // ├── utils.ts\n * // └── components\n * // ├── Button.tsx\n * // └── Input.tsx\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Display tree structure in terminal\n * async function showProjectStructure(filePath: string) {\n * try {\n * const structure = await importTree(filePath)\n * console.log('📁 Project Structure:')\n * console.log(structure)\n * } catch (error) {\n * console.error('Failed to load structure:', error.message)\n * }\n * }\n * ```\n *\n * @throws {DataError} If called in Browser environment (use readFileAsText with File object instead)\n *\n * @see {@link exportTree} for creating .tree files\n * @see {@link readFileAsText} for browser-compatible file reading\n */\nexport async function importTree(filePath: string, _options?: any): Promise<string> {\n if (isNode()) {\n // Node.js - read file from system\n const fs = await import('fs/promises')\n return fs.readFile(filePath, { encoding: 'utf-8' })\n } else {\n // Browser - cannot read files from system directly\n throw new DataError(\n '.tree file import is not supported in browser. Use readFileAsText() with a File object.',\n TsHelpersErrorCode.ENVIRONMENT_NOT_SUPPORTED,\n { data: { environment: 'browser', operation: 'importTree' } }\n )\n }\n}\n\n// =============================================================================\n// TEXT EXPORT OPTIONS\n// =============================================================================\n\nexport interface TxtExportOptions {\n /** Separator between array elements (default: '\\n') */\n separator?: string\n /** Custom function to convert objects to string */\n stringify?: (obj: any) => string\n /** Indentation for JSON objects (default: 2) */\n indent?: number\n}\n\n/**\n * Exports any type of data as plain text file\n *\n * Universal text file exporter supporting strings, arrays, objects, and primitives.\n * Automatically formats data based on type. Supports Node.js (filesystem) and Browser (download).\n *\n * Features:\n * - **Strings**: Direct output\n * - **Arrays**: One element per line (or custom separator)\n * - **Objects**: Formatted JSON with indentation\n * - **Primitives**: String conversion\n * - **Custom stringify**: Optional transform function\n *\n * @param data - Data to export (string, array, object, or primitive)\n * @param filePath - Output file path (Node.js) or filename (Browser)\n * @param options - Export options (separator, stringify function, indent)\n *\n * @example\n * ```typescript\n * // Export string - Direct output\n * await exportTxt('Hello, World!', 'message.txt')\n * // Creates: Hello, World!\n * ```\n *\n * @example\n * ```typescript\n * // Export array - One per line\n * const logs = [\n * '[INFO] Server started',\n * '[WARN] High memory usage',\n * '[ERROR] Connection failed'\n * ]\n * await exportTxt(logs, 'server.log')\n * // Creates:\n * // [INFO] Server started\n * // [WARN] High memory usage\n * // [ERROR] Connection failed\n * ```\n *\n * @example\n * ```typescript\n * // Export object - Formatted JSON\n * const config = {\n * server: { host: 'localhost', port: 3000 },\n * database: { url: 'mongodb://localhost' }\n * }\n * await exportTxt(config, 'config.txt', { indent: 2 })\n * // Creates formatted JSON\n * ```\n *\n * @example\n * ```typescript\n * // Custom separator - Comma-separated list\n * const tags = ['typescript', 'nodejs', 'express', 'mongodb']\n * await exportTxt(tags, 'tags.txt', { separator: ', ' })\n * // Creates: typescript, nodejs, express, mongodb\n * ```\n *\n * @example\n * ```typescript\n * // Custom stringify function - Markdown list\n * const tasks = [\n * { id: 1, title: 'Setup project', done: true },\n * { id: 2, title: 'Write tests', done: false }\n * ]\n * await exportTxt(tasks, 'tasks.txt', {\n * stringify: (tasks) =>\n * tasks.map(t => `- [${t.done ? 'x' : ' '}] ${t.title}`).join('\\n')\n * })\n * // Creates:\n * // - [x] Setup project\n * // - [ ] Write tests\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Export error log with timestamps\n * async function exportErrorLog(errors: Error[]) {\n * const timestamp = new Date().toISOString()\n * const filename = `errors-${timestamp}.txt`\n *\n * await exportTxt(errors, filename, {\n * stringify: (errors) =>\n * errors.map(e => `[${new Date().toISOString()}] ${e.message}\\n${e.stack}`).join('\\n\\n')\n * })\n *\n * console.log(`✅ Exported ${errors.length} errors to ${filename}`)\n * }\n * ```\n *\n * @see {@link importTxt} for importing text files with security validations\n * @see {@link TxtExportOptions} for configuration options\n */\nexport async function exportTxt(\n data: any,\n filePath: string,\n options?: TxtExportOptions\n): Promise<void> {\n const { separator = '\\n', stringify, indent = 2 } = options || {}\n\n let textContent: string\n\n if (stringify) {\n // Use custom function\n textContent = stringify(data)\n } else if (typeof data === 'string') {\n // Already string\n textContent = data\n } else if (Array.isArray(data)) {\n // Array: each element on a line (or custom separator)\n textContent = data\n .map(item => (typeof item === 'string' ? item : JSON.stringify(item, null, indent)))\n .join(separator)\n } else if (typeof data === 'object' && data !== null) {\n // Object: formatted JSON\n textContent = JSON.stringify(data, null, indent)\n } else {\n // Primitives: convert to string\n textContent = String(data)\n }\n\n // Write file according to environment\n if (isNode()) {\n // Node.js - write file to system\n const fs = await import('fs/promises')\n await fs.writeFile(filePath, textContent, { encoding: 'utf-8' })\n } else {\n // Browser - download as file\n const blob = new Blob([textContent], { type: 'text/plain;charset=utf-8' })\n const url = URL.createObjectURL(blob)\n\n const link = document.createElement('a')\n link.href = url\n link.download = filePath.split('/').pop() || 'file.txt'\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n\n URL.revokeObjectURL(url)\n }\n}\n\nexport interface TxtImportOptions {\n /** Maximum file size in bytes (default: 10MB) */\n maxFileSize?: number\n /** Maximum content length in characters (default: 1M characters) */\n maxLength?: number\n /** Whether to validate content for security (default: true) */\n validateSecurity?: boolean\n /** Whether to sanitize content (default: true) */\n sanitize?: boolean\n}\n\n/**\n * Sanitizes text content removing problematic characters\n */\nfunction sanitizeTextContent(content: string): string {\n return (\n content\n // Normalize different types of line breaks\n .replace(/\\r\\n|\\r/g, '\\n')\n // Remove dangerous control characters (keep \\n, \\t)\n // eslint-disable-next-line no-control-regex\n .replace(/[\\x00-\\x08\\x0E-\\x1F\\x7F]/g, '')\n // Limit multiple consecutive line breaks\n .replace(/\\n{4,}/g, '\\n\\n\\n')\n // Remove trailing spaces from lines\n .replace(/[ \\t]+$/gm, '')\n )\n}\n\n/**\n * Imports a .txt file as plain text with security validations (Node.js only)\n *\n * Reads text files with comprehensive security checks: file size limits, content length validation,\n * path traversal prevention, and dangerous character sanitization. Only supported in Node.js.\n *\n * Security features:\n * - **File size limit**: Default 10MB (configurable)\n * - **Content length limit**: Default 1M characters (configurable)\n * - **Path validation**: Prevents directory traversal attacks\n * - **Content sanitization**: Removes dangerous control characters\n * - **Line break normalization**: Standardizes to \\n\n *\n * @param filePath - Path to .txt file (Node.js only)\n * @param options - Security and sanitization options\n *\n * @example\n * ```typescript\n * // Basic import - Default security settings\n * const content = await importTxt('./notes.txt')\n * console.log(content)\n * ```\n *\n * @example\n * ```typescript\n * // Custom size limits - Large file support\n * const largeFile = await importTxt('./large-log.txt', {\n * maxFileSize: 50 * 1024 * 1024, // 50MB\n * maxLength: 10_000_000 // 10M characters\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Disable sanitization - Raw content\n * const rawContent = await importTxt('./data.txt', {\n * sanitize: false,\n * validateSecurity: false\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Safe log file reader with error handling\n * async function readServerLog(logPath: string) {\n * try {\n * const log = await importTxt(logPath, {\n * maxFileSize: 100 * 1024 * 1024, // 100MB logs\n * maxLength: 50_000_000, // 50M chars\n * sanitize: true,\n * validateSecurity: true\n * })\n *\n * const lines = log.split('\\n')\n * const errors = lines.filter(line => line.includes('[ERROR]'))\n *\n * console.log(`📊 Log stats:`)\n * console.log(` Total lines: ${lines.length}`)\n * console.log(` Errors: ${errors.length}`)\n *\n * return { lines, errors }\n * } catch (error) {\n * if (error.message.includes('too large')) {\n * console.error('Log file exceeds size limit')\n * } else if (error.message.includes('not found')) {\n * console.error('Log file does not exist')\n * } else {\n * console.error('Failed to read log:', error.message)\n * }\n * throw error\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Secure user file upload processing\n * async function processUserUpload(uploadedFilePath: string) {\n * // Enforce strict limits for user-uploaded files\n * const content = await importTxt(uploadedFilePath, {\n * maxFileSize: 5 * 1024 * 1024, // 5MB max\n * maxLength: 1_000_000, // 1M chars max\n * validateSecurity: true, // Check for dangerous content\n * sanitize: true // Remove control chars\n * })\n *\n * // Process sanitized content safely\n * return analyzeText(content)\n * }\n * ```\n *\n * @throws {Error} If file exceeds maxFileSize\n * @throws {Error} If content exceeds maxLength\n * @throws {Error} If file path is invalid or unsafe\n * @throws {Error} If file not found\n * @throws {Error} If called in Browser environment\n *\n * @see {@link exportTxt} for creating text files\n * @see {@link TxtImportOptions} for configuration options\n * @see {@link readFileAsText} for browser-compatible file reading\n */\nexport async function importTxt(filePath: string, options: TxtImportOptions = {}): Promise<string> {\n const {\n maxFileSize = 10 * 1024 * 1024, // 10MB default\n maxLength = 1_000_000, // 1M characters default\n validateSecurity = true,\n sanitize = true,\n } = options\n\n if (isNode()) {\n // Node.js - read file from system with validations\n const fs = await import('fs/promises')\n const path = await import('path')\n\n // Validate file path for security using our validator\n const { isValidFilePath, isValidFileSize, isValidTextContent } = await import('./validators')\n\n if (!isValidFilePath(filePath)) {\n throw new Error('Invalid or unsafe file path')\n }\n\n const resolvedPath = path.resolve(filePath)\n\n // Check file size before reading\n try {\n const stats = await fs.stat(resolvedPath)\n\n if (!isValidFileSize(stats.size, maxFileSize)) {\n throw new Error(`File too large: ${stats.size} bytes (max: ${maxFileSize})`)\n }\n } catch (error) {\n if ((error as any).code === 'ENOENT') {\n throw new Error(`File not found: ${filePath}`)\n }\n throw error\n }\n\n // Read and validate content\n const content = await fs.readFile(resolvedPath, { encoding: 'utf-8' })\n\n // Validate content security using our validator\n if (validateSecurity && !isValidTextContent(content, { maxLength })) {\n throw new Error('File contains potentially dangerous content or exceeds security limits')\n }\n\n return sanitize ? sanitizeTextContent(content) : content\n } else {\n // Browser - cannot read files from system directly\n throw new Error(\n '.txt file import is not supported in browser. Use readFileAsText() with a File object.'\n )\n }\n}\n\n// =============================================================================\n// UNIVERSAL FUNCTIONS\n// =============================================================================\n\n/**\n * Exports data in specified format based on file extension (Universal dispatcher)\n *\n * Universal export function that automatically detects format from filename extension\n * and delegates to specialized exporters (CSV, JSON, Tree, TXT). Simplifies data export\n * with a single unified API for all formats.\n *\n * Automatic format detection:\n * - **.csv** → exportCSV (PapaParse, UTF-8 BOM, semicolon delimiter)\n * - **.json** → exportJSON (pretty-printed JSON)\n * - **.tree** → exportTree (ASCII/Unicode tree visualization)\n * - **.txt** → exportTxt (plain text with auto-formatting)\n *\n * Environment support:\n * - **Node.js**: Writes to filesystem\n * - **Browser**: Triggers download\n *\n * @param data - Data to export (structure depends on format)\n * @param filePath - Output file path with extension (determines format)\n * @param options - Format-specific options (passed to specialized exporter)\n *\n * @example\n * ```typescript\n * // CSV export - Array of objects\n * const users = [\n * { id: 1, name: 'Alice', email: 'alice@example.com' },\n * { id: 2, name: 'Bob', email: 'bob@example.com' }\n * ]\n * await exportData(users, 'users.csv')\n * // Auto-detects CSV format, exports with semicolon delimiter\n * ```\n *\n * @example\n * ```typescript\n * // JSON export - Any data structure\n * const config = {\n * server: { host: 'localhost', port: 3000 },\n * database: { url: 'mongodb://localhost' },\n * features: { auth: true, cache: false }\n * }\n * await exportData(config, 'config.json', { indent: 2 })\n * // Auto-detects JSON format, pretty-prints with 2-space indent\n * ```\n *\n * @example\n * ```typescript\n * // Tree export - Hierarchical structure\n * const fileTree = [\n * {\n * name: 'src',\n * children: [\n * { name: 'index.ts' },\n * { name: 'utils.ts' },\n * {\n * name: 'components',\n * children: [\n * { name: 'Button.tsx' },\n * { name: 'Input.tsx' }\n * ]\n * }\n * ]\n * }\n * ]\n * await exportData(fileTree, 'structure.tree')\n * // Auto-detects tree format, renders as ASCII tree\n * ```\n *\n * @example\n * ```typescript\n * // Text export - Logs/strings\n * const logs = [\n * '[2024-01-15 10:30:00] Server started',\n * '[2024-01-15 10:30:15] Connected to database',\n * '[2024-01-15 10:31:00] Ready to accept connections'\n * ]\n * await exportData(logs, 'server.log')\n * // Auto-detects txt format, one line per array element\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Multi-format export function\n * async function exportReport(data: any[], format: 'csv' | 'json' | 'txt') {\n * const timestamp = new Date().toISOString().split('T')[0]\n * const filename = `report-${timestamp}.${format}`\n *\n * try {\n * await exportData(data, filename)\n * console.log(`✅ Report exported: ${filename}`)\n * return { success: true, filename }\n * } catch (error) {\n * console.error(`❌ Export failed:`, error.message)\n * return { success: false, error: error.message }\n * }\n * }\n *\n * // Usage\n * exportReport(users, 'csv') // users-2024-01-15.csv\n * exportReport(stats, 'json') // report-2024-01-15.json\n * exportReport(logs, 'txt') // report-2024-01-15.txt\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: User-selected format export\n * async function exportWithUserChoice(data: any[], filename: string) {\n * // User provides filename with desired extension\n * // exportData automatically routes to correct exporter\n *\n * await exportData(data, filename)\n *\n * const format = detectFormatFromFilename(filename)\n * console.log(`Exported as ${format.toUpperCase()}`)\n * }\n *\n * // Works with any supported extension\n * exportWithUserChoice(data, 'data.csv')\n * exportWithUserChoice(data, 'data.json')\n * exportWithUserChoice(data, 'data.txt')\n * ```\n *\n * @throws {DataError} If file extension is not supported\n * @throws {ValidationError} If data structure is invalid for format (e.g., CSV requires array)\n *\n * @see {@link importData} for universal import\n * @see {@link exportCSV} for CSV-specific export\n * @see {@link exportJSON} for JSON-specific export\n * @see {@link exportTree} for tree-specific export\n * @see {@link exportTxt} for text-specific export\n */\nexport async function exportData(data: any, filePath: string, options?: any): Promise<void> {\n const format = detectFormatFromFilename(filePath)\n\n // Only validate as ExportData for formats that require it (CSV)\n if (format === 'csv') {\n validateExportData(data)\n }\n\n switch (format) {\n case 'csv': {\n const { exportCSV } = await import('./csv')\n return exportCSV(data, filePath, options)\n }\n case 'json': {\n const { exportJSON } = await import('./json')\n return exportJSON(data, filePath, options)\n }\n case 'tree': {\n return exportTree(data, filePath, options)\n }\n case 'txt': {\n return exportTxt(data, filePath, options)\n }\n default:\n throw new Error(`Unsupported format: ${format}`)\n }\n}\n\n/**\n * Imports data from specified file based on extension (Universal dispatcher)\n *\n * Universal import function that automatically detects format from filename extension\n * and delegates to specialized importers (CSV, JSON, Tree, TXT). Simplifies data import\n * with a single unified API for all formats. Node.js only.\n *\n * Automatic format detection:\n * - **.csv** → importCSV (PapaParse with header detection)\n * - **.json** → importJSON (native JSON.parse)\n * - **.tree** → importTree (plain text tree visualization)\n * - **.txt** → importTxt (plain text with security validations)\n *\n * @param filePath - Input file path with extension (determines format)\n * @param options - Format-specific options (passed to specialized importer)\n * @returns Promise<ImportData> Imported data (type depends on format)\n *\n * @example\n * ```typescript\n * // CSV import - Returns array of objects\n * const users = await importData('./users.csv')\n * // [\n * // { id: '1', name: 'Alice', email: 'alice@example.com' },\n * // { id: '2', name: 'Bob', email: 'bob@example.com' }\n * // ]\n * ```\n *\n * @example\n * ```typescript\n * // JSON import - Returns original data structure\n * const config = await importData('./config.json')\n * console.log(config.server.host) // 'localhost'\n * console.log(config.server.port) // 3000\n * ```\n *\n * @example\n * ```typescript\n * // Tree import - Returns plain text visualization\n * const treeText = await importData('./structure.tree')\n * console.log(treeText)\n * // └── src\n * // ├── index.ts\n * // ├── utils.ts\n * // └── components\n * ```\n *\n * @example\n * ```typescript\n * // Text import - Returns file content as string\n * const log = await importData('./server.log')\n * const lines = log.split('\\n')\n * const errors = lines.filter(line => line.includes('[ERROR]'))\n * console.log(`Found ${errors.length} errors`)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Dynamic file processor\n * async function processFile(filePath: string) {\n * const ext = detectFileExtension(filePath)\n * console.log(`Processing ${ext} file...`)\n *\n * const data = await importData(filePath)\n *\n * switch (ext) {\n * case 'csv':\n * return processCSVData(data as any[])\n * case 'json':\n * return processJSONData(data)\n * case 'txt':\n * return processTextData(data as string)\n * default:\n * throw new Error(`Unsupported format: ${ext}`)\n * }\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Batch file import with error handling\n * async function importAllFiles(directory: string) {\n * const files = await fs.readdir(directory)\n * const results = []\n *\n * for (const file of files) {\n * const filePath = path.join(directory, file)\n * const ext = detectFileExtension(file)\n *\n * // Skip unsupported formats\n * if (!['csv', 'json', 'txt', 'tree'].includes(ext || '')) {\n * console.log(`⏭️ Skipping unsupported file: ${file}`)\n * continue\n * }\n *\n * try {\n * const data = await importData(filePath)\n * results.push({ file, data, success: true })\n * console.log(`✅ Imported: ${file}`)\n * } catch (error) {\n * results.push({ file, error: error.message, success: false })\n * console.error(`❌ Failed: ${file} - ${error.message}`)\n * }\n * }\n *\n * return results\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Import with custom options per format\n * async function smartImport(filePath: string) {\n * const format = detectFormatFromFilename(filePath)\n *\n * let options: any = {}\n *\n * if (format === 'csv') {\n * options = { delimiter: ';', header: true }\n * } else if (format === 'json') {\n * options = { reviver: (key, value) => key === 'date' ? new Date(value) : value }\n * } else if (format === 'txt') {\n * options = { maxFileSize: 50 * 1024 * 1024, sanitize: true }\n * }\n *\n * return await importData(filePath, options)\n * }\n * ```\n *\n * @throws {DataError} If file extension is not supported\n * @throws {Error} If file not found or cannot be read\n * @throws {Error} If file format is invalid\n * @throws {DataError} If called in Browser environment (use readFileAsText with File object instead)\n *\n * @see {@link exportData} for universal export\n * @see {@link importCSV} for CSV-specific import\n * @see {@link importJSON} for JSON-specific import\n * @see {@link importTree} for tree-specific import\n * @see {@link importTxt} for text-specific import\n */\nexport async function importData(filePath: string, options?: any): Promise<ImportData> {\n const format = detectFormatFromFilename(filePath)\n\n switch (format) {\n case 'csv': {\n const { importCSV } = await import('./csv')\n return importCSV(filePath, options)\n }\n case 'json': {\n const { importJSON } = await import('./json')\n return importJSON(filePath, options)\n }\n case 'tree': {\n return importTree(filePath, options)\n }\n case 'txt': {\n return importTxt(filePath, options)\n }\n default:\n throw new Error(`Unsupported format: ${format}`)\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TsHelpersError
|
|
3
|
+
} from "./chunk-75XNTC34.js";
|
|
4
|
+
import {
|
|
5
|
+
__export
|
|
6
|
+
} from "./chunk-PZ5AY32C.js";
|
|
7
|
+
|
|
8
|
+
// src/async.ts
|
|
9
|
+
var async_exports = {};
|
|
10
|
+
__export(async_exports, {
|
|
11
|
+
handleOperation: () => handleOperation,
|
|
12
|
+
runBatch: () => runBatch,
|
|
13
|
+
sleep: () => sleep,
|
|
14
|
+
wait: () => wait
|
|
15
|
+
});
|
|
16
|
+
var sleep = (ms) => {
|
|
17
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
|
+
};
|
|
19
|
+
var wait = (ms) => sleep(ms);
|
|
20
|
+
async function runBatch(jobs, batchSize = 50) {
|
|
21
|
+
if (batchSize <= 0) {
|
|
22
|
+
throw new TsHelpersError("Batch size must be greater than 0", {
|
|
23
|
+
code: "INVALID_OPERATION" /* INVALID_OPERATION */,
|
|
24
|
+
data: { batchSize }
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const results = [];
|
|
28
|
+
const batches = Math.ceil(jobs.length / batchSize);
|
|
29
|
+
for (let i = 0; i < batches; i++) {
|
|
30
|
+
const batchStart = i * batchSize;
|
|
31
|
+
const batchEnd = batchStart + batchSize;
|
|
32
|
+
const batch = jobs.slice(batchStart, batchEnd);
|
|
33
|
+
const batchResults = await Promise.all(batch.map((job) => job));
|
|
34
|
+
results.push(...batchResults);
|
|
35
|
+
}
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
function handleOperation(target, operation, ...args) {
|
|
39
|
+
if (operation.includes("/")) {
|
|
40
|
+
const [parentOp, childOp] = operation.split("/");
|
|
41
|
+
if (!target[parentOp] || !target[parentOp][childOp]) {
|
|
42
|
+
throw new TsHelpersError(`Operation [${operation}] does not exist`, {
|
|
43
|
+
code: "INVALID_OPERATION" /* INVALID_OPERATION */,
|
|
44
|
+
data: { operation, parentOp, childOp }
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return target[parentOp][childOp](...args);
|
|
48
|
+
}
|
|
49
|
+
if (!target[operation]) {
|
|
50
|
+
throw new TsHelpersError(`Operation [${operation}] does not exist`, {
|
|
51
|
+
code: "INVALID_OPERATION" /* INVALID_OPERATION */,
|
|
52
|
+
data: { operation }
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return target[operation](...args);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export {
|
|
59
|
+
sleep,
|
|
60
|
+
wait,
|
|
61
|
+
runBatch,
|
|
62
|
+
handleOperation,
|
|
63
|
+
async_exports
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=chunk-LYTET5NX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/async.ts"],"sourcesContent":["/**\n * Asynchronous utilities and operations\n * Consolidated from core/async module\n */\n\n/* eslint-disable no-await-in-loop */\n\nimport { TsHelpersError, TsHelpersErrorCode } from './errors'\n\n// =============================================================================\n// DELAY UTILITIES\n// =============================================================================\n\n/**\n * Creates an asynchronous delay for the specified number of milliseconds\n *\n * Pauses async execution without blocking the event loop. Uses Promise with setTimeout\n * internally to schedule resumption after the specified delay.\n *\n * Common use cases:\n * - Rate limiting API calls\n * - Retry delays with exponential backoff\n * - Animation/transition timing\n * - Polling intervals\n * - Debouncing operations\n *\n * @param ms - Milliseconds to sleep (delay duration)\n * @returns Promise that resolves after the specified delay\n *\n * @example\n * ```typescript\n * // Basic delay\n * console.log('Starting...')\n * await sleep(2000) // Wait 2 seconds\n * console.log('Done!')\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Rate-limited API calls\n * async function fetchAllUsers(userIds: string[]): Promise<User[]> {\n * const users: User[] = []\n *\n * for (const id of userIds) {\n * const user = await api.getUser(id)\n * users.push(user)\n *\n * // Rate limit: 100ms between requests (max 10 req/sec)\n * await sleep(100)\n * }\n *\n * return users\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Exponential backoff retry\n * async function fetchWithRetry(url: string, maxRetries = 3): Promise<Response> {\n * let lastError: Error | null = null\n *\n * for (let attempt = 0; attempt < maxRetries; attempt++) {\n * try {\n * return await fetch(url)\n * } catch (error) {\n * lastError = error as Error\n * const delay = Math.pow(2, attempt) * 1000 // 1s, 2s, 4s\n * console.log(`Retry ${attempt + 1}/${maxRetries} in ${delay}ms`)\n * await sleep(delay)\n * }\n * }\n *\n * throw lastError\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Polling with timeout\n * async function pollUntilReady(\n * checkFn: () => Promise<boolean>,\n * timeoutMs = 30000\n * ): Promise<boolean> {\n * const startTime = Date.now()\n *\n * while (Date.now() - startTime < timeoutMs) {\n * if (await checkFn()) {\n * return true\n * }\n * await sleep(1000) // Poll every second\n * }\n *\n * throw new Error('Timeout waiting for ready state')\n * }\n *\n * // Usage: Wait for service to be ready\n * await pollUntilReady(async () => {\n * const response = await fetch('/health')\n * return response.ok\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Animated progress bar\n * async function animateProgress(element: HTMLElement): Promise<void> {\n * for (let i = 0; i <= 100; i += 5) {\n * element.style.width = `${i}%`\n * await sleep(50) // 50ms per step = 1 second total\n * }\n * }\n * ```\n *\n * @see {@link wait} for alias with same functionality\n * @see {@link runBatch} for controlled concurrent execution\n */\nexport const sleep = (ms: number): Promise<void> => {\n return new Promise(resolve => setTimeout(resolve, ms))\n}\n\n/**\n * Simple alias for sleep - Pauses execution for specified milliseconds\n *\n * Identical to {@link sleep}, provided for semantic clarity in code.\n * Use `wait` when the context emphasizes waiting for a condition or event.\n *\n * @param ms - Milliseconds to wait\n * @returns Promise that resolves after the specified delay\n *\n * @example\n * ```typescript\n * // Semantically clearer in some contexts\n * await wait(1000) // Wait 1 second before continuing\n *\n * // vs sleep (implies intentional delay)\n * await sleep(1000) // Sleep for 1 second\n * ```\n *\n * @see {@link sleep} for full documentation and examples\n */\nexport const wait = (ms: number): Promise<void> => sleep(ms)\n\n// =============================================================================\n// BATCH PROCESSING\n// =============================================================================\n\n/**\n * Executes an array of Promises in batches to control concurrency\n *\n * Processes promises in sequential batches, waiting for each batch to complete before\n * starting the next. Prevents overwhelming systems with too many concurrent requests.\n *\n * Algorithm:\n * 1. Divide promises into batches of size `batchSize`\n * 2. Execute each batch with Promise.all (parallel within batch)\n * 3. Wait for batch completion before starting next batch\n * 4. Collect and return all results in original order\n *\n * Use cases:\n * - Rate-limited API calls (respect API quotas)\n * - Database bulk operations (avoid connection pool exhaustion)\n * - File system operations (prevent file descriptor limits)\n * - Memory-intensive operations (control memory usage)\n *\n * @param jobs - Array of Promises to execute\n * @param batchSize - Number of promises to execute concurrently per batch (default: 50)\n * @returns Promise resolving to array of all results in original order\n *\n * @example\n * ```typescript\n * // Basic batch processing\n * const urls = ['url1', 'url2', ..., 'url100'] // 100 URLs\n * const fetchPromises = urls.map(url => fetch(url))\n *\n * // Execute 10 at a time instead of all 100 simultaneously\n * const responses = await runBatch(fetchPromises, 10)\n * // Batch 1: urls 0-9 (parallel)\n * // Batch 2: urls 10-19 (parallel) - starts after batch 1 completes\n * // ... 10 batches total\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Bulk user data fetch respecting API rate limits\n * async function fetchUsersInBatches(userIds: string[]): Promise<User[]> {\n * console.log(`Fetching ${userIds.length} users in batches of 20...`)\n *\n * const fetchPromises = userIds.map(id =>\n * fetch(`/api/users/${id}`).then(res => res.json())\n * )\n *\n * const users = await runBatch(fetchPromises, 20)\n *\n * console.log(`✅ Fetched ${users.length} users`)\n * return users\n * }\n *\n * // Fetches 1000 users: 50 batches of 20, not 1000 simultaneous requests\n * const allUsers = await fetchUsersInBatches(userIdArray)\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Database bulk insert with connection pool limits\n * async function bulkInsertUsers(users: User[]): Promise<void> {\n * // Database pool has 10 connections, use batch size of 5 for safety\n * const insertPromises = users.map(user =>\n * db.query('INSERT INTO users VALUES ($1, $2)', [user.name, user.email])\n * )\n *\n * await runBatch(insertPromises, 5)\n * console.log(`✅ Inserted ${users.length} users`)\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Image processing pipeline\n * async function processImages(imagePaths: string[]): Promise<Buffer[]> {\n * console.log(`Processing ${imagePaths.length} images...`)\n *\n * const processingPromises = imagePaths.map(async path => {\n * const img = await loadImage(path)\n * const resized = await resize(img, { width: 800, height: 600 })\n * const compressed = await compress(resized, { quality: 80 })\n * return compressed\n * })\n *\n * // Process 3 images at a time to avoid memory exhaustion\n * const processed = await runBatch(processingPromises, 3)\n *\n * console.log(`✅ Processed ${processed.length} images`)\n * return processed\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Parallel file uploads with progress tracking\n * async function uploadFilesWithProgress(\n * files: File[]\n * ): Promise<UploadResult[]> {\n * let completed = 0\n *\n * const uploadPromises = files.map(async file => {\n * const result = await uploadToS3(file)\n * completed++\n * console.log(`Progress: ${completed}/${files.length} (${((completed/files.length)*100).toFixed(1)}%)`)\n * return result\n * })\n *\n * // Upload 5 files at a time\n * return await runBatch(uploadPromises, 5)\n * }\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Scraping with politeness delay\n * async function scrapeWebsites(urls: string[]): Promise<ScrapedData[]> {\n * const scrapePromises = urls.map(async url => {\n * const html = await fetch(url).then(r => r.text())\n * const data = parseHTML(html)\n *\n * // Polite delay between requests in same batch\n * await sleep(200)\n *\n * return data\n * })\n *\n * // Only 3 concurrent requests to avoid overwhelming target server\n * return await runBatch(scrapePromises, 3)\n * }\n * ```\n *\n * @see {@link sleep} for adding delays between operations\n * @see {@link handleOperation} for dynamic operation execution\n */\nexport async function runBatch<T>(jobs: Promise<T>[], batchSize: number = 50): Promise<T[]> {\n if (batchSize <= 0) {\n throw new TsHelpersError('Batch size must be greater than 0', {\n code: TsHelpersErrorCode.INVALID_OPERATION,\n data: { batchSize },\n })\n }\n\n const results: T[] = []\n const batches = Math.ceil(jobs.length / batchSize)\n\n for (let i = 0; i < batches; i++) {\n const batchStart = i * batchSize\n const batchEnd = batchStart + batchSize\n const batch = jobs.slice(batchStart, batchEnd)\n\n const batchResults = await Promise.all(batch.map(job => job))\n results.push(...batchResults)\n }\n\n return results\n}\n\n// =============================================================================\n// DYNAMIC OPERATION UTILITIES\n// =============================================================================\n\n/**\n * Dynamically executes an operation on a target object by name\n *\n * Invokes methods on objects using string-based operation names. Supports nested\n * method access using 'parent/child' slash notation for organized operation namespacing.\n *\n * Features:\n * - Dynamic method invocation by string name\n * - Nested method access with '/' separator (e.g., 'api/getUser')\n * - Automatic error handling with descriptive messages\n * - Type-safe return value with generics\n *\n * Use cases:\n * - Dynamic API route handlers\n * - Plugin architectures with string-based commands\n * - RPC (Remote Procedure Call) implementations\n * - Command pattern implementations\n * - Configuration-driven operations\n *\n * @param target - Object containing methods to execute\n * @param operation - Method name or 'parent/child' path to execute\n * @param args - Arguments to pass to the operation\n * @returns Promise resolving to operation result\n * @throws {TsHelpersError} If operation doesn't exist on target\n *\n * @example\n * ```typescript\n * // Basic operation execution\n * const api = {\n * getUser: async (id: string) => ({ id, name: 'John' }),\n * createUser: async (data: any) => ({ id: '123', ...data })\n * }\n *\n * const user = await handleOperation<User>(api, 'getUser', 'user-123')\n * // { id: 'user-123', name: 'John' }\n * ```\n *\n * @example\n * ```typescript\n * // Nested operations with slash notation\n * const service = {\n * users: {\n * list: async () => [{ id: '1' }, { id: '2' }],\n * create: async (data: any) => ({ id: 'new', ...data }),\n * delete: async (id: string) => ({ success: true })\n * },\n * posts: {\n * list: async () => [{ id: 'post-1' }],\n * create: async (data: any) => ({ id: 'new-post', ...data })\n * }\n * }\n *\n * // Execute nested operation\n * const users = await handleOperation(service, 'users/list')\n * const newUser = await handleOperation(service, 'users/create', { name: 'Alice' })\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Dynamic API router\n * interface ApiRequest {\n * operation: string\n * params: any[]\n * }\n *\n * const apiHandlers = {\n * user: {\n * get: async (id: string) => db.users.findById(id),\n * create: async (data: UserInput) => db.users.create(data),\n * update: async (id: string, data: Partial<UserInput>) =>\n * db.users.update(id, data),\n * delete: async (id: string) => db.users.delete(id)\n * },\n * product: {\n * search: async (query: string) => db.products.search(query),\n * list: async (page: number) => db.products.list(page)\n * }\n * }\n *\n * async function handleApiRequest(req: ApiRequest): Promise<any> {\n * try {\n * return await handleOperation(\n * apiHandlers,\n * req.operation,\n * ...req.params\n * )\n * } catch (error) {\n * console.error(`Failed to execute ${req.operation}:`, error)\n * throw error\n * }\n * }\n *\n * // Usage\n * await handleApiRequest({ operation: 'user/get', params: ['user-123'] })\n * await handleApiRequest({ operation: 'product/search', params: ['laptop'] })\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: Plugin system with dynamic commands\n * interface Plugin {\n * name: string\n * commands: Record<string, (...args: any[]) => Promise<any>>\n * }\n *\n * class PluginManager {\n * private plugins: Map<string, Plugin> = new Map()\n *\n * register(plugin: Plugin): void {\n * this.plugins.set(plugin.name, plugin)\n * }\n *\n * async executeCommand(\n * pluginName: string,\n * command: string,\n * ...args: any[]\n * ): Promise<any> {\n * const plugin = this.plugins.get(pluginName)\n * if (!plugin) throw new Error(`Plugin ${pluginName} not found`)\n *\n * return handleOperation(plugin.commands, command, ...args)\n * }\n * }\n *\n * // Register plugins\n * const manager = new PluginManager()\n * manager.register({\n * name: 'fileOps',\n * commands: {\n * read: async (path: string) => fs.readFile(path, 'utf-8'),\n * write: async (path: string, content: string) =>\n * fs.writeFile(path, content)\n * }\n * })\n *\n * // Execute plugin commands dynamically\n * await manager.executeCommand('fileOps', 'read', './config.json')\n * ```\n *\n * @example\n * ```typescript\n * // Real-world: RPC-style service calls\n * const rpcService = {\n * math: {\n * add: async (a: number, b: number) => a + b,\n * multiply: async (a: number, b: number) => a * b\n * },\n * string: {\n * reverse: async (s: string) => s.split('').reverse().join(''),\n * uppercase: async (s: string) => s.toUpperCase()\n * }\n * }\n *\n * async function rpcCall(method: string, ...params: any[]): Promise<any> {\n * console.log(`RPC Call: ${method}(${params.join(', ')})`)\n * const result = await handleOperation(rpcService, method, ...params)\n * console.log(`RPC Result: ${result}`)\n * return result\n * }\n *\n * await rpcCall('math/add', 5, 3) // RPC Result: 8\n * await rpcCall('string/reverse', 'hello') // RPC Result: olleh\n * ```\n *\n * @example\n * ```typescript\n * // Error handling\n * try {\n * await handleOperation(api, 'nonexistent/method')\n * } catch (error) {\n * console.error(error.message)\n * // \"Operation [nonexistent/method] does not exist\"\n * console.error(error.code)\n * // TsHelpersErrorCode.INVALID_OPERATION\n * }\n * ```\n *\n * @see {@link TsHelpersError} for error structure\n * @see {@link TsHelpersErrorCode} for error codes\n */\nexport function handleOperation<T>(\n target: Record<string, any>,\n operation: string,\n ...args: any[]\n): Promise<T> {\n if (operation.includes('/')) {\n const [parentOp, childOp] = operation.split('/')\n\n if (!target[parentOp] || !target[parentOp][childOp]) {\n throw new TsHelpersError(`Operation [${operation}] does not exist`, {\n code: TsHelpersErrorCode.INVALID_OPERATION,\n data: { operation, parentOp, childOp },\n })\n }\n\n return target[parentOp][childOp](...args)\n }\n\n if (!target[operation]) {\n throw new TsHelpersError(`Operation [${operation}] does not exist`, {\n code: TsHelpersErrorCode.INVALID_OPERATION,\n data: { operation },\n })\n }\n\n return target[operation](...args)\n}\n"],"mappings":";;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoHO,IAAM,QAAQ,CAAC,OAA8B;AAClD,SAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACvD;AAsBO,IAAM,OAAO,CAAC,OAA8B,MAAM,EAAE;AA0I3D,eAAsB,SAAY,MAAoB,YAAoB,IAAkB;AAC1F,MAAI,aAAa,GAAG;AAClB,UAAM,IAAI,eAAe,qCAAqC;AAAA,MAC5D;AAAA,MACA,MAAM,EAAE,UAAU;AAAA,IACpB,CAAC;AAAA,EACH;AAEA,QAAM,UAAe,CAAC;AACtB,QAAM,UAAU,KAAK,KAAK,KAAK,SAAS,SAAS;AAEjD,WAAS,IAAI,GAAG,IAAI,SAAS,KAAK;AAChC,UAAM,aAAa,IAAI;AACvB,UAAM,WAAW,aAAa;AAC9B,UAAM,QAAQ,KAAK,MAAM,YAAY,QAAQ;AAE7C,UAAM,eAAe,MAAM,QAAQ,IAAI,MAAM,IAAI,SAAO,GAAG,CAAC;AAC5D,YAAQ,KAAK,GAAG,YAAY;AAAA,EAC9B;AAEA,SAAO;AACT;AA0LO,SAAS,gBACd,QACA,cACG,MACS;AACZ,MAAI,UAAU,SAAS,GAAG,GAAG;AAC3B,UAAM,CAAC,UAAU,OAAO,IAAI,UAAU,MAAM,GAAG;AAE/C,QAAI,CAAC,OAAO,QAAQ,KAAK,CAAC,OAAO,QAAQ,EAAE,OAAO,GAAG;AACnD,YAAM,IAAI,eAAe,cAAc,SAAS,oBAAoB;AAAA,QAClE;AAAA,QACA,MAAM,EAAE,WAAW,UAAU,QAAQ;AAAA,MACvC,CAAC;AAAA,IACH;AAEA,WAAO,OAAO,QAAQ,EAAE,OAAO,EAAE,GAAG,IAAI;AAAA,EAC1C;AAEA,MAAI,CAAC,OAAO,SAAS,GAAG;AACtB,UAAM,IAAI,eAAe,cAAc,SAAS,oBAAoB;AAAA,MAClE;AAAA,MACA,MAAM,EAAE,UAAU;AAAA,IACpB,CAAC;AAAA,EACH;AAEA,SAAO,OAAO,SAAS,EAAE,GAAG,IAAI;AAClC;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|