@mitre/hdf-utilities 3.2.0 → 3.3.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/README.md CHANGED
@@ -46,7 +46,7 @@ import { parseJSON, stringifyJSON, isValidJSON } from '@mitre/hdf-utilities';
46
46
 
47
47
  // Parse JSON with clear error messages
48
48
  try {
49
- const data = parseJSON<HdfResults>(jsonString);
49
+ const data = parseJSON<HDFResults>(jsonString);
50
50
  console.log(data.baselines);
51
51
  } catch (error) {
52
52
  console.error('Invalid JSON:', error.message);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/csv/index.ts"],"mappings":";;;;;;UAiBiB,eAAA;EACf,MAAA;EACA,cAAA;EACA,eAAA,IAAmB,MAAA,UAAgB,KAAA;EACnC,aAAA;EACA,SAAA;EACA,OAAA;AAAA;;;;UAMe,eAAA;EACf,MAAA;EACA,OAAA;EACA,cAAA;EACA,SAAA;AAAA;AAwEF;;;;;;;AAAA,iBAAgB,QAAA,KAAa,MAAA,kBAAA,CAC3B,GAAA,UACA,OAAA,GAAU,OAAA,CAAQ,eAAA,IACjB,CAAA;;;;;;;;iBAoDa,aAAA,CACd,GAAA,UACA,OAAA,GAAU,OAAO,CAAC,eAAA;;AAtDhB;AAoDJ;;;;;;;;iBAmCgB,QAAA,KAAa,MAAA,kBAAA,CAC3B,IAAA,EAAM,CAAA,IACN,OAAA,GAAU,OAAA,CAAQ,eAAA;EAAqB,QAAA;AAAA;;;;;;;;;;;iBA2CzB,aAAA,CACd,IAAA,cACA,OAAA,GAAU,OAAO,CAAC,eAAA;EAAqB,QAAA;AAAA;;;;;AA7CoB;iBAwE7C,UAAA,CAAW,GAAW;;;;;;;;;;iBA0BtB,gBAAA,CAAiB,KAAc;AA1B/C;;;;AAAsC;AA0BtC;AA1BA,iBAyCgB,gBAAA,CAAiB,MAAiB;;;AAfH;AAe/C;;;iBAUgB,iBAAA,WAA4B,MAAA,kBAAA,CAC1C,GAAA,EAAK,CAAA,GACJ,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/csv/index.ts"],"mappings":";;;;;;UAiBiB,eAAA;EACf,MAAA;EACA,cAAA;EACA,eAAA,IAAmB,MAAA,UAAgB,KAAA;EACnC,aAAA;EACA,SAAA;EACA,OAAA;AAAA;;;;UAMe,eAAA;EACf,MAAA;EACA,OAAA;EACA,cAAA;EACA,SAAA;AAAA;AAwEF;;;;;;;AAAA,iBAAgB,QAAA,KAAa,MAAA,mBAC3B,GAAA,UACA,OAAA,GAAU,OAAA,CAAQ,eAAA,IACjB,CAAA;;;;;;;;iBAoDa,aAAA,CACd,GAAA,UACA,OAAA,GAAU,OAAO,CAAC,eAAA;;AAtDhB;AAoDJ;;;;;;;;iBAmCgB,QAAA,KAAa,MAAA,mBAC3B,IAAA,EAAM,CAAA,IACN,OAAA,GAAU,OAAA,CAAQ,eAAA;EAAqB,QAAA;AAAA;;;;;;;;;;;iBA2CzB,aAAA,CACd,IAAA,cACA,OAAA,GAAU,OAAO,CAAC,eAAA;EAAqB,QAAA;AAAA;;;;;AA7CoB;iBAwE7C,UAAA,CAAW,GAAW;;;;;;;;;;iBA0BtB,gBAAA,CAAiB,KAAc;AA1B/C;;;;AAAsC;AA0BtC;AA1BA,iBAyCgB,gBAAA,CAAiB,MAAiB;;;AAfH;AAe/C;;;iBAUgB,iBAAA,WAA4B,MAAA,mBAC1C,GAAA,EAAK,CAAA,GACJ,MAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/csv/index.ts"],"sourcesContent":["/**\n * CSV parsing and generation utilities using d3-dsv\n *\n * SECURITY WARNING: CSV files opened in Excel/LibreOffice may execute formulas.\n * Use sanitization options when exporting data that may contain user-controlled content.\n */\n\nimport { dsvFormat } from 'd3-dsv';\n\nexport {\n extractColumn as extractCsvColumn,\n findRows as findCsvRows,\n} from '../object/index.js';\n\n/**\n * Options for parsing CSV\n */\nexport interface CsvParseOptions {\n header?: boolean;\n skipEmptyLines?: boolean;\n transformHeader?: (header: string, index: number) => string;\n dynamicTyping?: boolean;\n delimiter?: string;\n maxSize?: number;\n}\n\n/**\n * Options for building CSV\n */\nexport interface CsvBuildOptions {\n header?: boolean;\n newline?: string;\n skipEmptyLines?: boolean;\n delimiter?: string;\n}\n\n/**\n * Default options for parsing CSV with headers\n */\nconst DEFAULT_PARSE_OPTIONS: CsvParseOptions = {\n header: true,\n skipEmptyLines: true,\n transformHeader: undefined,\n dynamicTyping: false,\n};\n\n/**\n * Default options for parsing CSV without headers\n */\nconst DEFAULT_PARSE_ARRAY_OPTIONS: CsvParseOptions = {\n header: false,\n skipEmptyLines: true,\n dynamicTyping: false,\n};\n\n/**\n * Default options for building CSV\n */\nconst DEFAULT_BUILD_OPTIONS: CsvBuildOptions = {\n header: true,\n newline: '\\n',\n skipEmptyLines: true,\n};\n\n/**\n * Validate CSV quoting — throws if unclosed quotes are found\n */\nfunction validateQuoting(csv: string): void {\n let inQuotes = false;\n let row = 0;\n for (let i = 0; i < csv.length; i++) {\n if (csv[i] === '\"') {\n if (inQuotes && i + 1 < csv.length && csv[i + 1] === '\"') {\n i++; // skip escaped quote \"\"\n } else {\n inQuotes = !inQuotes;\n }\n } else if (csv[i] === '\\n' && !inQuotes) {\n row++;\n }\n }\n if (inQuotes) {\n throw new Error(`CSV parsing failed: Row ${row}: Unclosed quote`);\n }\n}\n\n/**\n * Convert string value to number or boolean when appropriate\n */\nfunction dynamicTypeConvert(value: string): unknown {\n if (value === '') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n const num = Number(value);\n if (!isNaN(num) && value.trim() !== '') return num;\n return value;\n}\n\n/**\n * Parse CSV string into array of objects with headers as keys\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of objects representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsv<T = Record<string, unknown>>(\n csv: string,\n options?: Partial<CsvParseOptions>\n): T[] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n const parsed = dsv.parse(trimmed);\n const { columns } = parsed;\n\n // Pre-compute transformed headers\n const headers = opts.transformHeader\n ? columns.map((col, i) => opts.transformHeader!(col, i))\n : columns;\n\n let rows: Record<string, unknown>[] = parsed.map((row) => {\n const obj: Record<string, unknown> = {};\n columns.forEach((col, i) => {\n let value: unknown = row[col] ?? '';\n if (opts.dynamicTyping && typeof value === 'string') {\n value = dynamicTypeConvert(value);\n }\n const key = headers[i];\n if (key !== undefined) {\n obj[key] = value;\n }\n });\n return obj;\n });\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) =>\n Object.values(row).some((v) => v !== '' && v !== null && v !== undefined)\n );\n }\n\n return rows as T[];\n}\n\n/**\n * Parse CSV string into array of arrays without treating first row as headers\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of arrays representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsvArray(\n csv: string,\n options?: Partial<CsvParseOptions>\n): string[][] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_ARRAY_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let rows = dsv.parseRows(trimmed);\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) => !(row.length === 1 && row[0] === ''));\n }\n\n return rows;\n}\n\n/**\n * Build CSV string from array of objects\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of objects to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsv<T = Record<string, unknown>>(\n data: T[],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) =>\n sanitizeCsvObject(row as Record<string, unknown>)\n ) as T[];\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result: string;\n\n if (opts.header === false) {\n const rows = processedData.map((row) =>\n Object.values(row as Record<string, unknown>).map(String)\n );\n result = dsv.formatRows(rows);\n } else {\n result = dsv.format(processedData as Record<string, unknown>[]);\n }\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Build CSV string from array of arrays\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of arrays to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsvArray(\n data: string[][],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, header: false, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) => sanitizeCsvArray(row));\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result = dsv.formatRows(processedData);\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Validate CSV format\n * @param csv - CSV string to validate\n * @returns True if valid CSV, false otherwise\n */\nexport function isValidCsv(csv: string): boolean {\n if (!csv || csv.trim().length === 0) return false;\n\n const trimmed = csv.trim();\n\n // CSV must have at least one delimiter (comma) to be considered CSV structure\n if (!trimmed.includes(',')) return false;\n\n try {\n validateQuoting(trimmed);\n } catch {\n return false;\n }\n\n return true;\n}\n\n/**\n * Sanitize a single CSV value to prevent formula injection\n *\n * Prefixes values starting with =, +, -, @, |, or % with a single quote\n * to prevent Excel/LibreOffice from interpreting them as formulas.\n *\n * @param value - Value to sanitize\n * @returns Sanitized value\n */\nexport function sanitizeCsvValue(value: unknown): string {\n const str = String(value);\n // Check if string starts with formula trigger characters\n if (/^[=+\\-@|%]/.test(str)) {\n return `'${str}`;\n }\n return str;\n}\n\n/**\n * Sanitize all values in an array to prevent formula injection\n *\n * @param values - Array of values to sanitize\n * @returns Array with sanitized values\n */\nexport function sanitizeCsvArray(values: unknown[]): string[] {\n return values.map(sanitizeCsvValue);\n}\n\n/**\n * Sanitize all values in an object to prevent formula injection\n *\n * @param obj - Object with values to sanitize\n * @returns New object with sanitized values\n */\nexport function sanitizeCsvObject<T extends Record<string, unknown>>(\n obj: T\n): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(obj)) {\n sanitized[key] = sanitizeCsvValue(value);\n }\n return sanitized;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/csv/index.ts"],"sourcesContent":["/**\n * CSV parsing and generation utilities using d3-dsv\n *\n * SECURITY WARNING: CSV files opened in Excel/LibreOffice may execute formulas.\n * Use sanitization options when exporting data that may contain user-controlled content.\n */\n\nimport { dsvFormat } from 'd3-dsv';\n\nexport {\n extractColumn as extractCsvColumn,\n findRows as findCsvRows,\n} from '../object/index.js';\n\n/**\n * Options for parsing CSV\n */\nexport interface CsvParseOptions {\n header?: boolean;\n skipEmptyLines?: boolean;\n transformHeader?: (header: string, index: number) => string;\n dynamicTyping?: boolean;\n delimiter?: string;\n maxSize?: number;\n}\n\n/**\n * Options for building CSV\n */\nexport interface CsvBuildOptions {\n header?: boolean;\n newline?: string;\n skipEmptyLines?: boolean;\n delimiter?: string;\n}\n\n/**\n * Default options for parsing CSV with headers\n */\nconst DEFAULT_PARSE_OPTIONS: CsvParseOptions = {\n header: true,\n skipEmptyLines: true,\n transformHeader: undefined,\n dynamicTyping: false,\n};\n\n/**\n * Default options for parsing CSV without headers\n */\nconst DEFAULT_PARSE_ARRAY_OPTIONS: CsvParseOptions = {\n header: false,\n skipEmptyLines: true,\n dynamicTyping: false,\n};\n\n/**\n * Default options for building CSV\n */\nconst DEFAULT_BUILD_OPTIONS: CsvBuildOptions = {\n header: true,\n newline: '\\n',\n skipEmptyLines: true,\n};\n\n/**\n * Validate CSV quoting — throws if unclosed quotes are found\n */\nfunction validateQuoting(csv: string): void {\n let inQuotes = false;\n let row = 0;\n for (let i = 0; i < csv.length; i++) {\n if (csv[i] === '\"') {\n if (inQuotes && i + 1 < csv.length && csv[i + 1] === '\"') {\n i++; // skip escaped quote \"\"\n } else {\n inQuotes = !inQuotes;\n }\n } else if (csv[i] === '\\n' && !inQuotes) {\n row++;\n }\n }\n if (inQuotes) {\n throw new Error(`CSV parsing failed: Row ${row}: Unclosed quote`);\n }\n}\n\n/**\n * Convert string value to number or boolean when appropriate\n */\nfunction dynamicTypeConvert(value: string): unknown {\n if (value === '') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n const num = Number(value);\n if (!isNaN(num) && value.trim() !== '') return num;\n return value;\n}\n\n/**\n * Parse CSV string into array of objects with headers as keys\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of objects representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsv<T = Record<string, unknown>>(\n csv: string,\n options?: Partial<CsvParseOptions>\n): T[] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n const parsed = dsv.parse(trimmed);\n const { columns } = parsed;\n\n // Pre-compute transformed headers\n const headers = opts.transformHeader\n ? columns.map((col, i) => opts.transformHeader!(col, i))\n : columns;\n\n let rows: Record<string, unknown>[] = parsed.map((row) => {\n const obj: Record<string, unknown> = {};\n columns.forEach((col, i) => {\n let value: unknown = row[col] ?? '';\n if (opts.dynamicTyping && typeof value === 'string') {\n value = dynamicTypeConvert(value);\n }\n const key = headers[i];\n if (key !== undefined) {\n obj[key] = value;\n }\n });\n return obj;\n });\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) =>\n Object.values(row).some((v) => v !== '' && v !== null && v !== undefined)\n );\n }\n\n return rows as T[];\n}\n\n/**\n * Parse CSV string into array of arrays without treating first row as headers\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of arrays representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsvArray(\n csv: string,\n options?: Partial<CsvParseOptions>\n): string[][] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_ARRAY_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let rows = dsv.parseRows(trimmed);\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) => !(row.length === 1 && row[0] === ''));\n }\n\n return rows;\n}\n\n/**\n * Build CSV string from array of objects\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of objects to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsv<T = Record<string, unknown>>(\n data: T[],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) =>\n sanitizeCsvObject(row as Record<string, unknown>)\n ) as T[];\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result: string;\n\n if (opts.header === false) {\n const rows = processedData.map((row) =>\n Object.values(row as Record<string, unknown>).map(String)\n );\n result = dsv.formatRows(rows);\n } else {\n result = dsv.format(processedData as Record<string, unknown>[]);\n }\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Build CSV string from array of arrays\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of arrays to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsvArray(\n data: string[][],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, header: false, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) => sanitizeCsvArray(row));\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result = dsv.formatRows(processedData);\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Validate CSV format\n * @param csv - CSV string to validate\n * @returns True if valid CSV, false otherwise\n */\nexport function isValidCsv(csv: string): boolean {\n if (!csv || csv.trim().length === 0) return false;\n\n const trimmed = csv.trim();\n\n // CSV must have at least one delimiter (comma) to be considered CSV structure\n if (!trimmed.includes(',')) return false;\n\n try {\n validateQuoting(trimmed);\n } catch {\n return false;\n }\n\n return true;\n}\n\n/**\n * Sanitize a single CSV value to prevent formula injection\n *\n * Prefixes values starting with =, +, -, @, |, or % with a single quote\n * to prevent Excel/LibreOffice from interpreting them as formulas.\n *\n * @param value - Value to sanitize\n * @returns Sanitized value\n */\nexport function sanitizeCsvValue(value: unknown): string {\n const str = String(value);\n // Check if string starts with formula trigger characters\n if (/^[=+\\-@|%]/.test(str)) {\n return `'${str}`;\n }\n return str;\n}\n\n/**\n * Sanitize all values in an array to prevent formula injection\n *\n * @param values - Array of values to sanitize\n * @returns Array with sanitized values\n */\nexport function sanitizeCsvArray(values: unknown[]): string[] {\n return values.map(sanitizeCsvValue);\n}\n\n/**\n * Sanitize all values in an object to prevent formula injection\n *\n * @param obj - Object with values to sanitize\n * @returns New object with sanitized values\n */\nexport function sanitizeCsvObject<T extends Record<string, unknown>>(\n obj: T\n): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(obj)) {\n sanitized[key] = sanitizeCsvValue(value);\n }\n return sanitized;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/hash/index.ts"],"sourcesContent":["/**\n * Hash utilities for HDF content\n * Provides cryptographic hashing functions for content identification and integrity verification\n * Uses the Web Crypto API (globalThis.crypto.subtle) for browser and Node.js compatibility\n */\n\n/**\n * Supported hash algorithms\n */\nexport type HashAlgorithm = 'sha256' | 'sha512';\n\n/**\n * Supported output encodings\n */\nexport type HashEncoding = 'hex' | 'base64';\n\n/**\n * Options for hash generation\n */\nexport interface HashOptions {\n /** Hash algorithm (default: 'sha256') */\n algorithm?: HashAlgorithm;\n /** Output encoding (default: 'hex') */\n encoding?: HashEncoding;\n}\n\n/** Map our algorithm names to Web Crypto algorithm identifiers */\nconst ALGORITHM_MAP: Record<HashAlgorithm, string> = {\n sha256: 'SHA-256',\n sha512: 'SHA-512',\n};\n\n/** Convert an ArrayBuffer to a hex string */\nfunction bufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let hex = '';\n for (const b of bytes) {\n hex += b.toString(16).padStart(2, '0');\n }\n return hex;\n}\n\n/** Convert an ArrayBuffer to a base64 string */\nfunction bufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (const b of bytes) {\n binary += String.fromCharCode(b);\n }\n return btoa(binary);\n}\n\n/**\n * Generate a hash of the given data\n *\n * @param data - String data to hash\n * @param options - Hash generation options\n * @returns Hex-encoded hash string\n *\n * @example\n * ```typescript\n * const hash = await generateHash('hello world');\n * // Returns: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'\n * ```\n */\nexport async function generateHash(data: string, options: HashOptions = {}): Promise<string> {\n const { algorithm = 'sha256', encoding = 'hex' } = options;\n const webAlgorithm = ALGORITHM_MAP[algorithm];\n const encoded = new TextEncoder().encode(data);\n const hashBuffer = await globalThis.crypto.subtle.digest(webAlgorithm, encoded);\n return encoding === 'base64' ? bufferToBase64(hashBuffer) : bufferToHex(hashBuffer);\n}\n\n/**\n * Generate a SHA-256 hash (recommended for most use cases)\n *\n * @param data - String data to hash\n * @returns Hex-encoded SHA-256 hash\n *\n * @example\n * ```typescript\n * const hash = await sha256('hello world');\n * // Returns: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'\n * ```\n */\nexport async function sha256(data: string): Promise<string> {\n return generateHash(data, { algorithm: 'sha256' });\n}\n\n/**\n * Generate a SHA-512 hash (enhanced security for sensitive data)\n *\n * @param data - String data to hash\n * @returns Hex-encoded SHA-512 hash\n *\n * @example\n * ```typescript\n * const hash = await sha512('hello world');\n * // Returns: '309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f'\n * ```\n */\nexport async function sha512(data: string): Promise<string> {\n return generateHash(data, { algorithm: 'sha512' });\n}\n\n/**\n * Generate a hash of an object by stringifying it first\n * Useful for hashing HDF control objects or other structured data\n *\n * @param obj - Object to hash\n * @param options - Hash generation options\n * @returns Hex-encoded hash string\n *\n * @example\n * ```typescript\n * const control = { id: 'V-12345', title: 'Test Control' };\n * const hash = await hashObject(control);\n * // Returns hash of the JSON-stringified object\n * ```\n */\nexport async function hashObject(obj: unknown, options: HashOptions = {}): Promise<string> {\n // JSON.stringify returns undefined for undefined, so handle it explicitly\n const stringified = obj === undefined ? 'undefined' : JSON.stringify(obj);\n return generateHash(stringified, options);\n}\n\n/**\n * Verify that data matches a given hash\n *\n * @param data - String data to verify\n * @param expectedHash - Expected hash value\n * @param options - Hash generation options (must match hash generation)\n * @returns True if data matches the hash\n *\n * @example\n * ```typescript\n * const data = 'hello world';\n * const hash = await sha256(data);\n * const isValid = await verifyHash(data, hash); // true\n * ```\n */\nexport async function verifyHash(\n data: string,\n expectedHash: string,\n options: HashOptions = {}\n): Promise<boolean> {\n const actualHash = await generateHash(data, options);\n return actualHash === expectedHash;\n}\n"],"mappings":";;AA2BA,MAAM,gBAA+C;CACnD,QAAQ;CACR,QAAQ;AACV;;AAGA,SAAS,YAAY,QAA6B;CAChD,MAAM,QAAQ,IAAI,WAAW,MAAM;CACnC,IAAI,MAAM;CACV,KAAK,MAAM,KAAK,OACd,OAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;CAEvC,OAAO;AACT;;AAGA,SAAS,eAAe,QAA6B;CACnD,MAAM,QAAQ,IAAI,WAAW,MAAM;CACnC,IAAI,SAAS;CACb,KAAK,MAAM,KAAK,OACd,UAAU,OAAO,aAAa,CAAC;CAEjC,OAAO,KAAK,MAAM;AACpB;;;;;;;;;;;;;;AAeA,eAAsB,aAAa,MAAc,UAAuB,CAAC,GAAoB;CAC3F,MAAM,EAAE,YAAY,UAAU,WAAW,UAAU;CACnD,MAAM,eAAe,cAAc;CACnC,MAAM,UAAU,IAAI,YAAY,EAAE,OAAO,IAAI;CAC7C,MAAM,aAAa,MAAM,WAAW,OAAO,OAAO,OAAO,cAAc,OAAO;CAC9E,OAAO,aAAa,WAAW,eAAe,UAAU,IAAI,YAAY,UAAU;AACpF;;;;;;;;;;;;;AAcA,eAAsB,OAAO,MAA+B;CAC1D,OAAO,aAAa,MAAM,EAAE,WAAW,SAAS,CAAC;AACnD;;;;;;;;;;;;;AAcA,eAAsB,OAAO,MAA+B;CAC1D,OAAO,aAAa,MAAM,EAAE,WAAW,SAAS,CAAC;AACnD;;;;;;;;;;;;;;;;AAiBA,eAAsB,WAAW,KAAc,UAAuB,CAAC,GAAoB;CAGzF,OAAO,aADa,QAAQ,KAAA,IAAY,cAAc,KAAK,UAAU,GAAG,GACvC,OAAO;AAC1C;;;;;;;;;;;;;;;;AAiBA,eAAsB,WACpB,MACA,cACA,UAAuB,CAAC,GACN;CAElB,OAAO,MADkB,aAAa,MAAM,OAAO,MAC7B;AACxB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/hash/index.ts"],"sourcesContent":["/**\n * Hash utilities for HDF content\n * Provides cryptographic hashing functions for content identification and integrity verification\n * Uses the Web Crypto API (globalThis.crypto.subtle) for browser and Node.js compatibility\n */\n\n/**\n * Supported hash algorithms\n */\nexport type HashAlgorithm = 'sha256' | 'sha512';\n\n/**\n * Supported output encodings\n */\nexport type HashEncoding = 'hex' | 'base64';\n\n/**\n * Options for hash generation\n */\nexport interface HashOptions {\n /** Hash algorithm (default: 'sha256') */\n algorithm?: HashAlgorithm;\n /** Output encoding (default: 'hex') */\n encoding?: HashEncoding;\n}\n\n/** Map our algorithm names to Web Crypto algorithm identifiers */\nconst ALGORITHM_MAP: Record<HashAlgorithm, string> = {\n sha256: 'SHA-256',\n sha512: 'SHA-512',\n};\n\n/** Convert an ArrayBuffer to a hex string */\nfunction bufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let hex = '';\n for (const b of bytes) {\n hex += b.toString(16).padStart(2, '0');\n }\n return hex;\n}\n\n/** Convert an ArrayBuffer to a base64 string */\nfunction bufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (const b of bytes) {\n binary += String.fromCharCode(b);\n }\n return btoa(binary);\n}\n\n/**\n * Generate a hash of the given data\n *\n * @param data - String data to hash\n * @param options - Hash generation options\n * @returns Hex-encoded hash string\n *\n * @example\n * ```typescript\n * const hash = await generateHash('hello world');\n * // Returns: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'\n * ```\n */\nexport async function generateHash(data: string, options: HashOptions = {}): Promise<string> {\n const { algorithm = 'sha256', encoding = 'hex' } = options;\n const webAlgorithm = ALGORITHM_MAP[algorithm];\n const encoded = new TextEncoder().encode(data);\n const hashBuffer = await globalThis.crypto.subtle.digest(webAlgorithm, encoded);\n return encoding === 'base64' ? bufferToBase64(hashBuffer) : bufferToHex(hashBuffer);\n}\n\n/**\n * Generate a SHA-256 hash (recommended for most use cases)\n *\n * @param data - String data to hash\n * @returns Hex-encoded SHA-256 hash\n *\n * @example\n * ```typescript\n * const hash = await sha256('hello world');\n * // Returns: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'\n * ```\n */\nexport async function sha256(data: string): Promise<string> {\n return generateHash(data, { algorithm: 'sha256' });\n}\n\n/**\n * Generate a SHA-512 hash (enhanced security for sensitive data)\n *\n * @param data - String data to hash\n * @returns Hex-encoded SHA-512 hash\n *\n * @example\n * ```typescript\n * const hash = await sha512('hello world');\n * // Returns: '309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f'\n * ```\n */\nexport async function sha512(data: string): Promise<string> {\n return generateHash(data, { algorithm: 'sha512' });\n}\n\n/**\n * Generate a hash of an object by stringifying it first\n * Useful for hashing HDF control objects or other structured data\n *\n * @param obj - Object to hash\n * @param options - Hash generation options\n * @returns Hex-encoded hash string\n *\n * @example\n * ```typescript\n * const control = { id: 'V-12345', title: 'Test Control' };\n * const hash = await hashObject(control);\n * // Returns hash of the JSON-stringified object\n * ```\n */\nexport async function hashObject(obj: unknown, options: HashOptions = {}): Promise<string> {\n // JSON.stringify returns undefined for undefined, so handle it explicitly\n const stringified = obj === undefined ? 'undefined' : JSON.stringify(obj);\n return generateHash(stringified, options);\n}\n\n/**\n * Verify that data matches a given hash\n *\n * @param data - String data to verify\n * @param expectedHash - Expected hash value\n * @param options - Hash generation options (must match hash generation)\n * @returns True if data matches the hash\n *\n * @example\n * ```typescript\n * const data = 'hello world';\n * const hash = await sha256(data);\n * const isValid = await verifyHash(data, hash); // true\n * ```\n */\nexport async function verifyHash(\n data: string,\n expectedHash: string,\n options: HashOptions = {}\n): Promise<boolean> {\n const actualHash = await generateHash(data, options);\n return actualHash === expectedHash;\n}\n"],"mappings":";;AA2BA,MAAM,gBAA+C;CACnD,QAAQ;CACR,QAAQ;AACV;;AAGA,SAAS,YAAY,QAA6B;CAChD,MAAM,QAAQ,IAAI,WAAW,MAAM;CACnC,IAAI,MAAM;CACV,KAAK,MAAM,KAAK,OACd,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;CAEvC,OAAO;AACT;;AAGA,SAAS,eAAe,QAA6B;CACnD,MAAM,QAAQ,IAAI,WAAW,MAAM;CACnC,IAAI,SAAS;CACb,KAAK,MAAM,KAAK,OACd,UAAU,OAAO,aAAa,CAAC;CAEjC,OAAO,KAAK,MAAM;AACpB;;;;;;;;;;;;;;AAeA,eAAsB,aAAa,MAAc,UAAuB,CAAC,GAAoB;CAC3F,MAAM,EAAE,YAAY,UAAU,WAAW,UAAU;CACnD,MAAM,eAAe,cAAc;CACnC,MAAM,UAAU,IAAI,YAAY,CAAC,CAAC,OAAO,IAAI;CAC7C,MAAM,aAAa,MAAM,WAAW,OAAO,OAAO,OAAO,cAAc,OAAO;CAC9E,OAAO,aAAa,WAAW,eAAe,UAAU,IAAI,YAAY,UAAU;AACpF;;;;;;;;;;;;;AAcA,eAAsB,OAAO,MAA+B;CAC1D,OAAO,aAAa,MAAM,EAAE,WAAW,SAAS,CAAC;AACnD;;;;;;;;;;;;;AAcA,eAAsB,OAAO,MAA+B;CAC1D,OAAO,aAAa,MAAM,EAAE,WAAW,SAAS,CAAC;AACnD;;;;;;;;;;;;;;;;AAiBA,eAAsB,WAAW,KAAc,UAAuB,CAAC,GAAoB;CAGzF,OAAO,aADa,QAAQ,KAAA,IAAY,cAAc,KAAK,UAAU,GAAG,GACvC,OAAO;AAC1C;;;;;;;;;;;;;;;;AAiBA,eAAsB,WACpB,MACA,cACA,UAAuB,CAAC,GACN;CAElB,OAAO,MADkB,aAAa,MAAM,OAAO,MAC7B;AACxB"}
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import { CsvBuildOptions, CsvParseOptions, buildCsv, buildCsvArray, isValidCsv,
3
3
  import { HashAlgorithm, HashEncoding, HashOptions, generateHash, hashObject, sha256, sha512, verifyHash } from "./hash/index.js";
4
4
  import { StringifyOptions, isValidJSON, parseJSON, stringifyJSON } from "./json/index.js";
5
5
  import { buildXml, extractTextFromXml, isValidXml, parseXml, parseXmlWithArrays } from "./xml/index.js";
6
- import { parseTimestamp, stripHtml } from "./string/index.js";
6
+ import { formatTimestamp, formatTimestampSeconds, parseTimestamp, stripHtml, trimUtcFraction } from "./string/index.js";
7
7
 
8
8
  //#region src/severity/index.d.ts
9
9
  /**
@@ -38,6 +38,205 @@ declare function severityToImpact(severity: string | null): number | null;
38
38
  declare function impactToSeverity(impact: null): null;
39
39
  declare function impactToSeverity(impact: number): string;
40
40
  declare function impactToSeverity(impact: number | null): string | null;
41
+ /**
42
+ * Map a raw CVSS base score (0.0–10.0) to a FIRST qualitative severity band.
43
+ *
44
+ * Bands per FIRST CVSS v3.x / v4.0 spec:
45
+ * none = 0.0
46
+ * low = 0.1 – 3.9
47
+ * medium = 4.0 – 6.9
48
+ * high = 7.0 – 8.9
49
+ * critical = 9.0 – 10.0
50
+ *
51
+ * Out-of-range inputs are clamped: scores < 0.1 → "none" (band floor),
52
+ * scores > 10.0 → "critical". This matches scanner behavior of treating
53
+ * malformed scores as informational/critical extremes rather than throwing.
54
+ *
55
+ * @param score - CVSS base score
56
+ * @returns FIRST severity band label
57
+ */
58
+ declare function cvssScoreToSeverity(score: number): 'none' | 'low' | 'medium' | 'high' | 'critical';
59
+ //#endregion
60
+ //#region src/cvss/index.d.ts
61
+ /**
62
+ * CVSS vector parsing and validation utilities.
63
+ *
64
+ * Supports CVSS 2.0 (legacy, no prefix), 3.0, 3.1, and 4.0 vector strings.
65
+ * Parsing is permissive: it splits any well-shaped key:value pair separated
66
+ * by `/`. Validation is strict per the FIRST grammar for each version.
67
+ *
68
+ * References:
69
+ * - CVSS 2.0: https://www.first.org/cvss/v2/guide
70
+ * - CVSS 3.1: https://www.first.org/cvss/v3.1/specification-document
71
+ * - CVSS 4.0: https://www.first.org/cvss/v4.0/specification-document
72
+ */
73
+ interface ParsedCvssVector {
74
+ /** "2.0" | "3.0" | "3.1" | "4.0" | "unknown" */
75
+ version: string;
76
+ /** Metric name -> metric value (e.g. "AV" -> "N"). Order-preserving via Map. */
77
+ metrics: Map<string, string>;
78
+ }
79
+ interface CvssValidationResult {
80
+ valid: boolean;
81
+ errors: string[];
82
+ }
83
+ /**
84
+ * Parse a CVSS vector string into version + metric map.
85
+ *
86
+ * Version detection rule: a leading `CVSS:X.Y` segment yields version "X.Y".
87
+ * Otherwise (legacy v2 vectors), version is "2.0". Malformed input yields
88
+ * "unknown" with an empty metric map; no exceptions are thrown.
89
+ *
90
+ * @param vector - CVSS vector string (e.g. "CVSS:3.1/AV:N/AC:L/...")
91
+ * @returns Parsed version + metric map
92
+ */
93
+ declare function parseCvssVector(vector: string): ParsedCvssVector;
94
+ /**
95
+ * Validate a CVSS vector string against the FIRST grammar for its version.
96
+ *
97
+ * Checks:
98
+ * - Version is one of the supported set (2.0, 3.0, 3.1, 4.0).
99
+ * - All required base metrics for the version are present.
100
+ * - All known metric values are within the allowed enum.
101
+ *
102
+ * Unknown metric keys are tolerated (forward-compat with future minor versions
103
+ * or scanner-specific extensions). To check a v2 vector without the CVSS:
104
+ * prefix, callers may pass version="2.0" explicitly; otherwise the parser
105
+ * defaults to "2.0" on prefix absence.
106
+ *
107
+ * @param vector - CVSS vector string
108
+ * @param version - Optional explicit version override (else inferred)
109
+ * @returns `{ valid, errors[] }` — never throws
110
+ */
111
+ declare function validateCvssVector(vector: string, version?: string): CvssValidationResult;
112
+ //#endregion
113
+ //#region src/cpe/index.d.ts
114
+ /**
115
+ * CPE 2.3 URI parser for HDF converters.
116
+ *
117
+ * Common Platform Enumeration (CPE) 2.3 URIs identify affected products.
118
+ * Container scanners (grype, twistlock, snyk) emit them in vulnerability
119
+ * findings. This module provides an accept-and-warn parser: any input that
120
+ * carries the `cpe:2.3:` prefix is best-effort parsed; deviations from the
121
+ * canonical 13-field form are reported via `warnings` rather than rejected.
122
+ *
123
+ * Canonical form:
124
+ * `cpe:2.3:part:vendor:product:version:update:edition:language:sw_edition:target_sw:target_hw:other`
125
+ *
126
+ * Reference: NIST IR 7695 (https://csrc.nist.gov/publications/detail/nistir/7695/final)
127
+ */
128
+ /** Valid CPE 2.3 `part` values. */
129
+ type CpePart = 'a' | 'o' | 'h' | '*';
130
+ /**
131
+ * Parsed CPE 2.3 URI.
132
+ *
133
+ * `warnings` collects any deviations from the canonical form (truncation,
134
+ * extra fields, unknown `part`). An empty array means the input was a strict
135
+ * 13-field, valid-part CPE.
136
+ */
137
+ interface ParsedCpe {
138
+ /** The original input string, unmodified. */
139
+ raw: string;
140
+ /** Application kind. `'a'` (app), `'o'` (OS), `'h'` (hardware), `'*'` (any). */
141
+ part: CpePart | string;
142
+ vendor: string;
143
+ product: string;
144
+ version: string;
145
+ update: string;
146
+ edition: string;
147
+ language: string;
148
+ swEdition: string;
149
+ targetSw: string;
150
+ targetHw: string;
151
+ other: string;
152
+ /** Deviation messages; empty when the input is a canonical 13-field CPE. */
153
+ warnings: string[];
154
+ }
155
+ /**
156
+ * Parse a CPE 2.3 URI.
157
+ *
158
+ * Accept-and-warn semantics:
159
+ * - Input missing the `cpe:2.3:` prefix → returns `null`.
160
+ * - Input with the prefix but fewer than 11 product fields → fields are
161
+ * padded with `"*"` and a `truncated:` warning is added.
162
+ * - Input with more than 11 product fields → extras are dropped and an
163
+ * `"extra fields ignored"` warning is added.
164
+ * - Unknown `part` value → kept as-is in the result, `"unknown part: X"`
165
+ * warning is added.
166
+ * - `\:` and `\\` escapes inside fields are honored during splitting and
167
+ * unescaped in the returned field values.
168
+ *
169
+ * @param cpeUri - CPE 2.3 URI string
170
+ * @returns Parsed CPE with warnings, or `null` if input lacks the prefix
171
+ *
172
+ * @example
173
+ * ```typescript
174
+ * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k:*:*:*:*:*:*:*');
175
+ * // { part: 'a', vendor: 'openssl', product: 'openssl', version: '1.1.1k', ... warnings: [] }
176
+ *
177
+ * parseCpe('cpe:2.3:a:openssl:openssl:1.1.1k');
178
+ * // truncated input → padded with '*', warnings: ['truncated: expected 13 colon-separated fields, got 6']
179
+ *
180
+ * parseCpe('openssl:1.1.1k'); // null — no prefix
181
+ * ```
182
+ */
183
+ declare function parseCpe(cpeUri: string): ParsedCpe | null;
184
+ //#endregion
185
+ //#region src/purl/index.d.ts
186
+ /**
187
+ * Package URL (PURL) parser for SBOM and CVE-scanner ingestion.
188
+ *
189
+ * PURL is the canonical package identifier used by CycloneDX, SPDX, OSV,
190
+ * GitHub Advisory Database, and most modern scanners. Spec:
191
+ * https://github.com/package-url/purl-spec/blob/master/PURL-SPECIFICATION.rst
192
+ *
193
+ * Grammar: `pkg:type/namespace/name@version?qualifiers#subpath`
194
+ *
195
+ * This parser is intentionally accept-and-warn. Scanners occasionally emit
196
+ * slightly malformed PURLs (qualifier with no `=`, missing name, etc.) and
197
+ * we'd rather record a warning than fail ingestion.
198
+ */
199
+ /**
200
+ * Result of parsing a PURL string.
201
+ */
202
+ interface ParsedPurl {
203
+ /** Original input string, unmodified. */
204
+ raw: string;
205
+ /** Package type (e.g., "npm", "rpm", "pypi", "maven"). Lowercased. */
206
+ type: string;
207
+ /** Namespace segment (e.g., "redhat" for RPM, "org.apache..." for Maven). */
208
+ namespace: string | null;
209
+ /** Package name. Empty string (with a warning) if the input lacks one. */
210
+ name: string;
211
+ /** Version after the `@` separator. URL-decoded. */
212
+ version: string | null;
213
+ /** Qualifier key-value pairs from the `?` segment. */
214
+ qualifiers: Map<string, string>;
215
+ /** Subpath from the `#` fragment. */
216
+ subpath: string | null;
217
+ /** Deviations or oddities encountered during parsing. */
218
+ warnings: string[];
219
+ }
220
+ /**
221
+ * Parse a Package URL string.
222
+ *
223
+ * Returns `null` only when the input is missing the mandatory `pkg:` prefix
224
+ * or the type segment. Other deviations are surfaced via `warnings` on the
225
+ * returned object.
226
+ *
227
+ * @param purlStr - PURL string, e.g. `pkg:npm/lodash@4.17.21`
228
+ * @returns Parsed PURL or `null` if the input is unrecoverably malformed
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * const r = parsePurl('pkg:rpm/redhat/openssl@1.1.1k-7.el8_4?arch=x86_64');
233
+ * r?.type; // "rpm"
234
+ * r?.namespace; // "redhat"
235
+ * r?.name; // "openssl"
236
+ * r?.qualifiers.get('arch'); // "x86_64"
237
+ * ```
238
+ */
239
+ declare function parsePurl(purlStr: string): ParsedPurl | null;
41
240
  //#endregion
42
- export { type CsvBuildOptions, type CsvParseOptions, type HashAlgorithm, type HashEncoding, type HashOptions, type StringifyOptions, buildCsv, buildCsvArray, buildXml, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, generateHash, hashObject, impactToSeverity, isValidCsv, isValidJSON, isValidXml, parseCsv, parseCsvArray, parseJSON, parseTimestamp, parseXml, parseXmlWithArrays, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, sha256, sha512, stringifyJSON, stripHtml, verifyHash };
241
+ export { type CpePart, type CsvBuildOptions, type CsvParseOptions, type CvssValidationResult, type HashAlgorithm, type HashEncoding, type HashOptions, type ParsedCpe, type ParsedCvssVector, type ParsedPurl, type StringifyOptions, buildCsv, buildCsvArray, buildXml, cvssScoreToSeverity, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, formatTimestamp, formatTimestampSeconds, generateHash, hashObject, impactToSeverity, isValidCsv, isValidJSON, isValidXml, parseCpe, parseCsv, parseCsvArray, parseCvssVector, parseJSON, parsePurl, parseTimestamp, parseXml, parseXmlWithArrays, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, sha256, sha512, stringifyJSON, stripHtml, trimUtcFraction, validateCvssVector, verifyHash };
43
242
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/severity/index.ts"],"mappings":";;;;;;;;;;;;;;;;AAgCA;;;;AAA+C;AAC/C;;;;AAAiD;AACjD;;iBAFgB,gBAAA,CAAiB,QAAc;AAAA,iBAC/B,gBAAA,CAAiB,QAAgB;AAAA,iBACjC,gBAAA,CAAiB,QAAuB;AAaxD;;;;AAA6C;AAC7C;AADA,iBAAgB,gBAAA,CAAiB,MAAY;AAAA,iBAC7B,gBAAA,CAAiB,MAAc;AAAA,iBAC/B,gBAAA,CAAiB,MAAqB"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/severity/index.ts","../src/cvss/index.ts","../src/cpe/index.ts","../src/purl/index.ts"],"mappings":";;;;;;;;;;;;;;;;AAgCA;;;;AAA+C;AAC/C;;;;AAAiD;AACjD;;iBAFgB,gBAAA,CAAiB,QAAc;AAAA,iBAC/B,gBAAA,CAAiB,QAAgB;AAAA,iBACjC,gBAAA,CAAiB,QAAuB;AAaxD;;;;AAA6C;AAC7C;AADA,iBAAgB,gBAAA,CAAiB,MAAY;AAAA,iBAC7B,gBAAA,CAAiB,MAAc;AAAA,iBAC/B,gBAAA,CAAiB,MAAqB;AADP;AAC/C;;;;AAAsD;AA2BtD;;;;AACe;;;;AChEf;;;ADmC+C,iBA4B/B,mBAAA,CACd,KAAa;;;;;;;;;;;AA7Cf;;;;UCnBiB,gBAAA;EDoBD;EClBd,OAAA;;EAEA,OAAA,EAAS,GAAG;AAAA;AAAA,UAGG,oBAAA;EACf,KAAA;EACA,MAAM;AAAA;ADYgD;AAaxD;;;;AAA6C;AAC7C;;;;AAdwD,iBC+HxC,eAAA,CAAgB,MAAA,WAAiB,gBAAgB;ADhHjE;;;;AAAsD;AA2BtD;;;;AACe;;;;AChEf;;;;ADoCA,iBC4KgB,kBAAA,CACd,MAAA,UACA,OAAA,YACC,oBAAoB;;;;;;;;;;;ADhMvB;;;;AAA+C;AAC/C;;KEjBY,OAAA;;AFiBqC;AACjD;;;;AAAwD;UETvC,SAAA;EFsBe;EEpB9B,GAAA;EFoB2C;EElB3C,IAAA,EAAM,OAAO;EACb,MAAA;EACA,OAAA;EACA,OAAA;EACA,MAAA;EACA,OAAA;EACA,QAAA;EACA,SAAA;EACA,QAAA;EACA,QAAA;EACA,KAAA;EFqCc;EEnCd,QAAA;AAAA;;AFoCa;;;;AChEf;;;;;;;;AAIc;AAGd;;;;AAEQ;AA2IR;;;;AAAiE;AA4DjE;;;;iBC7GgB,QAAA,CAAS,MAAA,WAAiB,SAAS;;;;;;;;;;;AFhFnD;;;;AAA+C;AAC/C;;;AAAA,UGhBiB,UAAA;EHgBgC;EGd/C,GAAA;EHe8B;EGb9B,IAAA;EHasD;EGXtD,SAAA;EHwBc;EGtBd,IAAA;;EAEA,OAAA;EHoB2C;EGlB3C,UAAA,EAAY,GAAG;EHmBe;EGjB9B,OAAA;EHiB6C;EGf7C,QAAA;AAAA;;;;AHgBoD;AA2BtD;;;;AACe;;;;AChEf;;;;;;;iBE4CgB,SAAA,CAAU,OAAA,WAAkB,UAAU"}