From 4c074562d87ffda4a50ae41aeef95f943f9e4b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Sat, 4 Apr 2026 12:55:10 +0000 Subject: [PATCH 01/18] parse file a stream --- docs/ASYNC.md | 87 ++++++++++++ index.d.ts | 11 ++ index.js | 36 +++++ src/csvToJsonAsync.js | 256 ++++++++++++++++++++++++++++++++++-- test/csvToJsonAsync.spec.js | 81 ++++++++++++ 5 files changed, 462 insertions(+), 9 deletions(-) diff --git a/docs/ASYNC.md b/docs/ASYNC.md index c48700e..de26cb5 100644 --- a/docs/ASYNC.md +++ b/docs/ASYNC.md @@ -7,6 +7,8 @@ Promise-based async/await API for modern Node.js applications. Perfect for handl - [File Operations](#file-operations) - [Working with Raw CSV Data](#working-with-raw-csv-data) - [Processing Large Files](#processing-large-files) +- [Stream Processing](#stream-processing) +- [File Streaming](#file-streaming) - [Batch Processing](#batch-processing) - [Error Handling](#error-handling) - [Method Chaining](#method-chaining) @@ -158,6 +160,91 @@ async function processChunk(records) { } ``` +## Stream Processing + +For true memory-efficient processing of large CSV files, use the stream API which processes data in chunks without loading the entire file into memory. + +### Basic Stream Usage + +```js +const fs = require('fs'); +const csvToJson = require('convert-csv-to-json'); + +async function processLargeCSV(filePath) { + const stream = fs.createReadStream(filePath); + const jsonData = await csvToJson.getJsonFromStreamAsync(stream); + + console.log(`Processed ${jsonData.length} records`); + return jsonData; +} + +// Usage +const data = await processLargeCSV('large-dataset.csv'); +``` + +### Stream with Configuration + +```js +const fs = require('fs'); +const csvToJson = require('convert-csv-to-json'); + +async function processConfiguredStream(filePath) { + const stream = fs.createReadStream(filePath, { encoding: 'utf8' }); + + const jsonData = await csvToJson + .fieldDelimiter(';') + .supportQuotedField(true) + .getJsonFromStreamAsync(stream); + + return jsonData; +} +``` + +### Stream from Other Sources + +```js +const { Readable } = require('stream'); +const csvToJson = require('convert-csv-to-json'); + +// Create a stream from a string +function createCSVStream(csvString) { + const stream = new Readable(); + stream.push(csvString); + stream.push(null); // End the stream + return stream; +} + +async function processStringAsStream() { + const csvData = 'name,age\nAlice,30\nBob,25'; + const stream = createCSVStream(csvData); + + const json = await csvToJson.getJsonFromStreamAsync(stream); + console.log(json); + // Output: [{ name: 'Alice', age: '30' }, { name: 'Bob', age: '25' }] +} +``` + +### File Streaming + +For the most efficient processing of large CSV files, use the built-in file streaming API which handles all the complexity of chunked reading and parsing: + +```js +const csvToJson = require('convert-csv-to-json'); + +async function processLargeCSV(filePath) { + const jsonData = await csvToJson + .fieldDelimiter(';') + .supportQuotedField(true) + .getJsonFromFileStreamingAsync(filePath); + + console.log(`Processed ${jsonData.length} records`); + return jsonData; +} + +// Usage +const data = await processLargeCSV('large-dataset.csv'); +``` + ## Batch Processing ### Sequential Processing diff --git a/index.d.ts b/index.d.ts index 5cdfcc4..d5c7b48 100644 --- a/index.d.ts +++ b/index.d.ts @@ -104,6 +104,17 @@ declare module 'convert-csv-to-json' { */ getJsonFromCsvAsync(inputFileNameOrCsv: string, options?: { raw?: boolean }): Promise; + /** + * Parse CSV from a Readable stream and return parsed data as JSON array + * Processes data in chunks for memory-efficient handling of large files + */ + getJsonFromStreamAsync(stream: NodeJS.ReadableStream): Promise; + + /** + * Parse CSV from a file path using streaming for memory-efficient processing + */ + getJsonFromFileStreamingAsync(filePath: string): Promise; + csvStringToJson(csvString: string): any[]; /** diff --git a/index.js b/index.js index 46ed3ac..fdb7c2b 100644 --- a/index.js +++ b/index.js @@ -253,6 +253,36 @@ exports.getJsonFromCsv = function(inputFileName) { */ const csvToJsonAsync = require('./src/csvToJsonAsync'); +/** + * Parse CSV from a Readable stream and return parsed data as JSON array + * Processes data in chunks for memory-efficient handling of large files + * @param {Readable} stream - Node.js Readable stream containing CSV data + * @returns {Promise>} Promise resolving to array of objects representing CSV rows + * @throws {InputValidationError} If stream is invalid + * @throws {CsvFormatError} If CSV is malformed + * @category 1-Core API + * @example + * const fs = require('fs'); + * const csvToJson = require('convert-csv-to-json'); + * const stream = fs.createReadStream('large.csv'); + * const data = await csvToJson.getJsonFromStreamAsync(stream); + * console.log(data); + */ + +/** + * Parse CSV from a file path using streaming for memory-efficient processing + * @param {string} filePath - Path to the CSV file + * @returns {Promise>} Promise resolving to array of objects representing CSV rows + * @throws {InputValidationError} If filePath is invalid + * @throws {FileOperationError} If file cannot be read + * @throws {CsvFormatError} If CSV is malformed + * @category 1-Core API + * @example + * const csvToJson = require('convert-csv-to-json'); + * const data = await csvToJson.getJsonFromFileStreamingAsync('large.csv'); + * console.log(data); + */ + // Re-export all async API methods Object.assign(exports, { getJsonFromCsvAsync: function(input, options) { @@ -266,6 +296,12 @@ Object.assign(exports, { }, generateJsonFileFromCsvAsync: function(input, output) { return csvToJsonAsync.generateJsonFileFromCsv(input, output); + }, + getJsonFromStreamAsync: function(stream) { + return csvToJsonAsync.getJsonFromStreamAsync(stream); + }, + getJsonFromFileStreamingAsync: function(filePath) { + return csvToJsonAsync.getJsonFromFileStreamingAsync(filePath); } }); diff --git a/src/csvToJsonAsync.js b/src/csvToJsonAsync.js index 0dc317a..1b5033f 100644 --- a/src/csvToJsonAsync.js +++ b/src/csvToJsonAsync.js @@ -2,8 +2,14 @@ 'use strict'; const fileUtils = require('./util/fileUtils'); +const stringUtils = require('./util/stringUtils'); const csvToJson = require('./csvToJson'); -const { InputValidationError } = require('./util/errors'); +const { InputValidationError, CsvFormatError } = require('./util/errors'); + +const QUOTE_CHAR = '"'; +const CRLF = '\r\n'; +const LF = '\n'; +const CR = '\r'; /** * Asynchronous CSV to JSON converter @@ -177,18 +183,250 @@ class CsvToJsonAsync { } /** - * Parse CSV string to stringified JSON (async) - * @param {string} csvString - CSV content as string - * @returns {Promise} JSON stringified array of objects + * Parse CSV from a Readable stream and return parsed data as JSON array + * Processes data in chunks for memory-efficient handling of large files + * @param {Readable} stream - Node.js Readable stream containing CSV data + * @returns {Promise>} Promise resolving to array of objects representing CSV rows + * @throws {InputValidationError} If stream is invalid * @throws {CsvFormatError} If CSV is malformed * @example + * const fs = require('fs'); * const csvToJson = require('convert-csv-to-json'); - * const jsonString = await csvToJson.csvStringToJsonStringifiedAsync('name,age\nAlice,30'); - * console.log(jsonString); + * const stream = fs.createReadStream('large.csv'); + * const data = await csvToJson.getJsonFromStreamAsync(stream); + * console.log(data); */ - async csvStringToJsonStringifiedAsync(csvString) { - const json = await this.csvStringToJsonAsync(csvString); - return JSON.stringify(json, undefined, 1); + async getJsonFromStreamAsync(stream) { + if (!stream || typeof stream.pipe !== 'function') { + throw new InputValidationError( + 'stream', + 'Readable stream', + typeof stream, + 'Provide a valid Node.js Readable stream.' + ); + } + + return new Promise((resolve, reject) => { + const chunks = []; + let buffer = ''; + let insideQuotes = false; + let headers = null; + let headerIndex = this.csvToJson.getIndexHeader(); + let jsonResult = []; + let recordIndex = 0; + + stream.on('data', (chunk) => { + buffer += chunk.toString(); + + // Process complete records from buffer + const records = this._parseRecordsFromBuffer(buffer, insideQuotes); + buffer = records.remainingBuffer; + insideQuotes = records.insideQuotes; + + // Process each complete record + for (const record of records.completeRecords) { + try { + if (headers === null) { + // Try to find headers + const fields = this.csvToJson.isSupportQuotedField + ? this.csvToJson.split(record) + : record.split(this.csvToJson.delimiter || ','); + + if (stringUtils.hasContent(fields)) { + headers = fields; + continue; + } + } else { + // Parse data row + const fields = this.csvToJson.isSupportQuotedField + ? this.csvToJson.split(record) + : record.split(this.csvToJson.delimiter || ','); + + if (stringUtils.hasContent(fields)) { + let row = this.csvToJson.buildJsonResult(headers, fields); + + // Apply row mapper if defined + if (this.csvToJson.rowMapper) { + row = this.csvToJson.rowMapper(row, recordIndex); + recordIndex++; + if (row != null) { + jsonResult.push(row); + } + } else { + jsonResult.push(row); + recordIndex++; + } + } + } + } catch (error) { + reject(error); + return; + } + } + }); + + stream.on('end', () => { + try { + // Process any remaining buffer + if (buffer.length > 0) { + if (insideQuotes) { + throw CsvFormatError.mismatchedQuotes('CSV stream'); + } + + const records = this._parseRecordsFromBuffer(buffer + '\n', false); + for (const record of records.completeRecords) { + if (headers === null) { + const fields = this.csvToJson.isSupportQuotedField + ? this.csvToJson.split(record) + : record.split(this.csvToJson.delimiter || ','); + + if (stringUtils.hasContent(fields)) { + headers = fields; + } + } else { + const fields = this.csvToJson.isSupportQuotedField + ? this.csvToJson.split(record) + : record.split(this.csvToJson.delimiter || ','); + + if (stringUtils.hasContent(fields)) { + let row = this.csvToJson.buildJsonResult(headers, fields); + + if (this.csvToJson.rowMapper) { + row = this.csvToJson.rowMapper(row, recordIndex); + recordIndex++; + if (row != null) { + jsonResult.push(row); + } + } else { + jsonResult.push(row); + recordIndex++; + } + } + } + } + } + + // If no headers found and no data, return empty array (empty stream) + if (!headers && jsonResult.length === 0) { + resolve([]); + return; + } + + if (!headers) { + throw CsvFormatError.missingHeader(); + } + + resolve(jsonResult); + } catch (error) { + reject(error); + } + }); + + stream.on('error', (error) => { + reject(new FileOperationError(`Stream error: ${error.message}`)); + }); + }); + } + + /** + * Parse complete records from buffer, handling quoted fields across chunks + * @param {string} buffer - Current buffer content + * @param {boolean} insideQuotes - Whether we're currently inside quotes from previous chunk + * @returns {object} Object with completeRecords array and remaining buffer/quote state + * @private + */ + _parseRecordsFromBuffer(buffer, insideQuotes) { + const completeRecords = []; + let currentRecord = ''; + let i = 0; + + while (i < buffer.length) { + const char = buffer[i]; + + // Handle quote characters + if (char === QUOTE_CHAR) { + if (insideQuotes && i + 1 < buffer.length && buffer[i + 1] === QUOTE_CHAR) { + // Escaped quote: two consecutive quotes = single quote representation + currentRecord += QUOTE_CHAR + QUOTE_CHAR; + i += 2; + } else { + // Toggle quote state + insideQuotes = !insideQuotes; + currentRecord += char; + i++; + } + continue; + } + + // Handle line endings (only outside quoted fields) + if (!insideQuotes) { + let lineEndingLength = this._getLineEndingLength(buffer, i); + if (lineEndingLength > 0) { + completeRecords.push(currentRecord); + currentRecord = ''; + i += lineEndingLength; + continue; + } + } + + // Regular character + currentRecord += char; + i++; + } + + return { + completeRecords, + remainingBuffer: currentRecord, + insideQuotes + }; + } + + /** + * Get the length of line ending at current position + * @param {string} content - Content to check + * @param {number} index - Current index + * @returns {number} Length of line ending + * @private + */ + _getLineEndingLength(content, index) { + if (content.slice(index, index + 2) === CRLF) { + return 2; + } + if (content[index] === LF) { + return 1; + } + if (content[index] === CR && content[index + 1] !== LF) { + return 1; + } + return 0; + } + + /** + * Parse CSV from a file path using streaming for memory-efficient processing + * @param {string} filePath - Path to the CSV file + * @returns {Promise>} Promise resolving to array of objects representing CSV rows + * @throws {InputValidationError} If filePath is invalid + * @throws {FileOperationError} If file cannot be read + * @throws {CsvFormatError} If CSV is malformed + * @example + * const csvToJson = require('convert-csv-to-json'); + * const data = await csvToJson.getJsonFromFileStreamingAsync('large.csv'); + * console.log(data); + */ + async getJsonFromFileStreamingAsync(filePath) { + if (!filePath || typeof filePath !== 'string') { + throw new InputValidationError( + 'filePath', + 'string (file path)', + typeof filePath, + 'Provide a valid file path as a string.' + ); + } + + const fs = require('fs'); + const encoding = typeof this.csvToJson.encoding === 'string' ? this.csvToJson.encoding : 'utf8'; + const stream = fs.createReadStream(filePath, { encoding }); + return this.getJsonFromStreamAsync(stream); } } diff --git a/test/csvToJsonAsync.spec.js b/test/csvToJsonAsync.spec.js index 007270a..4b07fe6 100644 --- a/test/csvToJsonAsync.spec.js +++ b/test/csvToJsonAsync.spec.js @@ -103,4 +103,85 @@ describe('Async API testing', () => { ]); }); }); + + describe('getJsonFromStreamAsync()', () => { + const fs = require('fs'); + const { Readable } = require('stream'); + + beforeEach(() => { + // Reset configurations for stream tests + csvToJsonAsync.fieldDelimiter(','); + csvToJsonAsync.supportQuotedField(false); + csvToJsonAsync.formatValueByType(true); + csvToJsonAsync.trimHeaderFieldWhiteSpace(false); + csvToJsonAsync.indexHeader(0); + }); + + it('should parse CSV from a readable stream', () => { + const stream = fs.createReadStream('test/resource/input_example.csv'); + return csvToJsonAsync.fieldDelimiter(';') + .getJsonFromStreamAsync(stream) + .then(result => { + assert.ok(Array.isArray(result)); + assert.ok(result.length > 0); + assert.ok(result[0].hasOwnProperty('firstName')); + assert.ok(result[0].hasOwnProperty('lastName')); + assert.strictEqual(result[0].firstName, 'Constantin'); + }); + }); + + it('should handle quoted fields in streams', () => { + const csvData = 'name,description\n"John","A person with a ""quote"" in description"\n"Jane","Simple description"'; + const stream = new Readable(); + stream.push(csvData); + stream.push(null); // End the stream + + return csvToJsonAsync.supportQuotedField(true) + .getJsonFromStreamAsync(stream) + .then(result => { + assert.ok(Array.isArray(result)); + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0].name, 'John'); + assert.strictEqual(result[0].description, 'A person with a "quote" in description'); + assert.strictEqual(result[1].name, 'Jane'); + assert.strictEqual(result[1].description, 'Simple description'); + }); + }); + + it('should reject when stream is not provided', () => { + return csvToJsonAsync.getJsonFromStreamAsync(null) + .then(() => { throw new Error('Should reject'); }) + .catch(err => assert.ok(err instanceof Error)); + }); + + it('should handle empty stream', () => { + const stream = new Readable(); + stream.push(''); + stream.push(null); + + return csvToJsonAsync.getJsonFromStreamAsync(stream) + .then(result => { + assert.ok(Array.isArray(result)); + assert.strictEqual(result.length, 0); + }); + }); + + it('should parse CSV from a file path using streaming', () => { + return csvToJsonAsync.fieldDelimiter(';') + .getJsonFromFileStreamingAsync('test/resource/input_example.csv') + .then(result => { + assert.ok(Array.isArray(result)); + assert.ok(result.length > 0); + assert.ok(result[0].hasOwnProperty('firstName')); + assert.ok(result[0].hasOwnProperty('lastName')); + assert.strictEqual(result[0].firstName, 'Constantin'); + }); + }); + + it('should reject when file path is invalid', () => { + return csvToJsonAsync.getJsonFromFileStreamingAsync(null) + .then(() => { throw new Error('Should reject'); }) + .catch(err => assert.ok(err instanceof Error)); + }); + }); }); \ No newline at end of file From 0a496c054d7b9ee2294830a8535c38ce5499d824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:02:23 +0000 Subject: [PATCH 02/18] small refactoring --- README.md | 45 ++++++- docs/ASYNC.md | 21 ++++ index.js | 2 +- src/csvToJsonAsync.js | 209 ++++---------------------------- src/streamProcessor.js | 267 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 354 insertions(+), 190 deletions(-) create mode 100644 src/streamProcessor.js diff --git a/README.md b/README.md index 838a650..af0ffea 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,14 @@ ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) > -Convert CSV files to JSON with **no dependencies**. Supports Node.js (Sync & Async), and Browser environments with full RFC 4180 compliance. +Convert CSV files to JSON with **no dependencies**. Supports Node.js (Sync & Async), and Browser environments with full RFC 4180 compliance. **Memory-efficient streaming** for processing large files without loading them entirely into memory. ## Overview Transform CSV data into JSON with a simple, chainable API. Choose your implementation style: - **[Synchronous API](docs/SYNC.md)** - Blocking operations for simple workflows -- **[Asynchronous API](docs/ASYNC.md)** - Promise-based for modern async/await patterns +- **[Asynchronous API](docs/ASYNC.md)** - Promise-based for modern async/await patterns with **memory-efficient streaming** for large files - **[Browser API](docs/BROWSER.md)** - Client-side CSV parsing for web applications ## Demo and JSDoc @@ -40,7 +40,7 @@ Transform CSV data into JSON with a simple, chainable API. Choose your implement ✅ **Full TypeScript Support** - Included type definitions for all APIs ✅ **Flexible Configuration** - Custom delimiters, encoding, trimming, and more ✅ **Method Chaining** - Fluent API for readable code -✅ **Large File Support** - Stream processing for memory-efficient handling +✅ **Memory-Efficient Streaming** - Process large files without loading them entirely into memory ✅ **Comprehensive Error Handling** - Detailed, actionable error messages with solutions (see [ERROR_HANDLING.md](docs/ERROR_HANDLING.md)) ## RFC 4180 Standard @@ -150,6 +150,8 @@ All APIs (Sync, Async and Browser) support the same configuration methods: - `trimHeaderFieldWhiteSpace(bool)` - Remove spaces from headers - `parseSubArray(delim, sep)` - Parse delimited arrays - `mapRows(fn)` - Transform, filter, or enrich each row +- `getJsonFromStreamAsync(stream)` - Process CSV from Readable streams +- `getJsonFromFileStreamingAsync(filePath)` - Stream processing for large files - `utf8Encoding()`, `latin1Encoding()`, etc. - Set file encoding ### Examples @@ -233,6 +235,43 @@ csvToJson.latin1Encoding().getJsonFromCsv('data.csv'); csvToJson.customEncoding('ucs2').getJsonFromCsv('data.csv'); ``` +#### `getJsonFromStreamAsync(stream)` - Process CSV from Readable streams +```js +const fs = require('fs'); +const csvToJson = require('convert-csv-to-json'); + +// Process large files without loading them entirely into memory +async function processLargeCSV() { + const stream = fs.createReadStream('large-dataset.csv'); + const jsonData = await csvToJson + .fieldDelimiter(';') + .supportQuotedField(true) + .getJsonFromStreamAsync(stream); + + console.log(`Processed ${jsonData.length} records efficiently`); + return jsonData; +} +``` + +#### `getJsonFromFileStreamingAsync(filePath)` - Stream processing for large files +```js +const csvToJson = require('convert-csv-to-json'); + +// Most efficient way to process large CSV files +async function processLargeCSV(filePath) { + const jsonData = await csvToJson + .fieldDelimiter(',') + .formatValueByType() + .getJsonFromFileStreamingAsync(filePath); + + console.log(`Streamed and processed ${jsonData.length} records`); + return jsonData; +} + +// Usage - handles files of any size without memory constraints +const data = await processLargeCSV('massive-dataset.csv'); +``` + See [SYNC.md](docs/SYNC.md), [ASYNC.md](docs/ASYNC.md) or [BROWSER.md](docs/BROWSER.md) for complete configuration details. ## Example: Complete Workflow diff --git a/docs/ASYNC.md b/docs/ASYNC.md index de26cb5..46d78a9 100644 --- a/docs/ASYNC.md +++ b/docs/ASYNC.md @@ -200,6 +200,27 @@ async function processConfiguredStream(filePath) { } ``` +### File Streaming with getJsonFromFileStreamingAsync + +For simplified file streaming without manually creating streams, use `getJsonFromFileStreamingAsync`: + +```js +const csvToJson = require('convert-csv-to-json'); + +async function processLargeCSV(filePath) { + const jsonData = await csvToJson + .fieldDelimiter(';') + .supportQuotedField(true) + .getJsonFromFileStreamingAsync(filePath); + + console.log(`Processed ${jsonData.length} records efficiently`); + return jsonData; +} + +// Usage - processes large files without loading them entirely into memory +const data = await processLargeCSV('large-dataset.csv'); +``` + ### Stream from Other Sources ```js diff --git a/index.js b/index.js index fdb7c2b..7d815c2 100644 --- a/index.js +++ b/index.js @@ -256,7 +256,7 @@ const csvToJsonAsync = require('./src/csvToJsonAsync'); /** * Parse CSV from a Readable stream and return parsed data as JSON array * Processes data in chunks for memory-efficient handling of large files - * @param {Readable} stream - Node.js Readable stream containing CSV data + * @param {object} stream - Node.js Readable stream containing CSV data * @returns {Promise>} Promise resolving to array of objects representing CSV rows * @throws {InputValidationError} If stream is invalid * @throws {CsvFormatError} If CSV is malformed diff --git a/src/csvToJsonAsync.js b/src/csvToJsonAsync.js index 1b5033f..8e1379e 100644 --- a/src/csvToJsonAsync.js +++ b/src/csvToJsonAsync.js @@ -2,14 +2,9 @@ 'use strict'; const fileUtils = require('./util/fileUtils'); -const stringUtils = require('./util/stringUtils'); const csvToJson = require('./csvToJson'); -const { InputValidationError, CsvFormatError } = require('./util/errors'); - -const QUOTE_CHAR = '"'; -const CRLF = '\r\n'; -const LF = '\n'; -const CR = '\r'; +const { InputValidationError } = require('./util/errors'); +const StreamProcessor = require('./streamProcessor'); /** * Asynchronous CSV to JSON converter @@ -185,7 +180,7 @@ class CsvToJsonAsync { /** * Parse CSV from a Readable stream and return parsed data as JSON array * Processes data in chunks for memory-efficient handling of large files - * @param {Readable} stream - Node.js Readable stream containing CSV data + * @param {object} stream - Node.js Readable stream containing CSV data * @returns {Promise>} Promise resolving to array of objects representing CSV rows * @throws {InputValidationError} If stream is invalid * @throws {CsvFormatError} If CSV is malformed @@ -197,126 +192,24 @@ class CsvToJsonAsync { * console.log(data); */ async getJsonFromStreamAsync(stream) { - if (!stream || typeof stream.pipe !== 'function') { - throw new InputValidationError( - 'stream', - 'Readable stream', - typeof stream, - 'Provide a valid Node.js Readable stream.' - ); - } + this._validateStream(stream); return new Promise((resolve, reject) => { - const chunks = []; - let buffer = ''; - let insideQuotes = false; - let headers = null; - let headerIndex = this.csvToJson.getIndexHeader(); - let jsonResult = []; - let recordIndex = 0; + const streamProcessor = new StreamProcessor(this.csvToJson); stream.on('data', (chunk) => { - buffer += chunk.toString(); - - // Process complete records from buffer - const records = this._parseRecordsFromBuffer(buffer, insideQuotes); - buffer = records.remainingBuffer; - insideQuotes = records.insideQuotes; - - // Process each complete record - for (const record of records.completeRecords) { - try { - if (headers === null) { - // Try to find headers - const fields = this.csvToJson.isSupportQuotedField - ? this.csvToJson.split(record) - : record.split(this.csvToJson.delimiter || ','); - - if (stringUtils.hasContent(fields)) { - headers = fields; - continue; - } - } else { - // Parse data row - const fields = this.csvToJson.isSupportQuotedField - ? this.csvToJson.split(record) - : record.split(this.csvToJson.delimiter || ','); - - if (stringUtils.hasContent(fields)) { - let row = this.csvToJson.buildJsonResult(headers, fields); - - // Apply row mapper if defined - if (this.csvToJson.rowMapper) { - row = this.csvToJson.rowMapper(row, recordIndex); - recordIndex++; - if (row != null) { - jsonResult.push(row); - } - } else { - jsonResult.push(row); - recordIndex++; - } - } - } - } catch (error) { - reject(error); - return; - } + try { + streamProcessor.processChunk(chunk); + } catch (error) { + reject(error); } }); stream.on('end', () => { try { - // Process any remaining buffer - if (buffer.length > 0) { - if (insideQuotes) { - throw CsvFormatError.mismatchedQuotes('CSV stream'); - } - - const records = this._parseRecordsFromBuffer(buffer + '\n', false); - for (const record of records.completeRecords) { - if (headers === null) { - const fields = this.csvToJson.isSupportQuotedField - ? this.csvToJson.split(record) - : record.split(this.csvToJson.delimiter || ','); - - if (stringUtils.hasContent(fields)) { - headers = fields; - } - } else { - const fields = this.csvToJson.isSupportQuotedField - ? this.csvToJson.split(record) - : record.split(this.csvToJson.delimiter || ','); - - if (stringUtils.hasContent(fields)) { - let row = this.csvToJson.buildJsonResult(headers, fields); - - if (this.csvToJson.rowMapper) { - row = this.csvToJson.rowMapper(row, recordIndex); - recordIndex++; - if (row != null) { - jsonResult.push(row); - } - } else { - jsonResult.push(row); - recordIndex++; - } - } - } - } - } - - // If no headers found and no data, return empty array (empty stream) - if (!headers && jsonResult.length === 0) { - resolve([]); - return; - } - - if (!headers) { - throw CsvFormatError.missingHeader(); - } - - resolve(jsonResult); + streamProcessor.finalizeProcessing(); + const result = streamProcessor.getResult(); + resolve(result); } catch (error) { reject(error); } @@ -329,76 +222,20 @@ class CsvToJsonAsync { } /** - * Parse complete records from buffer, handling quoted fields across chunks - * @param {string} buffer - Current buffer content - * @param {boolean} insideQuotes - Whether we're currently inside quotes from previous chunk - * @returns {object} Object with completeRecords array and remaining buffer/quote state - * @private - */ - _parseRecordsFromBuffer(buffer, insideQuotes) { - const completeRecords = []; - let currentRecord = ''; - let i = 0; - - while (i < buffer.length) { - const char = buffer[i]; - - // Handle quote characters - if (char === QUOTE_CHAR) { - if (insideQuotes && i + 1 < buffer.length && buffer[i + 1] === QUOTE_CHAR) { - // Escaped quote: two consecutive quotes = single quote representation - currentRecord += QUOTE_CHAR + QUOTE_CHAR; - i += 2; - } else { - // Toggle quote state - insideQuotes = !insideQuotes; - currentRecord += char; - i++; - } - continue; - } - - // Handle line endings (only outside quoted fields) - if (!insideQuotes) { - let lineEndingLength = this._getLineEndingLength(buffer, i); - if (lineEndingLength > 0) { - completeRecords.push(currentRecord); - currentRecord = ''; - i += lineEndingLength; - continue; - } - } - - // Regular character - currentRecord += char; - i++; - } - - return { - completeRecords, - remainingBuffer: currentRecord, - insideQuotes - }; - } - - /** - * Get the length of line ending at current position - * @param {string} content - Content to check - * @param {number} index - Current index - * @returns {number} Length of line ending + * Validate that the provided stream is a valid Readable stream + * @param {object} stream - The stream to validate + * @throws {InputValidationError} If stream is invalid * @private */ - _getLineEndingLength(content, index) { - if (content.slice(index, index + 2) === CRLF) { - return 2; - } - if (content[index] === LF) { - return 1; - } - if (content[index] === CR && content[index + 1] !== LF) { - return 1; + _validateStream(stream) { + if (!stream || typeof stream.pipe !== 'function') { + throw new InputValidationError( + 'stream', + 'Readable stream', + typeof stream, + 'Provide a valid Node.js Readable stream.' + ); } - return 0; } /** diff --git a/src/streamProcessor.js b/src/streamProcessor.js new file mode 100644 index 0000000..968aaaf --- /dev/null +++ b/src/streamProcessor.js @@ -0,0 +1,267 @@ +/* globals CsvFormatError */ +'use strict'; + +const stringUtils = require('./util/stringUtils'); + +const QUOTE_CHAR = '"'; +const CRLF = '\r\n'; +const LF = '\n'; +const CR = '\r'; + +/** + * Handles the processing of CSV data from a stream + * Encapsulates all stream processing logic following single responsibility principle + * @private + */ +class StreamProcessor { + /** + * Initialize the stream processor with CSV configuration + * @param {object} csvConfig - The CSV configuration object + */ + constructor(csvConfig) { + this.csvConfig = csvConfig; + this.buffer = ''; + this.isInsideQuotes = false; + this.headers = null; + this.headerRowIndex = csvConfig.getIndexHeader(); + this.currentRecordIndex = 0; + this.parsedRecords = []; + this.dataRowIndex = 0; + } + + /** + * Process a chunk of data from the stream + * @param {Buffer|string} chunk - The data chunk to process + */ + processChunk(chunk) { + this.buffer += chunk.toString(); + this._processCompleteRecords(); + } + + /** + * Finalize processing when the stream ends + */ + finalizeProcessing() { + this._processRemainingBuffer(); + this._validateProcessingResult(); + } + + /** + * Get the final processed result + * @returns {Array} Array of parsed JSON objects + */ + getResult() { + return this.parsedRecords; + } + + /** + * Process all complete records currently in the buffer + * @private + */ + _processCompleteRecords() { + const parseResult = this._parseRecordsFromBuffer(this.buffer, this.isInsideQuotes); + + this.buffer = parseResult.remainingBuffer; + this.isInsideQuotes = parseResult.isInsideQuotes; + + for (const record of parseResult.completeRecords) { + this._processRecord(record); + this.currentRecordIndex++; + } + } + + /** + * Process any remaining buffer content when stream ends + * @private + */ + _processRemainingBuffer() { + if (this.buffer.length > 0) { + if (this.isInsideQuotes) { + throw CsvFormatError.mismatchedQuotes('CSV stream'); + } + + const parseResult = this._parseRecordsFromBuffer(this.buffer + '\n', false); + + for (const record of parseResult.completeRecords) { + this._processRecord(record); + this.currentRecordIndex++; + } + } + } + + /** + * Process a single CSV record + * @param {string} record - The CSV record to process + * @private + */ + _processRecord(record) { + if (this.headers === null && this.currentRecordIndex === this.headerRowIndex) { + this._processHeaderRecord(record); + } else if (this.headers !== null) { + this._processDataRecord(record); + } + } + + /** + * Process a header record + * @param {string} record - The header record + * @private + */ + _processHeaderRecord(record) { + const headerFields = this._splitRecord(record); + if (stringUtils.hasContent(headerFields)) { + this.headers = headerFields; + } + } + + /** + * Process a data record + * @param {string} record - The data record + * @private + */ + _processDataRecord(record) { + const dataFields = this._splitRecord(record); + if (stringUtils.hasContent(dataFields)) { + const row = this.csvConfig.buildJsonResult(this.headers, dataFields); + const processedRow = this._applyRowMapper(row); + if (processedRow !== null) { + this.parsedRecords.push(processedRow); + } + } + } + + /** + * Apply row mapper function if configured + * @param {object} row - The parsed row object + * @returns {object|null} The processed row or null if filtered out + * @private + */ + _applyRowMapper(row) { + if (this.csvConfig.rowMapper) { + const mappedRow = this.csvConfig.rowMapper(row, this.dataRowIndex); + this.dataRowIndex++; + return mappedRow; + } + this.dataRowIndex++; + return row; + } + + /** + * Split a CSV record into fields based on configuration + * @param {string} record - The record to split + * @returns {string[]} Array of field values + * @private + */ + _splitRecord(record) { + if (this.csvConfig.isSupportQuotedField) { + return this.csvConfig.split(record); + } + return record.split(this.csvConfig.delimiter || ','); + } + + /** + * Parse complete records from buffer, handling quoted fields across chunks + * @param {string} buffer - Current buffer content + * @param {boolean} insideQuotes - Whether we're currently inside quotes + * @returns {object} Object with completeRecords array and remaining buffer/quote state + * @private + */ + _parseRecordsFromBuffer(buffer, insideQuotes) { + const completeRecords = []; + let currentRecord = ''; + let i = 0; + + while (i < buffer.length) { + const char = buffer[i]; + + if (char === QUOTE_CHAR) { + const escapedQuoteResult = this._handleEscapedQuote(buffer, i, insideQuotes); + if (escapedQuoteResult.wasEscaped) { + currentRecord += QUOTE_CHAR + QUOTE_CHAR; + i = escapedQuoteResult.newIndex; + continue; + } else { + insideQuotes = !insideQuotes; + } + } else if (!insideQuotes && this._isLineEnding(buffer, i)) { + const lineEndingLength = this._getLineEndingLength(buffer, i); + completeRecords.push(currentRecord); + currentRecord = ''; + i += lineEndingLength; + continue; + } + + currentRecord += char; + i++; + } + + return { + completeRecords, + remainingBuffer: currentRecord, + isInsideQuotes: insideQuotes + }; + } + + /** + * Handle escaped quotes in quoted fields + * @param {string} buffer - The buffer content + * @param {number} index - Current index in buffer + * @param {boolean} insideQuotes - Whether currently inside quotes + * @returns {object} Result indicating if quote was escaped and new index + * @private + */ + _handleEscapedQuote(buffer, index, insideQuotes) { + if (insideQuotes && index + 1 < buffer.length && buffer[index + 1] === QUOTE_CHAR) { + return { wasEscaped: true, newIndex: index + 2 }; + } + return { wasEscaped: false, newIndex: index + 1 }; + } + + /** + * Check if character at index is a line ending + * @param {string} buffer - The buffer content + * @param {number} index - Current index + * @returns {boolean} True if line ending + * @private + */ + _isLineEnding(buffer, index) { + return this._getLineEndingLength(buffer, index) > 0; + } + + /** + * Get the length of line ending at current position + * @param {string} content - Content to check + * @param {number} index - Current index + * @returns {number} Length of line ending + * @private + */ + _getLineEndingLength(content, index) { + if (content.slice(index, index + 2) === CRLF) { + return 2; + } + if (content[index] === LF) { + return 1; + } + if (content[index] === CR && content[index + 1] !== LF) { + return 1; + } + return 0; + } + + /** + * Validate the final processing result + * @private + */ + _validateProcessingResult() { + if (!this.headers && this.parsedRecords.length === 0) { + // Empty stream - this is OK + return; + } + + if (!this.headers) { + throw CsvFormatError.missingHeader(); + } + } +} + +module.exports = StreamProcessor; \ No newline at end of file From b72513e4e4c057bfaf187103a26dcc097b09801c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:32:02 +0000 Subject: [PATCH 03/18] imple browser readbleStream --- docs/BROWSER.md | 52 ++++++++++++ index.d.ts | 11 +++ src/browserApi.js | 171 ++++++++++++++++++++++++++++++++++++++++ src/util/errors.js | 18 +++++ test/browserApi.spec.js | 139 ++++++++++++++++++++++++++++++++ 5 files changed, 391 insertions(+) diff --git a/docs/BROWSER.md b/docs/BROWSER.md index 39011ce..72b09ed 100644 --- a/docs/BROWSER.md +++ b/docs/BROWSER.md @@ -6,6 +6,7 @@ Client-side CSV parsing for web browsers. Supports parsing CSV strings and file/ - [Basic Usage](#basic-usage) - [Parsing CSV Strings](#parsing-csv-strings) - [Parsing Files and Blobs](#parsing-files-and-blobs) +- [Streaming Large Files](#streaming-large-files) - [Configuration Options](#configuration-options) - [File Upload Examples](#file-upload-examples) - [TypeScript Support](#typescript-support) @@ -122,6 +123,57 @@ async function parseWithEncoding(file) { } ``` +## Streaming Large Files + +For memory-efficient processing of large CSV files in browsers, use the streaming API which processes data in chunks without loading the entire file into memory. + +### Stream from ReadableStream + +```js +const convert = require('convert-csv-to-json'); + +async function processStream(stream) { + const jsonData = await convert.browser + .fieldDelimiter(';') + .supportQuotedField(true) + .getJsonFromStreamAsync(stream); + + console.log(`Processed ${jsonData.length} records`); + return jsonData; +} + +// Usage with fetch +const response = await fetch('large-dataset.csv'); +const stream = response.body; +const data = await processStream(stream); +``` + +### Stream from File Object + +```js +const convert = require('convert-csv-to-json'); + +async function processLargeFile(file) { + const jsonData = await convert.browser + .fieldDelimiter(',') + .formatValueByType() + .getJsonFromFileStreamingAsync(file); + + console.log(`Streamed and processed ${jsonData.length} records`); + return jsonData; +} + +// Usage with file input +const fileInput = document.querySelector('#csvfile'); +fileInput.addEventListener('change', async (event) => { + const file = event.target.files[0]; + const data = await processLargeFile(file); + console.log(data); +}); +``` + +**Note:** Streaming requires modern browsers that support the `ReadableStream` API (Chrome 43+, Firefox 65+, Safari 10.1+). For older browsers, the method falls back to regular file parsing. + ## Configuration Options All configuration methods from the [Sync API](SYNC.md) are available: diff --git a/index.d.ts b/index.d.ts index d5c7b48..89f80ee 100644 --- a/index.d.ts +++ b/index.d.ts @@ -150,6 +150,17 @@ declare module 'convert-csv-to-json' { * Parse a File or Blob and return a Promise that resolves to the JSON array */ parseFile(file: Blob | File, options?: { encoding?: string }): Promise; + + /** + * Parse CSV from a ReadableStream and return parsed data as JSON array + */ + getJsonFromStreamAsync(stream: ReadableStream): Promise; + + /** + * Parse CSV from a File object using streaming for memory-efficient processing + */ + getJsonFromFileStreamingAsync(file: File): Promise; + } } export const browser: BrowserApi; diff --git a/src/browserApi.js b/src/browserApi.js index 86fac07..31f8511 100644 --- a/src/browserApi.js +++ b/src/browserApi.js @@ -216,6 +216,177 @@ class BrowserApi { } }); } + + /** + * Parse CSV from a browser ReadableStream and return parsed data as JSON array + * Processes data in chunks for memory-efficient handling of large streams + * @param {object} stream - Browser ReadableStream containing CSV data + * @returns {Promise>} Promise resolving to array of objects representing CSV rows + * @throws {InputValidationError} If stream is invalid + * @throws {BrowserApiError} If streaming is not supported or parsing fails + * @example + * const csvToJson = require('convert-csv-to-json'); + * const response = await fetch('large-dataset.csv'); + * const stream = response.body; + * const data = await csvToJson.browser.getJsonFromStreamAsync(stream); + * console.log(data); + */ + async getJsonFromStreamAsync(stream) { + if (typeof ReadableStream === 'undefined') { + throw BrowserApiError.streamingNotSupported(); + } + + if (!stream || typeof stream.getReader !== 'function') { + throw new InputValidationError( + 'stream', + 'ReadableStream', + typeof stream, + 'Provide a valid browser ReadableStream.' + ); + } + + return new Promise((resolve, reject) => { + const reader = stream.getReader(); + let buffer = ''; + let headers = null; + const parsedRecords = []; + let currentRecordIndex = 0; + const headerRowIndex = this.csvToJson.getIndexHeader(); + + const processChunk = async () => { + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + // Process any remaining buffer + if (buffer.length > 0) { + const result = this._processBrowserRecord(buffer, headers, parsedRecords, currentRecordIndex, headerRowIndex); + headers = result.headers; + } + resolve(parsedRecords); + return; + } + + // Convert chunk to string and add to buffer + buffer += (typeof value === 'string') ? value : + (typeof globalThis.TextDecoder !== 'undefined' ? new globalThis.TextDecoder().decode(value) : + String.fromCharCode.apply(null, new Uint8Array(value))); + + // Process complete records from buffer + const bufferResult = this._processBrowserBuffer(buffer, headers, parsedRecords, currentRecordIndex, headerRowIndex); + buffer = bufferResult.buffer; + headers = bufferResult.headers; + currentRecordIndex++; + } + } catch (error) { + reject(BrowserApiError.parseFileError(error)); + } + }; + + processChunk(); + }); + } + + /** + * Parse CSV from a File object using streaming for memory-efficient processing + * @param {File} file - File object containing CSV data + * @returns {Promise>} Promise resolving to array of objects representing CSV rows + * @throws {InputValidationError} If file is invalid + * @throws {BrowserApiError} If streaming is not supported or parsing fails + * @example + * const csvToJson = require('convert-csv-to-json'); + * const fileInput = document.querySelector('#csvfile').files[0]; + * const data = await csvToJson.browser.getJsonFromFileStreamingAsync(fileInput); + * console.log(data); + */ + async getJsonFromFileStreamingAsync(file) { + if (!file || !(file instanceof File)) { + throw new InputValidationError( + 'file', + 'File object', + typeof file, + 'Provide a valid File object.' + ); + } + + // Check if the file supports streaming + if (typeof file.stream === 'function' && file.hasOwnProperty('stream')) { + // Use native streaming if available + const stream = file.stream(); + return this.getJsonFromStreamAsync(stream); + } else { + // Fallback to regular file parsing for older browsers + return this.parseFile(file); + } + } + + /** + * Process buffer to extract complete records (browser implementation) + * @param {string} buffer - Current buffer content + * @param {Array|null} headers - CSV headers + * @param {Array} parsedRecords - Output array for parsed records + * @param {number} currentRecordIndex - Current record index + * @param {number} headerRowIndex - Index of header row + * @returns {object} Object with buffer and updated headers + * @private + */ + _processBrowserBuffer(buffer, headers, parsedRecords, currentRecordIndex, headerRowIndex) { + const lines = buffer.split('\n'); + const completeLines = lines.slice(0, -1); // All lines except the last (potentially incomplete) + const remainingBuffer = lines[lines.length - 1]; + + for (const line of completeLines) { + if (line.trim()) { // Skip empty lines + const result = this._processBrowserRecord(line, headers, parsedRecords, currentRecordIndex, headerRowIndex); + headers = result.headers; + currentRecordIndex++; + } + } + + return { buffer: remainingBuffer, headers }; + } + + /** + * Process a single CSV record (browser implementation) + * @param {string} record - CSV record to process + * @param {Array|null} headers - CSV headers + * @param {Array} parsedRecords - Output array for parsed records + * @param {number} currentRecordIndex - Current record index + * @param {number} headerRowIndex - Index of header row + * @returns {object} Object with updated headers + * @private + */ + _processBrowserRecord(record, headers, parsedRecords, currentRecordIndex, headerRowIndex) { + if (headers === null && currentRecordIndex === headerRowIndex) { + // Process header record + const headerFields = this._splitBrowserRecord(record); + if (headerFields.length > 0) { + headers = headerFields; + } + } else if (headers !== null) { + // Process data record + const dataFields = this._splitBrowserRecord(record); + if (dataFields.length > 0) { + const row = this.csvToJson.buildJsonResult(headers, dataFields); + parsedRecords.push(row); + } + } + return { headers }; + } + + /** + * Split a CSV record into fields (browser implementation) + * @param {string} record - Record to split + * @returns {string[]} Array of field values + * @private + */ + _splitBrowserRecord(record) { + if (this.csvToJson.isSupportQuotedField) { + return this.csvToJson.split(record); + } + return record.split(this.csvToJson.delimiter || ','); + } } module.exports = new BrowserApi(); diff --git a/src/util/errors.js b/src/util/errors.js index fe0443e..6d0c55d 100644 --- a/src/util/errors.js +++ b/src/util/errors.js @@ -318,6 +318,24 @@ class BrowserApiError extends CsvParsingError { { originalError: originalError.message } ); } + + /** + * Create error for unsupported streaming API in browser + * Occurs when browser doesn't support ReadableStream + * @returns {BrowserApiError} Configured error instance + * @static + */ + static streamingNotSupported() { + return new BrowserApiError( + `Browser compatibility error: ReadableStream API is not available.\n` + + `Your browser does not support the ReadableStream API required for streaming.\n\n` + + `Solutions:\n` + + ` 1. Use a modern browser that supports ReadableStream (Chrome 43+, Firefox 65+, Safari 10.1+)\n` + + ` 2. Use getJsonFromFileStreamingAsync() which falls back to regular file parsing\n` + + ` 3. Consider using parseFile() for non-streaming file parsing\n` + + ` 4. Implement a polyfill for ReadableStream support` + ); + } } module.exports = { diff --git a/test/browserApi.spec.js b/test/browserApi.spec.js index 8c7b1fb..a1784fc 100644 --- a/test/browserApi.spec.js +++ b/test/browserApi.spec.js @@ -73,4 +73,143 @@ describe('Browser API', () => { if (original !== undefined) global.FileReader = original; }); + + describe('Streaming Methods', () => { + test('getJsonFromStreamAsync processes ReadableStream', async () => { + const csv = 'name;age\nJohn;30\nJane;25'; + let chunkIndex = 0; + const chunks = [csv.slice(0, 10), csv.slice(10)]; + + class MockReader { + read() { + if (chunkIndex < chunks.length) { + const chunk = chunks[chunkIndex++]; + return Promise.resolve({ done: false, value: chunk }); + } + return Promise.resolve({ done: true }); + } + } + + class MockStream { + getReader() { + return new MockReader(); + } + } + + const original = global.ReadableStream; + global.ReadableStream = function() { return {}; }; + + const result = await browser.getJsonFromStreamAsync(new MockStream()); + expect(result).toEqual([ + { name: 'John', age: '30' }, + { name: 'Jane', age: '25' } + ]); + + if (original === undefined) { + delete global.ReadableStream; + } else { + global.ReadableStream = original; + } + }); + + test('getJsonFromStreamAsync rejects when ReadableStream not available', async () => { + const original = global.ReadableStream; + if (original !== undefined) delete global.ReadableStream; + + class MockStream {} + await expect(browser.getJsonFromStreamAsync(new MockStream())).rejects.toThrow(/ReadableStream.*not available/); + + if (original !== undefined) global.ReadableStream = original; + }); + + test('getJsonFromStreamAsync rejects with invalid stream', async () => { + global.ReadableStream = function() { return {}; }; + + await expect(browser.getJsonFromStreamAsync(null)).rejects.toThrow(/ReadableStream/); + await expect(browser.getJsonFromStreamAsync({})).rejects.toThrow(/ReadableStream/); + + delete global.ReadableStream; + }); + + test('getJsonFromFileStreamingAsync uses file.stream() when available', async () => { + const csv = 'name;age\nJohn;30'; + let chunkIndex = 0; + const chunks = [csv]; + + class MockReader { + read() { + if (chunkIndex < chunks.length) { + const chunk = chunks[chunkIndex++]; + return Promise.resolve({ done: false, value: chunk }); + } + return Promise.resolve({ done: true }); + } + } + + class MockStream { + getReader() { + return new MockReader(); + } + } + + const mockFile = Object.create(File.prototype); + mockFile.stream = jest.fn(() => new MockStream()); + + const original = global.ReadableStream; + global.ReadableStream = function() { return {}; }; + + const result = await browser.getJsonFromFileStreamingAsync(mockFile); + expect(result).toEqual([{ name: 'John', age: '30' }]); + expect(mockFile.stream).toHaveBeenCalled(); + + if (original === undefined) { + delete global.ReadableStream; + } else { + global.ReadableStream = original; + } + }); + + test('getJsonFromFileStreamingAsync falls back to parseFile when stream unavailable', async () => { + const csv = 'name;age\nJohn;30'; + + class MockFileReader { + constructor() { + this.onload = null; + this.onerror = null; + this.result = null; + } + readAsText(file, encoding) { + setTimeout(() => { + this.result = file.text || file; + if (this.onload) this.onload(); + }, 0); + } + } + + const mockFile = Object.create(File.prototype); + mockFile.text = csv; + // Don't add stream method when ReadableStream is not available + + const originalFileReader = global.FileReader; + const originalReadableStream = global.ReadableStream; + + global.FileReader = MockFileReader; + if (originalReadableStream !== undefined) delete global.ReadableStream; + + const result = await browser.getJsonFromFileStreamingAsync(mockFile); + expect(result).toEqual([{ name: 'John', age: '30' }]); + + if (originalFileReader === undefined) { + delete global.FileReader; + } else { + global.FileReader = originalFileReader; + } + if (originalReadableStream !== undefined) global.ReadableStream = originalReadableStream; + }); + + test('getJsonFromFileStreamingAsync rejects with invalid file', async () => { + await expect(browser.getJsonFromFileStreamingAsync(null)).rejects.toThrow(/File object/); + await expect(browser.getJsonFromFileStreamingAsync('not a file')).rejects.toThrow(/File object/); + }); + }); }); From efa6d657a080997fd16561f16be2298443af3c0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Sat, 4 Apr 2026 13:47:36 +0000 Subject: [PATCH 04/18] streaming node browser refactoring --- README.md | 4 +- index.d.ts | 29 ++++++----- src/browserApi.js | 110 ++--------------------------------------- src/csvToJsonAsync.js | 27 +--------- src/streamProcessor.js | 94 +++++++++++++++++++++++++++++++++-- 5 files changed, 112 insertions(+), 152 deletions(-) diff --git a/README.md b/README.md index af0ffea..e3f030d 100644 --- a/README.md +++ b/README.md @@ -150,8 +150,8 @@ All APIs (Sync, Async and Browser) support the same configuration methods: - `trimHeaderFieldWhiteSpace(bool)` - Remove spaces from headers - `parseSubArray(delim, sep)` - Parse delimited arrays - `mapRows(fn)` - Transform, filter, or enrich each row -- `getJsonFromStreamAsync(stream)` - Process CSV from Readable streams -- `getJsonFromFileStreamingAsync(filePath)` - Stream processing for large files +- `getJsonFromStreamAsync(stream)` - Process CSV from Readable streams for NodeJS and Browser +- `getJsonFromFileStreamingAsync(filePath)` - Stream processing for large files for NodeJS and Browser - `utf8Encoding()`, `latin1Encoding()`, etc. - Set file encoding ### Examples diff --git a/index.d.ts b/index.d.ts index 89f80ee..1942b24 100644 --- a/index.d.ts +++ b/index.d.ts @@ -99,21 +99,21 @@ declare module 'convert-csv-to-json' { */ getJsonFromCsv(inputFileName: string): any[]; - /** - * Async version of getJsonFromCsv. When options.raw is true the input is treated as a CSV string - */ - getJsonFromCsvAsync(inputFileNameOrCsv: string, options?: { raw?: boolean }): Promise; + /** + * Async version of getJsonFromCsv. When options.raw is true the input is treated as a CSV string + */ + getJsonFromCsvAsync(inputFileNameOrCsv: string, options?: { raw?: boolean }): Promise; - /** - * Parse CSV from a Readable stream and return parsed data as JSON array - * Processes data in chunks for memory-efficient handling of large files - */ - getJsonFromStreamAsync(stream: NodeJS.ReadableStream): Promise; + /** + * Parse CSV from a Readable stream and return parsed data as JSON array + * Processes data in chunks for memory-efficient handling of large files + */ + getJsonFromStreamAsync(stream: NodeJS.ReadableStream): Promise; - /** - * Parse CSV from a file path using streaming for memory-efficient processing - */ - getJsonFromFileStreamingAsync(filePath: string): Promise; + /** + * Parse CSV from a file path using streaming for memory-efficient processing + */ + getJsonFromFileStreamingAsync(filePath: string): Promise; csvStringToJson(csvString: string): any[]; @@ -154,14 +154,13 @@ declare module 'convert-csv-to-json' { /** * Parse CSV from a ReadableStream and return parsed data as JSON array */ - getJsonFromStreamAsync(stream: ReadableStream): Promise; + getJsonFromStreamAsync(stream: any): Promise; /** * Parse CSV from a File object using streaming for memory-efficient processing */ getJsonFromFileStreamingAsync(file: File): Promise; } - } export const browser: BrowserApi; } diff --git a/src/browserApi.js b/src/browserApi.js index 31f8511..42d1c6c 100644 --- a/src/browserApi.js +++ b/src/browserApi.js @@ -4,6 +4,7 @@ const csvToJson = require('./csvToJson'); const { InputValidationError, BrowserApiError } = require('./util/errors'); +const StreamProcessor = require('./streamProcessor'); /** * Browser-friendly CSV to JSON API @@ -245,47 +246,8 @@ class BrowserApi { ); } - return new Promise((resolve, reject) => { - const reader = stream.getReader(); - let buffer = ''; - let headers = null; - const parsedRecords = []; - let currentRecordIndex = 0; - const headerRowIndex = this.csvToJson.getIndexHeader(); - - const processChunk = async () => { - try { - while (true) { - const { done, value } = await reader.read(); - - if (done) { - // Process any remaining buffer - if (buffer.length > 0) { - const result = this._processBrowserRecord(buffer, headers, parsedRecords, currentRecordIndex, headerRowIndex); - headers = result.headers; - } - resolve(parsedRecords); - return; - } - - // Convert chunk to string and add to buffer - buffer += (typeof value === 'string') ? value : - (typeof globalThis.TextDecoder !== 'undefined' ? new globalThis.TextDecoder().decode(value) : - String.fromCharCode.apply(null, new Uint8Array(value))); - - // Process complete records from buffer - const bufferResult = this._processBrowserBuffer(buffer, headers, parsedRecords, currentRecordIndex, headerRowIndex); - buffer = bufferResult.buffer; - headers = bufferResult.headers; - currentRecordIndex++; - } - } catch (error) { - reject(BrowserApiError.parseFileError(error)); - } - }; - - processChunk(); - }); + const streamProcessor = new StreamProcessor(this.csvToJson, { isBrowser: true }); + return streamProcessor.processStream(stream); } /** @@ -321,72 +283,6 @@ class BrowserApi { } } - /** - * Process buffer to extract complete records (browser implementation) - * @param {string} buffer - Current buffer content - * @param {Array|null} headers - CSV headers - * @param {Array} parsedRecords - Output array for parsed records - * @param {number} currentRecordIndex - Current record index - * @param {number} headerRowIndex - Index of header row - * @returns {object} Object with buffer and updated headers - * @private - */ - _processBrowserBuffer(buffer, headers, parsedRecords, currentRecordIndex, headerRowIndex) { - const lines = buffer.split('\n'); - const completeLines = lines.slice(0, -1); // All lines except the last (potentially incomplete) - const remainingBuffer = lines[lines.length - 1]; - - for (const line of completeLines) { - if (line.trim()) { // Skip empty lines - const result = this._processBrowserRecord(line, headers, parsedRecords, currentRecordIndex, headerRowIndex); - headers = result.headers; - currentRecordIndex++; - } - } - - return { buffer: remainingBuffer, headers }; - } - - /** - * Process a single CSV record (browser implementation) - * @param {string} record - CSV record to process - * @param {Array|null} headers - CSV headers - * @param {Array} parsedRecords - Output array for parsed records - * @param {number} currentRecordIndex - Current record index - * @param {number} headerRowIndex - Index of header row - * @returns {object} Object with updated headers - * @private - */ - _processBrowserRecord(record, headers, parsedRecords, currentRecordIndex, headerRowIndex) { - if (headers === null && currentRecordIndex === headerRowIndex) { - // Process header record - const headerFields = this._splitBrowserRecord(record); - if (headerFields.length > 0) { - headers = headerFields; - } - } else if (headers !== null) { - // Process data record - const dataFields = this._splitBrowserRecord(record); - if (dataFields.length > 0) { - const row = this.csvToJson.buildJsonResult(headers, dataFields); - parsedRecords.push(row); - } - } - return { headers }; - } - - /** - * Split a CSV record into fields (browser implementation) - * @param {string} record - Record to split - * @returns {string[]} Array of field values - * @private - */ - _splitBrowserRecord(record) { - if (this.csvToJson.isSupportQuotedField) { - return this.csvToJson.split(record); - } - return record.split(this.csvToJson.delimiter || ','); - } } module.exports = new BrowserApi(); diff --git a/src/csvToJsonAsync.js b/src/csvToJsonAsync.js index 8e1379e..46cbbaf 100644 --- a/src/csvToJsonAsync.js +++ b/src/csvToJsonAsync.js @@ -194,31 +194,8 @@ class CsvToJsonAsync { async getJsonFromStreamAsync(stream) { this._validateStream(stream); - return new Promise((resolve, reject) => { - const streamProcessor = new StreamProcessor(this.csvToJson); - - stream.on('data', (chunk) => { - try { - streamProcessor.processChunk(chunk); - } catch (error) { - reject(error); - } - }); - - stream.on('end', () => { - try { - streamProcessor.finalizeProcessing(); - const result = streamProcessor.getResult(); - resolve(result); - } catch (error) { - reject(error); - } - }); - - stream.on('error', (error) => { - reject(new FileOperationError(`Stream error: ${error.message}`)); - }); - }); + const streamProcessor = new StreamProcessor(this.csvToJson, { isBrowser: false }); + return streamProcessor.processStream(stream); } /** diff --git a/src/streamProcessor.js b/src/streamProcessor.js index 968aaaf..107890f 100644 --- a/src/streamProcessor.js +++ b/src/streamProcessor.js @@ -1,4 +1,8 @@ /* globals CsvFormatError */ +/** + * @typedef {import('stream').Readable} Readable + * @typedef {ReadableStream} ReadableStream + */ 'use strict'; const stringUtils = require('./util/stringUtils'); @@ -11,15 +15,19 @@ const CR = '\r'; /** * Handles the processing of CSV data from a stream * Encapsulates all stream processing logic following single responsibility principle + * Works with both Node.js streams and browser ReadableStream * @private */ class StreamProcessor { /** * Initialize the stream processor with CSV configuration * @param {object} csvConfig - The CSV configuration object + * @param {object} options - Environment options + * @param {boolean} options.isBrowser - Whether running in browser environment */ - constructor(csvConfig) { + constructor(csvConfig, options = {}) { this.csvConfig = csvConfig; + this.isBrowser = options.isBrowser || (typeof window !== 'undefined' && typeof document !== 'undefined'); this.buffer = ''; this.isInsideQuotes = false; this.headers = null; @@ -31,13 +39,93 @@ class StreamProcessor { /** * Process a chunk of data from the stream - * @param {Buffer|string} chunk - The data chunk to process + * @param {Buffer|string|Uint8Array} chunk - The data chunk to process */ processChunk(chunk) { - this.buffer += chunk.toString(); + // Convert chunk to string, handling both Node.js Buffers and browser Uint8Array + let chunkString; + if (typeof chunk === 'string') { + chunkString = chunk; + } else if (this.isBrowser && typeof globalThis.TextDecoder !== 'undefined') { + chunkString = new globalThis.TextDecoder().decode(chunk); + } else if (this.isBrowser) { + // Fallback for older browsers without TextDecoder + chunkString = String.fromCharCode.apply(null, new Uint8Array(chunk)); + } else { + // Node.js environment + chunkString = chunk.toString(); + } + + this.buffer += chunkString; this._processCompleteRecords(); } + /** + * Process a stream directly (unified interface for both environments) + * @param {Readable|ReadableStream} stream - The stream to process + * @returns {Promise>} Promise resolving to parsed records + */ + async processStream(stream) { + return new Promise((resolve, reject) => { + if (this.isBrowser) { + // Browser ReadableStream + if (!stream || typeof stream.getReader !== 'function') { + reject(new Error('Invalid ReadableStream provided')); + return; + } + + const reader = stream.getReader(); + + const processChunk = async () => { + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + this.finalizeProcessing(); + resolve(this.getResult()); + return; + } + + this.processChunk(value); + } + } catch (error) { + reject(error); + } + }; + + processChunk(); + } else { + // Node.js Readable stream + if (!stream || typeof stream.pipe !== 'function') { + reject(new Error('Invalid Readable stream provided')); + return; + } + + stream.on('data', (chunk) => { + try { + this.processChunk(chunk); + } catch (error) { + reject(error); + } + }); + + stream.on('end', () => { + try { + this.finalizeProcessing(); + resolve(this.getResult()); + } catch (error) { + reject(error); + } + }); + + stream.on('error', (error) => { + reject(error); + }); + } + }); + } + /** * Finalize processing when the stream ends */ From bfa703a38edbe882260d3aeca5198ba340576f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Sat, 4 Apr 2026 14:42:35 +0000 Subject: [PATCH 05/18] WIP: browser streaming --- docs/demo-source.js | 0 docs/demo.bundle.js | 1008 +++++++---- docs/demo.bundle.js.backup | 2375 +++++++++++++++++++++++++ docs/index.html | 444 +++++ package-lock.json | 3423 +++++++++++++++++++++++++++++++++++- package.json | 1 + src/browserApi.js | 140 +- src/streamProcessor.js | 118 ++ test/browserApi.spec.js | 14 +- 9 files changed, 7182 insertions(+), 341 deletions(-) create mode 100644 docs/demo-source.js create mode 100644 docs/demo.bundle.js.backup diff --git a/docs/demo-source.js b/docs/demo-source.js new file mode 100644 index 0000000..e69de29 diff --git a/docs/demo.bundle.js b/docs/demo.bundle.js index c6fd09f..8ff968e 100644 --- a/docs/demo.bundle.js +++ b/docs/demo.bundle.js @@ -1,310 +1,13 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.demo = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i { - tab.addEventListener('click', function() { - showTab(this.getAttribute('data-tab')); - }); - }); - - // Output tab switching - document.querySelectorAll('.output-tab').forEach(tab => { - tab.addEventListener('click', function() { - showOutputTab(this.getAttribute('data-output')); - }); - }); - - // Convert button - document.getElementById('convert-btn').addEventListener('click', convert); - - // Clear button - document.getElementById('clear-btn').addEventListener('click', clearAll); - - // Options change listeners - document.getElementById('format-values').addEventListener('change', updateOptions); - document.getElementById('quoted-fields').addEventListener('change', updateOptions); - document.getElementById('delimiter').addEventListener('input', updateOptions); - document.getElementById('header-index').addEventListener('input', updateOptions); - - // File input change - document.getElementById('csv-file').addEventListener('change', handleFileSelect); - - // Sample data buttons - document.querySelectorAll('.sample-btn').forEach(btn => { - btn.addEventListener('click', function() { - loadSample(this.getAttribute('data-sample')); - }); - }); - } - - function showTab(tabName) { - // Hide all tabs - document.querySelectorAll('.tab-content').forEach(content => { - content.classList.remove('active'); - }); - document.querySelectorAll('.tab').forEach(tab => { - tab.classList.remove('active'); - }); - - // Show selected tab - document.getElementById(tabName + '-tab').classList.add('active'); - document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); - } - - function showOutputTab(tabName) { - // Hide all output tabs - document.querySelectorAll('.output-content').forEach(content => { - content.classList.remove('active'); - }); - document.querySelectorAll('.output-tab').forEach(tab => { - tab.classList.remove('active'); - }); - - // Show selected output tab - document.getElementById(tabName + '-content').classList.add('active'); - document.querySelector(`[data-output="${tabName}"]`).classList.add('active'); - } - - function updateOptions() { - const formatValues = document.getElementById('format-values').checked; - const quotedFields = document.getElementById('quoted-fields').checked; - const delimiter = document.getElementById('delimiter').value; - const headerIndex = parseInt(document.getElementById('header-index').value) || 0; - - csvToJson.formatValueByType(formatValues); - csvToJson.supportQuotedField(quotedFields); - csvToJson.fieldDelimiter(delimiter); - csvToJson.indexHeader(headerIndex); - } - - async function convert() { - const output = document.getElementById('output'); - const convertBtn = document.getElementById('convert-btn'); - - // Clear previous output - clearOutput(); - - // Disable button during conversion - convertBtn.disabled = true; - convertBtn.textContent = 'Converting...'; - - try { - let result; - - // Check which tab is active - const activeTab = document.querySelector('.tab-content.active').id; - if (activeTab === 'text-tab') { - // Text input - const csvText = document.getElementById('csv-input').value; - if (!csvText.trim()) { - throw new Error('Please enter CSV text'); - } - result = csvToJson.csvStringToJson(csvText); - } else { - // File input - const fileInput = document.getElementById('csv-file'); - if (!fileInput.files[0]) { - throw new Error('Please select a CSV file'); - } - result = await csvToJson.parseFile(fileInput.files[0]); - } - - // Display result - displayResult(result); - - } catch (error) { - displayError(error); - } finally { - // Re-enable button - convertBtn.disabled = false; - convertBtn.textContent = 'Convert to JSON'; - } - } - - function displayResult(result) { - const output = document.getElementById('output'); - const jsonOutput = document.getElementById('json-output'); - const tableOutput = document.getElementById('table-output'); - const statsOutput = document.getElementById('stats-output'); - - // Show results container - output.classList.remove('hidden'); - - // JSON output - jsonOutput.textContent = JSON.stringify(result, null, 2); - - // Table output - displayTable(result); - - // Stats - displayStats(result); - - // Switch to table view by default - showOutputTab('table'); - } - - function displayTable(data) { - const tableOutput = document.getElementById('table-output'); - if (!data || data.length === 0) { - tableOutput.innerHTML = '

No data to display

'; - return; - } - - let html = ''; - // Headers - Object.keys(data[0]).forEach(key => { - html += ``; - }); - html += ''; - - // Rows - data.forEach(row => { - html += ''; - Object.values(row).forEach(value => { - html += ``; - }); - html += ''; - }); - html += '
${escapeHtml(key)}
${escapeHtml(String(value))}
'; - - tableOutput.innerHTML = html; - } - - function displayStats(data) { - const statsOutput = document.getElementById('stats-output'); - if (!data || data.length === 0) { - statsOutput.innerHTML = '

No data

'; - return; - } - - const numRows = data.length; - const numCols = Object.keys(data[0]).length; - const size = JSON.stringify(data).length; - - statsOutput.innerHTML = ` -
Rows: ${numRows}
-
Columns: ${numCols}
-
JSON Size: ${size} characters
- `; - } - - function displayError(error) { - const output = document.getElementById('output'); - const errorOutput = document.getElementById('error-output'); - - output.classList.remove('hidden'); - errorOutput.textContent = error.message; - errorOutput.classList.remove('hidden'); - } - - function clearOutput() { - const output = document.getElementById('output'); - const errorOutput = document.getElementById('error-output'); - - output.classList.add('hidden'); - errorOutput.classList.add('hidden'); - errorOutput.textContent = ''; - } - - function clearAll() { - document.getElementById('csv-input').value = ''; - document.getElementById('csv-file').value = ''; - clearOutput(); - } - - function handleFileSelect(event) { - const file = event.target.files[0]; - if (file) { - // Show file info - const fileInfo = document.getElementById('file-info'); - fileInfo.textContent = `Selected: ${file.name} (${formatFileSize(file.size)})`; - fileInfo.classList.remove('hidden'); - } - } - - function loadSample(sampleName) { - const samples = { - basic: `name,age,city -John,25,New York -Jane,30,London -Bob,35,Paris`, - - quoted: `"name","age","description" -"John Doe","25","Software Engineer" -"Jane Smith","30","Product Manager"`, - - numbers: `product,price,quantity -Widget A,19.99,100 -Widget B,29.99,50 -Widget C,9.99,200`, - - dates: `event,date,attendees -Conference,2024-01-15,150 -Workshop,2024-02-20,75 -Seminar,2024-03-10,200` - }; - - document.getElementById('csv-input').value = samples[sampleName] || ''; - showTab('text'); - } - - function formatFileSize(bytes) { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; - } - - function escapeHtml(text) { - const div = document.createElement('div'); - div.textContent = text; - return div.innerHTML; - } - - function downloadJSON() { - const jsonOutput = document.getElementById('json-output'); - const data = jsonOutput.textContent; - const blob = new Blob([data], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - - const a = document.createElement('a'); - a.href = url; - a.download = 'converted-data.json'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } - - // Make functions globally available for inline event handlers - window.showTab = showTab; - window.convert = convert; - window.clearAll = clearAll; - window.downloadJSON = downloadJSON; -})(); -},{"../src/browserApi":3}],2:[function(require,module,exports){ - -},{}],3:[function(require,module,exports){ +},{}],2:[function(require,module,exports){ /* globals CsvFormatError */ "use strict"; const csvToJson = require('./csvToJson'); const { InputValidationError, BrowserApiError } = require('./util/errors'); +const StreamProcessor = require('./streamProcessor'); /** * Browser-friendly CSV to JSON API @@ -517,11 +220,215 @@ class BrowserApi { } }); } + + /** + * Parse CSV from a browser ReadableStream and return parsed data as JSON array + * Processes data in chunks for memory-efficient handling of large streams + * @param {object} stream - Browser ReadableStream containing CSV data + * @returns {Promise>} Promise resolving to array of objects representing CSV rows + * @throws {InputValidationError} If stream is invalid + * @throws {BrowserApiError} If streaming is not supported or parsing fails + * @example + * const csvToJson = require('convert-csv-to-json'); + * const response = await fetch('large-dataset.csv'); + * const stream = response.body; + * const data = await csvToJson.browser.getJsonFromStreamAsync(stream); + * console.log(data); + */ + async getJsonFromStreamAsync(stream) { + if (typeof ReadableStream === 'undefined') { + throw BrowserApiError.streamingNotSupported(); + } + + if (!stream || typeof stream.getReader !== 'function') { + throw new InputValidationError( + 'stream', + 'ReadableStream', + typeof stream, + 'Provide a valid browser ReadableStream.' + ); + } + + const streamProcessor = new StreamProcessor(this.csvToJson, { isBrowser: true }); + return streamProcessor.processStream(stream); + } + + /** + * Parse CSV from a File object using streaming for memory-efficient processing + * @param {File} file - File object containing CSV data + * @returns {Promise>} Promise resolving to array of objects representing CSV rows + * @throws {InputValidationError} If file is invalid + * @throws {BrowserApiError} If streaming is not supported or parsing fails + * @example + * const csvToJson = require('convert-csv-to-json'); + * const fileInput = document.querySelector('#csvfile').files[0]; + * const data = await csvToJson.browser.getJsonFromFileStreamingAsync(fileInput); + * console.log(data); + */ + async getJsonFromFileStreamingAsync(file) { + if (!file || !(file instanceof File)) { + throw new InputValidationError( + 'file', + 'File object', + typeof file, + 'Provide a valid File object.' + ); + } + + // Check if the file supports streaming + if (typeof file.stream === 'function') { + // Use native streaming if available + const stream = file.stream(); + return this.getJsonFromStreamAsync(stream); + } else { + // Fallback to regular file parsing for older browsers + return this.parseFile(file); + } + } + + /** + * Parse CSV from a File object using streaming with progress callbacks for large files + * Processes data in chunks to avoid memory issues with large datasets + * @param {File} file - File object containing CSV data + * @param {object} options - Processing options + * @param {function(Array, number, number): void} options.onChunk - Callback for each chunk of processed rows + * @param {function(Array): void} [options.onComplete] - Callback when processing is complete + * @param {function(Error): void} [options.onError] - Callback for errors + * @param {number} [options.chunkSize=1000] - Number of rows per chunk + * @returns {Promise} Promise that resolves when streaming starts + * @throws {InputValidationError} If file or options are invalid + * @example + * const csvToJson = require('convert-csv-to-json'); + * const fileInput = document.querySelector('#csvfile').files[0]; + * + * await csvToJson.browser.getJsonFromFileStreamingAsyncWithCallback(fileInput, { + * chunkSize: 500, + * onChunk: (rows, processed, total) => { + * console.log(`Processed ${processed}/${total} rows`); + * // Handle chunk of rows here + * }, + * onComplete: (allRows) => { + * console.log('Processing complete!'); + * }, + * onError: (error) => { + * console.error('Error:', error); + * } + * }); + */ + async getJsonFromFileStreamingAsyncWithCallback(file, options = {}) { + if (!file || !(file instanceof File)) { + throw new InputValidationError( + 'file', + 'File object', + typeof file, + 'Provide a valid File object.' + ); + } + + if (!options.onChunk || typeof options.onChunk !== 'function') { + throw new InputValidationError( + 'options.onChunk', + 'function', + typeof options.onChunk, + 'Provide a callback function to handle processed chunks.' + ); + } + + const chunkSize = options.chunkSize || 1000; + const streamProcessor = new StreamProcessor(this.csvToJson, { + isBrowser: true, + chunkSize, + onChunk: options.onChunk, + onComplete: options.onComplete, + onError: options.onError + }); + + // Check if the file supports streaming + if (typeof file.stream === 'function') { + console.log('Using native streaming'); + // Use native streaming if available + const stream = file.stream(); + return streamProcessor.processStreamWithCallbacks(stream); + } else { + console.log('Falling back to FileReader'); + // Fallback to regular file parsing for older browsers + return this.parseFileWithCallbacks(file, options); + } + } + + /** + * Parse a File object with progress callbacks (fallback for non-streaming browsers) + * @param {File} file - File object to parse + * @param {object} options - Processing options + * @private + */ + async parseFileWithCallbacks(file, options) { + const chunkSize = options.chunkSize || 1000; + const onChunk = options.onChunk; + const onComplete = options.onComplete; + const onError = options.onError; + + return new Promise((resolve, reject) => { + if (typeof FileReader === 'undefined') { + const error = BrowserApiError.fileReaderNotAvailable(); + if (onError) onError(error); + reject(error); + return; + } + + const reader = new FileReader(); + reader.onerror = () => { + const error = BrowserApiError.parseFileError( + reader.error || new Error('Unknown file reading error') + ); + if (onError) onError(error); + reject(error); + }; + + reader.onload = () => { + try { + const text = reader.result; + const allRows = this.csvToJson.csvToJson(String(text)); + + // Process in chunks + let processed = 0; + const total = allRows.length; + + const processChunk = () => { + const chunk = allRows.slice(processed, processed + chunkSize); + if (chunk.length > 0) { + onChunk(chunk, processed + chunk.length, total); + processed += chunk.length; + // Use setTimeout to avoid blocking the UI + setTimeout(processChunk, 0); + } else { + if (onComplete) onComplete(allRows); + resolve(); + } + }; + + processChunk(); + } catch (err) { + const error = BrowserApiError.parseFileError(err); + if (onError) onError(error); + reject(error); + } + }; + + reader.readAsText(file); + }); + } + } module.exports = new BrowserApi(); -},{"./csvToJson":4,"./util/errors":5}],4:[function(require,module,exports){ +// Assign to window for browser demo +if (typeof window !== 'undefined') { + window.csvToJson = module.exports; +} + +},{"./csvToJson":3,"./streamProcessor":4,"./util/errors":5}],3:[function(require,module,exports){ /* globals FileOperationError */ "use strict"; @@ -1078,7 +985,481 @@ class CsvToJson { module.exports = new CsvToJson(); -},{"./util/errors":5,"./util/fileUtils":6,"./util/jsonUtils":7,"./util/stringUtils":8}],5:[function(require,module,exports){ +},{"./util/errors":5,"./util/fileUtils":6,"./util/jsonUtils":7,"./util/stringUtils":8}],4:[function(require,module,exports){ +/* globals CsvFormatError */ +/** + * @typedef {import('stream').Readable} Readable + * @typedef {ReadableStream} ReadableStream + */ +'use strict'; + +const stringUtils = require('./util/stringUtils'); + +const QUOTE_CHAR = '"'; +const CRLF = '\r\n'; +const LF = '\n'; +const CR = '\r'; + +/** + * Handles the processing of CSV data from a stream + * Encapsulates all stream processing logic following single responsibility principle + * Works with both Node.js streams and browser ReadableStream + * @private + */ +class StreamProcessor { + /** + * Initialize the stream processor with CSV configuration + * @param {object} csvConfig - The CSV configuration object + * @param {object} options - Environment options + * @param {boolean} options.isBrowser - Whether running in browser environment + * @param {number} options.chunkSize - Number of rows per chunk for callback processing + * @param {function} options.onChunk - Callback for each chunk + * @param {function} options.onComplete - Callback when processing complete + * @param {function} options.onError - Callback for errors + */ + constructor(csvConfig, options = {}) { + this.csvConfig = csvConfig; + this.isBrowser = options.isBrowser || (typeof window !== 'undefined' && typeof document !== 'undefined'); + this.buffer = ''; + this.isInsideQuotes = false; + this.headers = null; + this.headerRowIndex = csvConfig.getIndexHeader(); + this.currentRecordIndex = 0; + this.parsedRecords = []; + this.dataRowIndex = 0; + + // Chunked processing options + this.chunkSize = options.chunkSize || 1000; + this.onChunk = options.onChunk; + this.onComplete = options.onComplete; + this.onError = options.onError; + this.allRecords = []; // For collecting all records when using callbacks + } + + /** + * Process a chunk of data from the stream + * @param {Buffer|string|Uint8Array} chunk - The data chunk to process + */ + processChunk(chunk) { + // Convert chunk to string, handling both Node.js Buffers and browser Uint8Array + let chunkString; + if (typeof chunk === 'string') { + chunkString = chunk; + } else if (this.isBrowser && typeof globalThis.TextDecoder !== 'undefined') { + chunkString = new globalThis.TextDecoder().decode(chunk); + } else if (this.isBrowser) { + // Fallback for older browsers without TextDecoder + chunkString = String.fromCharCode.apply(null, new Uint8Array(chunk)); + } else { + // Node.js environment + chunkString = chunk.toString(); + } + + this.buffer += chunkString; + this._processCompleteRecords(); + } + + /** + * Process a stream with chunked callbacks (for large files) + * @param {Readable|ReadableStream} stream - The stream to process + * @returns {Promise} Promise that resolves when streaming starts + */ + async processStreamWithCallbacks(stream) { + return new Promise((resolve, reject) => { + if (this.isBrowser) { + // Browser ReadableStream + if (!stream || typeof stream.getReader !== 'function') { + const error = new Error('Invalid ReadableStream provided'); + if (this.onError) this.onError(error); + reject(error); + return; + } + + const reader = stream.getReader(); + + const processChunk = async () => { + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + this.finalizeProcessing(); + this._sendRemainingChunks(); + if (this.onComplete) this.onComplete(this.allRecords); + resolve(); + return; + } + + this.processChunk(value); + this._sendPendingChunks(); + } + } catch (error) { + if (this.onError) this.onError(error); + reject(error); + } + }; + + processChunk(); + } else { + // Node.js Readable stream + if (!stream || typeof stream.pipe !== 'function') { + const error = new Error('Invalid Readable stream provided'); + if (this.onError) this.onError(error); + reject(error); + return; + } + + stream.on('data', (chunk) => { + try { + this.processChunk(chunk); + this._sendPendingChunks(); + } catch (error) { + if (this.onError) this.onError(error); + reject(error); + } + }); + + stream.on('end', () => { + try { + this.finalizeProcessing(); + this._sendRemainingChunks(); + if (this.onComplete) this.onComplete(this.allRecords); + resolve(); + } catch (error) { + if (this.onError) this.onError(error); + reject(error); + } + }); + + stream.on('error', (error) => { + if (this.onError) this.onError(error); + reject(error); + }); + } + }); + } + + /** + * Send pending chunks when they reach the chunk size + * @private + */ + _sendPendingChunks() { + if (!this.onChunk) return; + + while (this.parsedRecords.length >= this.chunkSize) { + const chunk = this.parsedRecords.splice(0, this.chunkSize); + this.allRecords.push(...chunk); + this.onChunk(chunk, this.allRecords.length, null); // null for total when streaming + } + } + + /** + * Send any remaining chunks at the end of processing + * @private + */ + _sendRemainingChunks() { + if (!this.onChunk || this.parsedRecords.length === 0) return; + + const chunk = [...this.parsedRecords]; + this.parsedRecords.length = 0; // Clear the array + this.allRecords.push(...chunk); + this.onChunk(chunk, this.allRecords.length, this.allRecords.length); + } + + /** + * Process a stream directly (unified interface for both environments) + * @param {Readable|ReadableStream} stream - The stream to process + * @returns {Promise>} Promise resolving to parsed records + */ + async processStream(stream) { + return new Promise((resolve, reject) => { + if (this.isBrowser) { + // Browser ReadableStream + if (!stream || typeof stream.getReader !== 'function') { + reject(new Error('Invalid ReadableStream provided')); + return; + } + + const reader = stream.getReader(); + + const processChunk = async () => { + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + this.finalizeProcessing(); + resolve(this.getResult()); + return; + } + + this.processChunk(value); + } + } catch (error) { + reject(error); + } + }; + + processChunk(); + } else { + // Node.js Readable stream + if (!stream || typeof stream.pipe !== 'function') { + reject(new Error('Invalid Readable stream provided')); + return; + } + + stream.on('data', (chunk) => { + try { + this.processChunk(chunk); + } catch (error) { + reject(error); + } + }); + + stream.on('end', () => { + try { + this.finalizeProcessing(); + resolve(this.getResult()); + } catch (error) { + reject(error); + } + }); + + stream.on('error', (error) => { + reject(error); + }); + } + }); + } + + /** + * Finalize processing when the stream ends + */ + finalizeProcessing() { + this._processRemainingBuffer(); + this._validateProcessingResult(); + } + + /** + * Get the final processed result + * @returns {Array} Array of parsed JSON objects + */ + getResult() { + return this.parsedRecords; + } + + /** + * Process all complete records currently in the buffer + * @private + */ + _processCompleteRecords() { + const parseResult = this._parseRecordsFromBuffer(this.buffer, this.isInsideQuotes); + + this.buffer = parseResult.remainingBuffer; + this.isInsideQuotes = parseResult.isInsideQuotes; + + for (const record of parseResult.completeRecords) { + this._processRecord(record); + this.currentRecordIndex++; + } + } + + /** + * Process any remaining buffer content when stream ends + * @private + */ + _processRemainingBuffer() { + if (this.buffer.length > 0) { + if (this.isInsideQuotes) { + throw CsvFormatError.mismatchedQuotes('CSV stream'); + } + + const parseResult = this._parseRecordsFromBuffer(this.buffer + '\n', false); + + for (const record of parseResult.completeRecords) { + this._processRecord(record); + this.currentRecordIndex++; + } + } + } + + /** + * Process a single CSV record + * @param {string} record - The CSV record to process + * @private + */ + _processRecord(record) { + if (this.headers === null && this.currentRecordIndex === this.headerRowIndex) { + this._processHeaderRecord(record); + } else if (this.headers !== null) { + this._processDataRecord(record); + } + } + + /** + * Process a header record + * @param {string} record - The header record + * @private + */ + _processHeaderRecord(record) { + const headerFields = this._splitRecord(record); + if (stringUtils.hasContent(headerFields)) { + this.headers = headerFields; + } + } + + /** + * Process a data record + * @param {string} record - The data record + * @private + */ + _processDataRecord(record) { + const dataFields = this._splitRecord(record); + if (stringUtils.hasContent(dataFields)) { + const row = this.csvConfig.buildJsonResult(this.headers, dataFields); + const processedRow = this._applyRowMapper(row); + if (processedRow !== null) { + this.parsedRecords.push(processedRow); + } + } + } + + /** + * Apply row mapper function if configured + * @param {object} row - The parsed row object + * @returns {object|null} The processed row or null if filtered out + * @private + */ + _applyRowMapper(row) { + if (this.csvConfig.rowMapper) { + const mappedRow = this.csvConfig.rowMapper(row, this.dataRowIndex); + this.dataRowIndex++; + return mappedRow; + } + this.dataRowIndex++; + return row; + } + + /** + * Split a CSV record into fields based on configuration + * @param {string} record - The record to split + * @returns {string[]} Array of field values + * @private + */ + _splitRecord(record) { + if (this.csvConfig.isSupportQuotedField) { + return this.csvConfig.split(record); + } + return record.split(this.csvConfig.delimiter || ','); + } + + /** + * Parse complete records from buffer, handling quoted fields across chunks + * @param {string} buffer - Current buffer content + * @param {boolean} insideQuotes - Whether we're currently inside quotes + * @returns {object} Object with completeRecords array and remaining buffer/quote state + * @private + */ + _parseRecordsFromBuffer(buffer, insideQuotes) { + const completeRecords = []; + let currentRecord = ''; + let i = 0; + + while (i < buffer.length) { + const char = buffer[i]; + + if (char === QUOTE_CHAR) { + const escapedQuoteResult = this._handleEscapedQuote(buffer, i, insideQuotes); + if (escapedQuoteResult.wasEscaped) { + currentRecord += QUOTE_CHAR + QUOTE_CHAR; + i = escapedQuoteResult.newIndex; + continue; + } else { + insideQuotes = !insideQuotes; + } + } else if (!insideQuotes && this._isLineEnding(buffer, i)) { + const lineEndingLength = this._getLineEndingLength(buffer, i); + completeRecords.push(currentRecord); + currentRecord = ''; + i += lineEndingLength; + continue; + } + + currentRecord += char; + i++; + } + + return { + completeRecords, + remainingBuffer: currentRecord, + isInsideQuotes: insideQuotes + }; + } + + /** + * Handle escaped quotes in quoted fields + * @param {string} buffer - The buffer content + * @param {number} index - Current index in buffer + * @param {boolean} insideQuotes - Whether currently inside quotes + * @returns {object} Result indicating if quote was escaped and new index + * @private + */ + _handleEscapedQuote(buffer, index, insideQuotes) { + if (insideQuotes && index + 1 < buffer.length && buffer[index + 1] === QUOTE_CHAR) { + return { wasEscaped: true, newIndex: index + 2 }; + } + return { wasEscaped: false, newIndex: index + 1 }; + } + + /** + * Check if character at index is a line ending + * @param {string} buffer - The buffer content + * @param {number} index - Current index + * @returns {boolean} True if line ending + * @private + */ + _isLineEnding(buffer, index) { + return this._getLineEndingLength(buffer, index) > 0; + } + + /** + * Get the length of line ending at current position + * @param {string} content - Content to check + * @param {number} index - Current index + * @returns {number} Length of line ending + * @private + */ + _getLineEndingLength(content, index) { + if (content.slice(index, index + 2) === CRLF) { + return 2; + } + if (content[index] === LF) { + return 1; + } + if (content[index] === CR && content[index + 1] !== LF) { + return 1; + } + return 0; + } + + /** + * Validate the final processing result + * @private + */ + _validateProcessingResult() { + if (!this.headers && this.parsedRecords.length === 0) { + // Empty stream - this is OK + return; + } + + if (!this.headers) { + throw CsvFormatError.missingHeader(); + } + } +} + +module.exports = StreamProcessor; +},{"./util/stringUtils":8}],5:[function(require,module,exports){ 'use strict'; /** @@ -1399,6 +1780,24 @@ class BrowserApiError extends CsvParsingError { { originalError: originalError.message } ); } + + /** + * Create error for unsupported streaming API in browser + * Occurs when browser doesn't support ReadableStream + * @returns {BrowserApiError} Configured error instance + * @static + */ + static streamingNotSupported() { + return new BrowserApiError( + `Browser compatibility error: ReadableStream API is not available.\n` + + `Your browser does not support the ReadableStream API required for streaming.\n\n` + + `Solutions:\n` + + ` 1. Use a modern browser that supports ReadableStream (Chrome 43+, Firefox 65+, Safari 10.1+)\n` + + ` 2. Use getJsonFromFileStreamingAsync() which falls back to regular file parsing\n` + + ` 3. Consider using parseFile() for non-streaming file parsing\n` + + ` 4. Implement a polyfill for ReadableStream support` + ); + } } module.exports = { @@ -1510,7 +1909,7 @@ class FileUtils { } module.exports = new FileUtils(); -},{"./errors":5,"fs":2}],7:[function(require,module,exports){ +},{"./errors":5,"fs":1}],7:[function(require,module,exports){ 'use strict'; const { JsonValidationError } = require('./errors'); @@ -1704,5 +2103,4 @@ class StringUtils { module.exports = new StringUtils(); -},{}]},{},[1])(1) -}); +},{}]},{},[2]); diff --git a/docs/demo.bundle.js.backup b/docs/demo.bundle.js.backup new file mode 100644 index 0000000..72612bd --- /dev/null +++ b/docs/demo.bundle.js.backup @@ -0,0 +1,2375 @@ +(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i { + tab.addEventListener('click', function() { + showTab(this.getAttribute('data-tab')); + }); + }); + + // Output tab switching + document.querySelectorAll('.output-tab').forEach(tab => { + tab.addEventListener('click', function() { + showOutputTab(this.getAttribute('data-output')); + }); + }); + + // Convert button + document.getElementById('convert-btn').addEventListener('click', convert); + + // Clear button + document.getElementById('clear-btn').addEventListener('click', clearAll); + + // Options change listeners + document.getElementById('format-values').addEventListener('change', updateOptions); + document.getElementById('quoted-fields').addEventListener('change', updateOptions); + document.getElementById('delimiter').addEventListener('input', updateOptions); + document.getElementById('header-index').addEventListener('input', updateOptions); + document.getElementById('parse-subarray').addEventListener('change', updateOptions); + document.getElementById('map-rows').addEventListener('change', updateOptions); + document.getElementById('use-streaming').addEventListener('change', updateOptions); + document.getElementById('use-chunked').addEventListener('change', toggleChunkedOptions); + + // File input change + document.getElementById('csv-file').addEventListener('change', handleFileSelect); + + // Sample data buttons + document.querySelectorAll('.sample-btn').forEach(btn => { + btn.addEventListener('click', function() { + loadSample(this.getAttribute('data-sample')); + }); + }); + } + + function showTab(tabName) { + // Hide all tabs + document.querySelectorAll('.tab-content').forEach(content => { + content.classList.remove('active'); + }); + document.querySelectorAll('.tab').forEach(tab => { + tab.classList.remove('active'); + }); + + // Show selected tab + document.getElementById(tabName + '-tab').classList.add('active'); + document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); + } + + function showOutputTab(tabName) { + // Hide all output tabs + document.querySelectorAll('.output-content').forEach(content => { + content.classList.remove('active'); + }); + document.querySelectorAll('.output-tab').forEach(tab => { + tab.classList.remove('active'); + }); + + // Show selected output tab + document.getElementById(tabName + '-content').classList.add('active'); + document.querySelector(`[data-output="${tabName}"]`).classList.add('active'); + } + + function updateOptions() { + const formatValues = document.getElementById('format-values').checked; + const quotedFields = document.getElementById('quoted-fields').checked; + const delimiter = document.getElementById('delimiter').value; + const headerIndex = parseInt(document.getElementById('header-index').value) || 0; + const parseSubarray = document.getElementById('parse-subarray').checked; + const mapRows = document.getElementById('map-rows').checked; + + csvToJson.formatValueByType(formatValues); + csvToJson.supportQuotedField(quotedFields); + csvToJson.fieldDelimiter(delimiter); + csvToJson.indexHeader(headerIndex); + + if (parseSubarray) { + csvToJson.parseSubArray('*', ','); + } + + if (mapRows) { + csvToJson.mapRows((row, index) => { + // Add a processed flag and row number + return { + ...row, + _processed: true, + _rowNumber: index + 1 + }; + }); + } + } + + function toggleChunkedOptions() { + const useChunked = document.getElementById('use-chunked').checked; + const chunkedOptions = document.getElementById('chunked-options'); + chunkedOptions.style.display = useChunked ? 'block' : 'none'; + } + + async function convert() { + const output = document.getElementById('output'); + const convertBtn = document.getElementById('convert-btn'); + + // Clear previous output + clearOutput(); + + // Disable button during conversion + convertBtn.disabled = true; + convertBtn.textContent = 'Converting...'; + + try { + let result; + + // Check which tab is active + const activeTab = document.querySelector('.tab-content.active').id; + const useStreaming = document.getElementById('use-streaming').checked; + + if (activeTab === 'text-tab') { + // Text input + const csvText = document.getElementById('csv-input').value; + if (!csvText.trim()) { + throw new Error('Please enter CSV text'); + } + result = csvToJson.csvStringToJson(csvText); + } else { + // File input + const fileInput = document.getElementById('csv-file'); + if (!fileInput.files[0]) { + throw new Error('Please select a CSV file'); + } + + const useChunked = document.getElementById('use-chunked').checked; + + if (useChunked) { + // Use chunked processing for large files + const chunkSize = parseInt(document.getElementById('chunk-size').value) || 1000; + await processFileInChunks(fileInput.files[0], chunkSize); + } else if (useStreaming) { + // Use streaming API + result = await csvToJson.getJsonFromFileStreamingAsync(fileInput.files[0]); + displayResult(result); + } else { + // Use regular file parsing + result = await csvToJson.parseFile(fileInput.files[0]); + displayResult(result); + } + } + convertBtn.textContent = 'Convert to JSON'; + } + } + + async function processFileInChunks(file, chunkSize) { + const output = document.getElementById('output'); + const jsonOutput = document.getElementById('json-output'); + const tableOutput = document.getElementById('table-output'); + const statsOutput = document.getElementById('stats-output'); + const convertBtn = document.getElementById('convert-btn'); + + // Show results container + output.classList.remove('hidden'); + clearOutput(); + + // Initialize progress tracking + let allRows = []; + let totalProcessed = 0; + let isComplete = false; + + // Create progress display + const progressDiv = document.createElement('div'); + progressDiv.id = 'progress-display'; + progressDiv.innerHTML = '

Processing large file...

Initializing...
'; + output.insertBefore(progressDiv, output.firstChild); + + try { + await csvToJson.getJsonFromFileStreamingAsyncWithCallback(file, { + chunkSize: chunkSize, + onChunk: (rows, processed, total) => { + // Accumulate rows + allRows.push(...rows); + totalProcessed = processed; + + // Update progress + const progressPercent = total ? Math.round((processed / total) * 100) : Math.min(Math.round((processed / 10000) * 100), 95); // Estimate for streaming + document.getElementById('progress-fill').style.width = progressPercent + '%'; + document.getElementById('progress-text').textContent = `Processed ${processed} rows...`; + + // For very large files, limit display to first 1000 rows in table + if (allRows.length <= 1000) { + displayTable(allRows); + } else if (allRows.length === 1001) { + tableOutput.innerHTML = '

Table display limited to first 1000 rows for performance. Full data available in JSON view.

'; + } + }, + onComplete: (allRowsComplete) => { + isComplete = true; + allRows = allRowsComplete; + + // Update progress to 100% + document.getElementById('progress-fill').style.width = '100%'; + document.getElementById('progress-text').textContent = `Complete! Processed ${allRows.length} rows.`; + + // Display final results + jsonOutput.textContent = JSON.stringify(allRows, null, 2); + displayStats(allRows); + showOutputTab('table'); + + // Remove progress after a delay + setTimeout(() => { + const progressDisplay = document.getElementById('progress-display'); + if (progressDisplay) progressDisplay.remove(); + }, 3000); + }, + onError: (error) => { + displayError(error); + const progressDisplay = document.getElementById('progress-display'); + if (progressDisplay) progressDisplay.remove(); + } + }); + } catch (error) { + displayError(error); + const progressDisplay = document.getElementById('progress-display'); + if (progressDisplay) progressDisplay.remove(); + } + } + + function displayResult(result) { + const output = document.getElementById('output'); + const jsonOutput = document.getElementById('json-output'); + const tableOutput = document.getElementById('table-output'); + const statsOutput = document.getElementById('stats-output'); + + // Show results container + output.classList.remove('hidden'); + + // JSON output + jsonOutput.textContent = JSON.stringify(result, null, 2); + + // Table output + displayTable(result); + + // Stats + displayStats(result); + + // Switch to table view by default + showOutputTab('table'); + } + + function displayTable(data) { + const tableOutput = document.getElementById('table-output'); + if (!data || data.length === 0) { + tableOutput.innerHTML = '

No data to display

'; + return; + } + + let html = ''; + // Headers + Object.keys(data[0]).forEach(key => { + html += ``; + }); + html += ''; + + // Rows + data.forEach(row => { + html += ''; + Object.values(row).forEach(value => { + html += ``; + }); + html += ''; + }); + html += '
${escapeHtml(key)}
${escapeHtml(String(value))}
'; + + tableOutput.innerHTML = html; + } + + function displayStats(data) { + const statsOutput = document.getElementById('stats-output'); + if (!data || data.length === 0) { + statsOutput.innerHTML = '

No data

'; + return; + } + + const numRows = data.length; + const numCols = Object.keys(data[0]).length; + const size = JSON.stringify(data).length; + + statsOutput.innerHTML = ` +
Rows: ${numRows}
+
Columns: ${numCols}
+
JSON Size: ${size} characters
+ `; + } + + function displayError(error) { + const output = document.getElementById('output'); + const errorOutput = document.getElementById('error-output'); + + output.classList.remove('hidden'); + errorOutput.textContent = error.message; + errorOutput.classList.remove('hidden'); + } + + function clearOutput() { + const output = document.getElementById('output'); + const errorOutput = document.getElementById('error-output'); + + output.classList.add('hidden'); + errorOutput.classList.add('hidden'); + errorOutput.textContent = ''; + } + + function clearAll() { + document.getElementById('csv-input').value = ''; + document.getElementById('csv-file').value = ''; + clearOutput(); + } + + function handleFileSelect(event) { + const file = event.target.files[0]; + if (file) { + // Show file info + const fileInfo = document.getElementById('file-info'); + fileInfo.textContent = `Selected: ${file.name} (${formatFileSize(file.size)})`; + fileInfo.classList.remove('hidden'); + } + } + + function loadSample(sampleName) { + const samples = { + basic: `name,age,city +John,25,New York +Jane,30,London +Bob,35,Paris`, + + quoted: `"name","age","description" +"John Doe","25","Software Engineer" +"Jane Smith","30","Product Manager"`, + + numbers: `product,price,quantity +Widget A,19.99,100 +Widget B,29.99,50 +Widget C,9.99,200`, + + dates: `event,date,attendees +Conference,2024-01-15,150 +Workshop,2024-02-20,75 +Seminar,2024-03-10,200`, + + subarray: `name,skills,hobbies +John,"JavaScript,Python,SQL","reading,gaming" +Jane,"React,Node.js,Docker","cooking,traveling" +Bob,"Vue.js,PHP,MongoDB","photography,hiking"`, + + mapping: `name,age,city,salary +John,25,New York,50000 +Jane,30,London,60000 +Bob,35,Paris,55000` + }; + + document.getElementById('csv-input').value = samples[sampleName] || ''; + showTab('text'); + + // Auto-enable relevant options for specific samples + if (sampleName === 'subarray') { + document.getElementById('parse-subarray').checked = true; + updateOptions(); + } else if (sampleName === 'mapping') { + document.getElementById('map-rows').checked = true; + updateOptions(); + } + } + + function formatFileSize(bytes) { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + } + + function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + function downloadJSON() { + const jsonOutput = document.getElementById('json-output'); + const data = jsonOutput.textContent; + const blob = new Blob([data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = 'converted-data.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + // Make functions globally available for inline event handlers + window.showTab = showTab; + window.convert = convert; + window.clearAll = clearAll; + window.downloadJSON = downloadJSON; +})(); +},{"../src/browserApi":3}],2:[function(require,module,exports){ + +},{}],3:[function(require,module,exports){ +/* globals CsvFormatError */ + +"use strict"; + +const csvToJson = require('./csvToJson'); +const { InputValidationError, BrowserApiError } = require('./util/errors'); +const StreamProcessor = require('./streamProcessor'); + +/** + * Browser-friendly CSV to JSON API + * Provides methods for parsing CSV strings and File/Blob objects in browser environments + * Proxies configuration to sync csvToJson instance + * @category 4-Browser + */ +class BrowserApi { + /** + * Constructor initializes proxy to sync csvToJson instance + */ + constructor() { + // reuse the existing csvToJson instance for parsing and configuration + this.csvToJson = csvToJson; + } + + /** + * Enable or disable automatic type formatting for values + * @param {boolean} active - Whether to format values by type (default: true) + * @returns {this} For method chaining + */ + formatValueByType(active = true) { + this.csvToJson.formatValueByType(active); + return this; + } + + /** + * Enable or disable support for RFC 4180 quoted fields + * @param {boolean} active - Whether to support quoted fields (default: false) + * @returns {this} For method chaining + */ + supportQuotedField(active = false) { + this.csvToJson.supportQuotedField(active); + return this; + } + + /** + * Set the field delimiter character + * @param {string} delimiter - Character(s) to use as field separator + * @returns {this} For method chaining + */ + fieldDelimiter(delimiter) { + this.csvToJson.fieldDelimiter(delimiter); + return this; + } + + /** + * Configure whitespace handling in header field names + * @param {boolean} active - If true, removes all whitespace; if false, only trims edges (default: false) + * @returns {this} For method chaining + */ + trimHeaderFieldWhiteSpace(active = false) { + this.csvToJson.trimHeaderFieldWhiteSpace(active); + return this; + } + + /** + * Set the row index where CSV headers are located + * @param {number} index - Zero-based row index containing headers + * @returns {this} For method chaining + */ + indexHeader(index) { + this.csvToJson.indexHeader(index); + return this; + } + + /** + * Configure sub-array parsing for special field values + * @param {string} delimiter - Bracket character (default: '*') + * @param {string} separator - Item separator within brackets (default: ',') + * @returns {this} For method chaining + */ + parseSubArray(delimiter = '*', separator = ',') { + this.csvToJson.parseSubArray(delimiter, separator); + return this; + } + + /** + * Set a mapper function to transform each row after conversion + * @param {function(object, number): (object|null)} mapperFn - Function receiving (row, index) that returns transformed row or null to filter + * @returns {this} For method chaining + */ + mapRows(mapperFn) { + this.csvToJson.mapRows(mapperFn); + return this; + } + + /** + * Parse a CSV string and return as JSON array of objects + * @param {string} csvString - CSV content as string + * @returns {Array} Array of objects representing CSV rows + * @throws {InputValidationError} If csvString is invalid + * @throws {CsvFormatError} If CSV is malformed + * @example + * const csvToJson = require('convert-csv-to-json'); + * const rows = csvToJson.browser.csvStringToJson('name,age\nAlice,30'); + * console.log(rows); // [{ name: 'Alice', age: '30' }] + */ + csvStringToJson(csvString) { + if (csvString === undefined || csvString === null) { + throw new InputValidationError( + 'csvString', + 'string', + `${typeof csvString}`, + 'Provide valid CSV content as a string to parse.' + ); + } + return this.csvToJson.csvToJson(csvString); + } + + /** + * Parse a CSV string and return as stringified JSON + * @param {string} csvString - CSV content as string + * @returns {string} JSON stringified array of objects + * @throws {InputValidationError} If csvString is invalid + * @throws {CsvFormatError} If CSV is malformed + * @example + * const csvToJson = require('convert-csv-to-json'); + * const jsonString = csvToJson.browser.csvStringToJsonStringified('name,age\nAlice,30'); + * console.log(jsonString); + */ + csvStringToJsonStringified(csvString) { + if (csvString === undefined || csvString === null) { + throw new InputValidationError( + 'csvString', + 'string', + `${typeof csvString}`, + 'Provide valid CSV content as a string to parse.' + ); + } + return this.csvToJson.csvStringToJsonStringified(csvString); + } + + /** + * Parse a CSV string asynchronously (returns resolved Promise) + * @param {string} csvString - CSV content as string + * @returns {Promise>} Promise resolving to array of objects + * @throws {InputValidationError} If csvString is invalid + * @throws {CsvFormatError} If CSV is malformed + * @example + * const csvToJson = require('convert-csv-to-json'); + * const rows = await csvToJson.browser.csvStringToJsonAsync('name,age\nAlice,30'); + * console.log(rows); + */ + csvStringToJsonAsync(csvString) { + return Promise.resolve(this.csvStringToJson(csvString)); + } + + /** + * Parse a CSV string asynchronously and return as stringified JSON + * @param {string} csvString - CSV content as string + * @returns {Promise} Promise resolving to JSON stringified array + * @throws {InputValidationError} If csvString is invalid + * @throws {CsvFormatError} If CSV is malformed + * @example + * const csvToJson = require('convert-csv-to-json'); + * const json = await csvToJson.browser.csvStringToJsonStringifiedAsync('name,age\nAlice,30'); + * console.log(json); + */ + csvStringToJsonStringifiedAsync(csvString) { + return Promise.resolve(this.csvStringToJsonStringified(csvString)); + } + + /** + * Parse a browser File or Blob object to JSON array. + * @param {File|Blob} file - File or Blob to read as text + * @param {object} [options] - options: { encoding?: string } + * @returns {Promise} Promise resolving to parsed JSON rows + * @example + * const csvToJson = require('convert-csv-to-json'); + * const fileInput = document.querySelector('#csvfile').files[0]; + * const rows = await csvToJson.browser.parseFile(fileInput); + * console.log(rows); + */ + parseFile(file, options = {}) { + if (!file) { + return Promise.reject(new InputValidationError( + 'file', + 'File or Blob object', + `${typeof file}`, + 'Provide a valid File or Blob object to parse.' + )); + } + + return new Promise((resolve, reject) => { + if (typeof FileReader === 'undefined') { + reject(BrowserApiError.fileReaderNotAvailable()); + return; + } + + const reader = new FileReader(); + reader.onerror = () => reject(BrowserApiError.parseFileError( + reader.error || new Error('Unknown file reading error') + )); + reader.onload = () => { + try { + const text = reader.result; + const result = this.csvToJson.csvToJson(String(text)); + resolve(result); + } catch (err) { + reject(BrowserApiError.parseFileError(err)); + } + }; + + // If encoding is provided, pass it to readAsText + if (options.encoding) { + reader.readAsText(file, options.encoding); + } else { + reader.readAsText(file); + } + }); + } + + /** + * Parse CSV from a browser ReadableStream and return parsed data as JSON array + * Processes data in chunks for memory-efficient handling of large streams + * @param {object} stream - Browser ReadableStream containing CSV data + * @returns {Promise>} Promise resolving to array of objects representing CSV rows + * @throws {InputValidationError} If stream is invalid + * @throws {BrowserApiError} If streaming is not supported or parsing fails + * @example + * const csvToJson = require('convert-csv-to-json'); + * const response = await fetch('large-dataset.csv'); + * const stream = response.body; + * const data = await csvToJson.browser.getJsonFromStreamAsync(stream); + * console.log(data); + */ + async getJsonFromStreamAsync(stream) { + if (typeof ReadableStream === 'undefined') { + throw BrowserApiError.streamingNotSupported(); + } + + if (!stream || typeof stream.getReader !== 'function') { + throw new InputValidationError( + 'stream', + 'ReadableStream', + typeof stream, + 'Provide a valid browser ReadableStream.' + ); + } + + const streamProcessor = new StreamProcessor(this.csvToJson, { isBrowser: true }); + return streamProcessor.processStream(stream); + } + + /** + * Parse CSV from a File object using streaming with progress callbacks for large files + * Processes data in chunks to avoid memory issues with large datasets + * @param {File} file - File object containing CSV data + * @param {object} options - Processing options + * @param {function(Array, number, number): void} options.onChunk - Callback for each chunk of processed rows + * @param {function(Array): void} [options.onComplete] - Callback when processing is complete + * @param {function(Error): void} [options.onError] - Callback for errors + * @param {number} [options.chunkSize=1000] - Number of rows per chunk + * @returns {Promise} Promise that resolves when streaming starts + * @throws {InputValidationError} If file or options are invalid + * @example + * const csvToJson = require('convert-csv-to-json'); + * const fileInput = document.querySelector('#csvfile').files[0]; + * + * await csvToJson.browser.getJsonFromFileStreamingAsyncWithCallback(fileInput, { + * chunkSize: 500, + * onChunk: (rows, processed, total) => { + * console.log(`Processed ${processed}/${total} rows`); + * // Handle chunk of rows here + * }, + * onComplete: (allRows) => { + * console.log('Processing complete!'); + * }, + * onError: (error) => { + * console.error('Error:', error); + * } + * }); + */ + async getJsonFromFileStreamingAsyncWithCallback(file, options = {}) { + if (!file || !(file instanceof File)) { + throw new InputValidationError( + 'file', + 'File object', + typeof file, + 'Provide a valid File object.' + ); + } + + if (!options.onChunk || typeof options.onChunk !== 'function') { + throw new InputValidationError( + 'options.onChunk', + 'function', + typeof options.onChunk, + 'Provide a callback function to handle processed chunks.' + ); + } + + const chunkSize = options.chunkSize || 1000; + const streamProcessor = new StreamProcessor(this.csvToJson, { + isBrowser: true, + chunkSize, + onChunk: options.onChunk, + onComplete: options.onComplete, + onError: options.onError + }); + + // Check if the file supports streaming + if (typeof file.stream === 'function') { + // Use native streaming if available + const stream = file.stream(); + return streamProcessor.processStreamWithCallbacks(stream); + } else { + // Fallback to regular file parsing for older browsers + return this.parseFileWithCallbacks(file, options); + } + } + + /** + * Parse a File object with progress callbacks (fallback for non-streaming browsers) + * @param {File} file - File object to parse + * @param {object} options - Processing options + * @private + */ + async parseFileWithCallbacks(file, options) { + const chunkSize = options.chunkSize || 1000; + const onChunk = options.onChunk; + const onComplete = options.onComplete; + const onError = options.onError; + + return new Promise((resolve, reject) => { + if (typeof FileReader === 'undefined') { + const error = BrowserApiError.fileReaderNotAvailable(); + if (onError) onError(error); + reject(error); + return; + } + + const reader = new FileReader(); + reader.onerror = () => { + const error = BrowserApiError.parseFileError( + reader.error || new Error('Unknown file reading error') + ); + if (onError) onError(error); + reject(error); + }; + + reader.onload = () => { + try { + const text = reader.result; + const allRows = this.csvToJson.csvToJson(String(text)); + + // Process in chunks + let processed = 0; + const total = allRows.length; + + const processChunk = () => { + const chunk = allRows.slice(processed, processed + chunkSize); + if (chunk.length > 0) { + onChunk(chunk, processed + chunk.length, total); + processed += chunk.length; + // Use setTimeout to avoid blocking the UI + setTimeout(processChunk, 0); + } else { + if (onComplete) onComplete(allRows); + resolve(); + } + }; + + processChunk(); + } catch (err) { + const error = BrowserApiError.parseFileError(err); + if (onError) onError(error); + reject(error); + } + }; + + reader.readAsText(file); + }); + } + +} + +module.exports = new BrowserApi(); + + +},{"./csvToJson":4,"./streamProcessor":5,"./util/errors":6}],4:[function(require,module,exports){ +/* globals FileOperationError */ +"use strict"; + +const fileUtils = require('./util/fileUtils'); +const stringUtils = require('./util/stringUtils'); +const jsonUtils = require('./util/jsonUtils'); +const { + ConfigurationError, + CsvFormatError, + JsonValidationError +} = require('./util/errors'); + +const DEFAULT_FIELD_DELIMITER = ","; +const QUOTE_CHAR = '"'; +const CRLF = '\r\n'; +const LF = '\n'; +const CR = '\r'; + +/** + * Main CSV to JSON converter class + * Provides chainable API for configuring and converting CSV data + * @category 2-Sync + */ +class CsvToJson { + + /** + * Enable or disable automatic type formatting for values + * When enabled, numeric strings are converted to numbers, 'true'/'false' to booleans + * @param {boolean} active - Whether to format values by type + * @returns {this} For method chaining + */ + formatValueByType(active) { + this.printValueFormatByType = active; + return this; + } + + /** + * Enable or disable support for RFC 4180 quoted fields + * When enabled, fields wrapped in double quotes can contain delimiters and newlines + * @param {boolean} active - Whether to support quoted fields + * @returns {this} For method chaining + */ + supportQuotedField(active) { + this.isSupportQuotedField = active; + return this; + } + + /** + * Set the field delimiter character + * @param {string} delimiter - Character(s) to use as field separator (default: ',') + * @returns {this} For method chaining + */ + fieldDelimiter(delimiter) { + this.delimiter = delimiter; + return this; + } + + /** + * Configure whitespace handling in header field names + * @param {boolean} active - If true, removes all whitespace from header names; if false, only trims edges + * @returns {this} For method chaining + */ + trimHeaderFieldWhiteSpace(active) { + this.isTrimHeaderFieldWhiteSpace = active; + return this; + } + + /** + * Set the row index where CSV headers are located + * @param {number} indexHeaderValue - Zero-based row index containing headers + * @returns {this} For method chaining + * @throws {ConfigurationError} If not a valid number + */ + indexHeader(indexHeaderValue) { + if (isNaN(indexHeaderValue)) { + throw ConfigurationError.invalidHeaderIndex(indexHeaderValue); + } + this.indexHeaderValue = indexHeaderValue; + return this; + } + + + /** + * Configure sub-array parsing for special field values + * Fields bracketed by delimiter and containing separator are parsed into arrays + * @param {string} delimiter - Bracket character (default: '*') + * @param {string} separator - Item separator within brackets (default: ',') + * @returns {this} For method chaining + * @example + * // Input: "*val1,val2,val3*" + * // Output: ["val1", "val2", "val3"] + * .parseSubArray('*', ',') + */ + parseSubArray(delimiter = '*',separator = ',') { + this.parseSubArrayDelimiter = delimiter; + this.parseSubArraySeparator = separator; + return this; + } + + /** + * Set file encoding for reading CSV files + * @param {string} encoding - Node.js supported encoding (e.g., 'utf8', 'latin1', 'ascii') + * @returns {this} For method chaining + */ + encoding(encoding){ + this.encoding = encoding; + return this; + } + + /** + * Sets a mapper function to transform each row after conversion + * @param {function(object, number): (object|null)} mapperFn - Function that receives (row, index) and returns transformed row or null to filter out + * @returns {this} For method chaining + */ + mapRows(mapperFn) { + if (typeof mapperFn !== 'function') { + throw new TypeError('mapperFn must be a function'); + } + this.rowMapper = mapperFn; + return this; + } + + /** + * Read a CSV file and write the parsed JSON to an output file + * @param {string} fileInputName - Path to input CSV file + * @param {string} fileOutputName - Path to output JSON file + * @throws {FileOperationError} If file read or write fails + * @throws {CsvFormatError} If CSV is malformed + */ + generateJsonFileFromCsv(fileInputName, fileOutputName) { + let jsonStringified = this.getJsonFromCsvStringified(fileInputName); + fileUtils.writeFile(jsonStringified, fileOutputName); + } + + /** + * Read a CSV file and return parsed data as stringified JSON + * @param {string} fileInputName - Path to input CSV file + * @returns {string} JSON stringified array of objects + * @throws {FileOperationError} If file read fails + * @throws {CsvFormatError} If CSV is malformed + * @throws {JsonValidationError} If JSON generation fails + * @example + * const csvToJson = require('convert-csv-to-json'); + * const jsonString = csvToJson.getJsonFromCsvStringified('resource/input.csv'); + * console.log(jsonString); + */ + getJsonFromCsvStringified(fileInputName) { + let json = this.getJsonFromCsv(fileInputName); + let jsonStringified = JSON.stringify(json, undefined, 1); + jsonUtils.validateJson(jsonStringified); + return jsonStringified; + } + + /** + * Read a CSV file and return parsed data as JSON array of objects + * @param {string} fileInputName - Path to input CSV file + * @returns {Array} Array of objects representing CSV rows + * @throws {FileOperationError} If file read fails + * @throws {CsvFormatError} If CSV is malformed + * @example + * const csvToJson = require('convert-csv-to-json'); + * const rows = csvToJson.getJsonFromCsv('resource/input.csv'); + * console.log(rows); + */ + getJsonFromCsv(fileInputName) { + let parsedCsv = fileUtils.readFile(fileInputName, this.encoding); + return this.csvToJson(parsedCsv); + } + + /** + * Parse CSV string content and return as JSON array of objects + * @param {string} csvString - CSV content as string + * @returns {Array} Array of objects representing CSV rows + * @throws {CsvFormatError} If CSV is malformed + * @example + * const csvToJson = require('convert-csv-to-json'); + * const rows = csvToJson.csvStringToJson('name,age\nAlice,30'); + * console.log(rows); // [{ name: 'Alice', age: '30' }] + */ + csvStringToJson(csvString) { + return this.csvToJson(csvString); + } + + /** + * Parse CSV string content and return as stringified JSON + * @param {string} csvString - CSV content as string + * @returns {string} JSON stringified array of objects + * @throws {CsvFormatError} If CSV is malformed + * @throws {JsonValidationError} If JSON generation fails + * @example + * const csvToJson = require('convert-csv-to-json'); + * const jsonString = csvToJson.csvStringToJsonStringified('name,age\nAlice,30'); + * console.log(jsonString); + */ + csvStringToJsonStringified(csvString) { + let json = this.csvStringToJson(csvString); + let jsonStringified = JSON.stringify(json, undefined, 1); + jsonUtils.validateJson(jsonStringified); + return jsonStringified; + } + + /** + * Core CSV parsing logic - converts CSV string to JSON array + * Handles quoted fields per RFC 4180 when configured + * Applies row mapping and filtering when configured + * @param {string} parsedCsv - Raw CSV content as string + * @returns {Array} Array of objects with CSV data + * @private + */ + csvToJson(parsedCsv) { + this.validateInputConfig(); + + // Parse CSV into individual records, respecting quoted fields that may contain newlines + let records = this.parseRecords(parsedCsv); + + let fieldDelimiter = this.getFieldDelimiter(); + let index = this.getIndexHeader(); + let headers; + + // Find the header row + while (index < records.length) { + if (this.isSupportQuotedField) { + headers = this.split(records[index]); + } else { + headers = records[index].split(fieldDelimiter); + } + + if (stringUtils.hasContent(headers)) { + break; + } + index++; + } + + if (!headers) { + throw CsvFormatError.missingHeader(); + } + + let jsonResult = []; + for (let i = (index + 1); i < records.length; i++) { + let currentLine; + if (this.isSupportQuotedField) { + currentLine = this.split(records[i]); + } else { + currentLine = records[i].split(fieldDelimiter); + } + + if (stringUtils.hasContent(currentLine)) { + let row = this.buildJsonResult(headers, currentLine); + + // Apply row mapper if defined + if (this.rowMapper) { + row = this.rowMapper(row, i - (index + 1)); // Pass row and 0-based row index + // If mapper returns null/undefined, skip this row (allows filtering) + if (row != null) { + jsonResult.push(row); + } + } else { + jsonResult.push(row); + } + } + } + return jsonResult; + } + + /** + * Parse CSV content into individual records, respecting quoted fields that may span multiple lines. + * RFC 4180 compliant parsing - handles quoted fields that may contain newlines. + * @param {string} csvContent - The raw CSV content + * @returns {string[]} Array of record strings + */ + parseRecords(csvContent) { + let records = []; + let currentRecord = ''; + let insideQuotes = false; + let i = 0; + + while (i < csvContent.length) { + let char = csvContent[i]; + + // Handle quote characters + if (char === QUOTE_CHAR) { + if (insideQuotes && i + 1 < csvContent.length && csvContent[i + 1] === QUOTE_CHAR) { + // Escaped quote: two consecutive quotes = single quote representation + currentRecord += QUOTE_CHAR + QUOTE_CHAR; + i += 2; + } else { + // Toggle quote state + insideQuotes = !insideQuotes; + currentRecord += char; + i++; + } + continue; + } + + // Handle line endings (only outside quoted fields) + if (!insideQuotes) { + let lineEndingLength = this.getLineEndingLength(csvContent, i); + if (lineEndingLength > 0) { + records.push(currentRecord); + currentRecord = ''; + i += lineEndingLength; + continue; + } + } + + // Regular character + currentRecord += char; + i++; + } + + // Add the last record if not empty + if (currentRecord.length > 0) { + records.push(currentRecord); + } + + // Validate matching quotes + if (insideQuotes) { + throw CsvFormatError.mismatchedQuotes('CSV'); + } + + return records; + } + + /** + * Get the length of line ending at current position (CRLF=2, LF=1, CR=1, or 0) + * @param {string} content - CSV content + * @param {number} index - Current index to check + * @returns {number} Length of line ending (0 if none) + */ + getLineEndingLength(content, index) { + if (content.slice(index, index + 2) === CRLF) { + return 2; + } + if (content[index] === LF) { + return 1; + } + if (content[index] === CR && content[index + 1] !== LF) { + return 1; + } + return 0; + } + + /** + * Get the configured field delimiter, or default if not set + * @returns {string} Field delimiter character + * @private + */ + getFieldDelimiter() { + if (this.delimiter) { + return this.delimiter; + } + return DEFAULT_FIELD_DELIMITER; + } + + /** + * Get the configured header row index, or default (0) if not set + * @returns {number} Header row index + * @private + */ + getIndexHeader(){ + if(this.indexHeaderValue !== null && !isNaN(this.indexHeaderValue)){ + return this.indexHeaderValue; + } + return 0; + } + + /** + * Build a JSON object from headers and field values + * Applies type formatting and sub-array parsing as configured + * @param {string[]} headers - Array of header field names + * @param {string[]} currentLine - Array of field values + * @returns {object} JSON object with header names as keys + * @private + */ + buildJsonResult(headers, currentLine) { + let jsonObject = {}; + for (let j = 0; j < headers.length; j++) { + let propertyName = stringUtils.trimPropertyName(this.isTrimHeaderFieldWhiteSpace, headers[j]); + let value = currentLine[j]; + + if(this.isParseSubArray(value)){ + value = this.buildJsonSubArray(value); + } + + if (this.printValueFormatByType && !Array.isArray(value)) { + value = stringUtils.getValueFormatByType(currentLine[j]); + } + + jsonObject[propertyName] = value; + } + return jsonObject; + } + + /** + * Parse a field value into a sub-array using configured delimiter and separator + * @param {string} value - Field value to parse + * @returns {Array} Array of parsed values + * @private + */ + buildJsonSubArray(value) { + let extractedValues = value.substring( + value.indexOf(this.parseSubArrayDelimiter) + 1, + value.lastIndexOf(this.parseSubArrayDelimiter) + ); + extractedValues.trim(); + value = extractedValues.split(this.parseSubArraySeparator); + if(this.printValueFormatByType){ + for(let i=0; i < value.length; i++){ + value[i] = stringUtils.getValueFormatByType(value[i]); + } + } + return value; + } + + /** + * Check if a field value should be parsed as a sub-array + * @param {string} value - Field value to check + * @returns {boolean} True if value is bracketed with sub-array delimiter + * @private + */ + isParseSubArray(value){ + if(this.parseSubArrayDelimiter){ + if (value && (value.indexOf(this.parseSubArrayDelimiter) === 0 && value.lastIndexOf(this.parseSubArrayDelimiter) === (value.length - 1))) { + return true; + } + } + return false; + } + + /** + * Validate configuration for conflicts and incompatibilities + * @throws {ConfigurationError} If incompatible options are set + * @private + */ + validateInputConfig(){ + if(this.isSupportQuotedField) { + if(this.getFieldDelimiter() === '"'){ + throw ConfigurationError.quotedFieldConflict('fieldDelimiter', '"'); + } + if(this.parseSubArraySeparator === '"'){ + throw ConfigurationError.quotedFieldConflict('parseSubArraySeparator', '"'); + } + if(this.parseSubArrayDelimiter === '"'){ + throw ConfigurationError.quotedFieldConflict('parseSubArrayDelimiter', '"'); + } + } + } + + /** + * Check if a line contains quote characters + * @param {string} line - Line to check + * @returns {boolean} True if line contains quotes + * @private + */ + hasQuotes(line) { + return line.includes('"'); + } + + /** + * Split a CSV record line into fields, respecting quoted fields per RFC 4180. + * Handles: + * - Quoted fields with embedded delimiters and newlines + * - Escaped quotes (double quotes within quoted fields) + * - Empty quoted fields + * @param {string} line - A single CSV record line + * @returns {string[]} Array of field values + */ + split(line) { + if (line.length === 0) { + return []; + } + + let fields = []; + let currentField = ''; + let insideQuotes = false; + let delimiter = this.getFieldDelimiter(); + + for (let i = 0; i < line.length; i++) { + let char = line[i]; + + // Handle quote character + if (char === QUOTE_CHAR) { + if (this.isEscapedQuote(line, i, insideQuotes)) { + // Two consecutive quotes inside quoted field = escaped quote + currentField += QUOTE_CHAR; + i++; // Skip next quote + } else if (this.isEmptyQuotedField(line, i, insideQuotes, currentField, delimiter)) { + // Empty quoted field: "" at field start before delimiter/end + i++; // Skip closing quote + } else { + // Regular quote: toggle quoted state + insideQuotes = !insideQuotes; + } + } else if (char === delimiter && !insideQuotes) { + // Delimiter outside quotes marks field boundary + fields.push(currentField); + currentField = ''; + } else { + // Regular character (including embedded newlines in quoted fields) + currentField += char; + } + } + + // Add final field + fields.push(currentField); + + // Validate matching quotes + if (insideQuotes) { + throw CsvFormatError.mismatchedQuotes('row'); + } + + return fields; + } + + /** + * Check if character at index is an escaped quote (double quote) + * Escaped quotes appear as "" within quoted fields per RFC 4180 + * @param {string} line - Line being parsed + * @param {number} index - Character index to check + * @param {boolean} insideQuoted - Whether currently inside a quoted field + * @returns {boolean} True if character is an escaped quote + * @private + */ + isEscapedQuote(line, index, insideQuoted) { + return insideQuoted && + index + 1 < line.length && + line[index + 1] === QUOTE_CHAR; + } + + /** + * Check if this is an empty quoted field: "" before delimiter or end of line + * @param {string} line - Line being parsed + * @param {number} index - Character index to check + * @param {boolean} insideQuoted - Whether currently inside a quoted field + * @param {string} currentField - Current field accumulation + * @param {string} delimiter - Field delimiter character + * @returns {boolean} True if this represents an empty quoted field + * @private + */ + isEmptyQuotedField(line, index, insideQuoted, currentField, delimiter) { + if (insideQuoted || currentField !== '' || index + 1 >= line.length) { + return false; + } + + let nextChar = line[index + 1]; + if (nextChar !== QUOTE_CHAR) { + return false; // Not a quote pair + } + + let afterQuotes = index + 2; + return afterQuotes === line.length || line[afterQuotes] === delimiter; + } +} + +module.exports = new CsvToJson(); + +},{"./util/errors":6,"./util/fileUtils":7,"./util/jsonUtils":8,"./util/stringUtils":9}],5:[function(require,module,exports){ +/* globals CsvFormatError */ +/** + * @typedef {import('stream').Readable} Readable + * @typedef {ReadableStream} ReadableStream + */ +'use strict'; + +const stringUtils = require('./util/stringUtils'); + +const QUOTE_CHAR = '"'; +const CRLF = '\r\n'; +const LF = '\n'; +const CR = '\r'; + +/** + * Handles the processing of CSV data from a stream + * Encapsulates all stream processing logic following single responsibility principle + * Works with both Node.js streams and browser ReadableStream + * @private + */ +class StreamProcessor { + /** + * Initialize the stream processor with CSV configuration + * @param {object} csvConfig - The CSV configuration object + * @param {object} options - Environment options + * @param {boolean} options.isBrowser - Whether running in browser environment + */ + constructor(csvConfig, options = {}) { + this.csvConfig = csvConfig; + this.isBrowser = options.isBrowser || (typeof window !== 'undefined' && typeof document !== 'undefined'); + this.buffer = ''; + this.isInsideQuotes = false; + this.headers = null; + this.headerRowIndex = csvConfig.getIndexHeader(); + this.currentRecordIndex = 0; + this.parsedRecords = []; + this.dataRowIndex = 0; + } + + /** + * Process a chunk of data from the stream + * @param {Buffer|string|Uint8Array} chunk - The data chunk to process + */ + processChunk(chunk) { + // Convert chunk to string, handling both Node.js Buffers and browser Uint8Array + let chunkString; + if (typeof chunk === 'string') { + chunkString = chunk; + } else if (this.isBrowser && typeof globalThis.TextDecoder !== 'undefined') { + chunkString = new globalThis.TextDecoder().decode(chunk); + } else if (this.isBrowser) { + // Fallback for older browsers without TextDecoder + chunkString = String.fromCharCode.apply(null, new Uint8Array(chunk)); + } else { + // Node.js environment + chunkString = chunk.toString(); + } + + this.buffer += chunkString; + this._processCompleteRecords(); + } + + /** + * Process a stream directly (unified interface for both environments) + * @param {Readable|ReadableStream} stream - The stream to process + * @returns {Promise>} Promise resolving to parsed records + */ + async processStream(stream) { + return new Promise((resolve, reject) => { + if (this.isBrowser) { + // Browser ReadableStream + if (!stream || typeof stream.getReader !== 'function') { + reject(new Error('Invalid ReadableStream provided')); + return; + } + + const reader = stream.getReader(); + + const processChunk = async () => { + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + this.finalizeProcessing(); + resolve(this.getResult()); + return; + } + + this.processChunk(value); + } + } catch (error) { + reject(error); + } + }; + + processChunk(); + } else { + // Node.js Readable stream + if (!stream || typeof stream.pipe !== 'function') { + reject(new Error('Invalid Readable stream provided')); + return; + } + + stream.on('data', (chunk) => { + try { + this.processChunk(chunk); + } catch (error) { + reject(error); + } + }); + + stream.on('end', () => { + try { + this.finalizeProcessing(); + resolve(this.getResult()); + } catch (error) { + reject(error); + } + }); + + stream.on('error', (error) => { + reject(error); + }); + } + }); + } + + /** + * Finalize processing when the stream ends + */ + finalizeProcessing() { + this._processRemainingBuffer(); + this._validateProcessingResult(); + } + + /** + * Get the final processed result + * @returns {Array} Array of parsed JSON objects + */ + getResult() { + return this.parsedRecords; + } + + /** + * Process all complete records currently in the buffer + * @private + */ + _processCompleteRecords() { + const parseResult = this._parseRecordsFromBuffer(this.buffer, this.isInsideQuotes); + + this.buffer = parseResult.remainingBuffer; + this.isInsideQuotes = parseResult.isInsideQuotes; + + for (const record of parseResult.completeRecords) { + this._processRecord(record); + this.currentRecordIndex++; + } + } + + /** + * Process any remaining buffer content when stream ends + * @private + */ + _processRemainingBuffer() { + if (this.buffer.length > 0) { + if (this.isInsideQuotes) { + throw CsvFormatError.mismatchedQuotes('CSV stream'); + } + + const parseResult = this._parseRecordsFromBuffer(this.buffer + '\n', false); + + for (const record of parseResult.completeRecords) { + this._processRecord(record); + this.currentRecordIndex++; + } + } + } + + /** + * Process a single CSV record + * @param {string} record - The CSV record to process + * @private + */ + _processRecord(record) { + if (this.headers === null && this.currentRecordIndex === this.headerRowIndex) { + this._processHeaderRecord(record); + } else if (this.headers !== null) { + this._processDataRecord(record); + } + } + + /** + * Process a header record + * @param {string} record - The header record + * @private + */ + _processHeaderRecord(record) { + const headerFields = this._splitRecord(record); + if (stringUtils.hasContent(headerFields)) { + this.headers = headerFields; + } + } + + /** + * Process a data record + * @param {string} record - The data record + * @private + */ + _processDataRecord(record) { + const dataFields = this._splitRecord(record); + if (stringUtils.hasContent(dataFields)) { + const row = this.csvConfig.buildJsonResult(this.headers, dataFields); + const processedRow = this._applyRowMapper(row); + if (processedRow !== null) { + this.parsedRecords.push(processedRow); + } + } + } + + /** + * Apply row mapper function if configured + * @param {object} row - The parsed row object + * @returns {object|null} The processed row or null if filtered out + * @private + */ + _applyRowMapper(row) { + if (this.csvConfig.rowMapper) { + const mappedRow = this.csvConfig.rowMapper(row, this.dataRowIndex); + this.dataRowIndex++; + return mappedRow; + } + this.dataRowIndex++; + return row; + } + + /** + * Split a CSV record into fields based on configuration + * @param {string} record - The record to split + * @returns {string[]} Array of field values + * @private + */ + _splitRecord(record) { + if (this.csvConfig.isSupportQuotedField) { + return this.csvConfig.split(record); + } + return record.split(this.csvConfig.delimiter || ','); + } + + /** + * Parse complete records from buffer, handling quoted fields across chunks + * @param {string} buffer - Current buffer content + * @param {boolean} insideQuotes - Whether we're currently inside quotes + * @returns {object} Object with completeRecords array and remaining buffer/quote state + * @private + */ + _parseRecordsFromBuffer(buffer, insideQuotes) { + const completeRecords = []; + let currentRecord = ''; + let i = 0; + + while (i < buffer.length) { + const char = buffer[i]; + + if (char === QUOTE_CHAR) { + const escapedQuoteResult = this._handleEscapedQuote(buffer, i, insideQuotes); + if (escapedQuoteResult.wasEscaped) { + currentRecord += QUOTE_CHAR + QUOTE_CHAR; + i = escapedQuoteResult.newIndex; + continue; + } else { + insideQuotes = !insideQuotes; + } + } else if (!insideQuotes && this._isLineEnding(buffer, i)) { + const lineEndingLength = this._getLineEndingLength(buffer, i); + completeRecords.push(currentRecord); + currentRecord = ''; + i += lineEndingLength; + continue; + } + + currentRecord += char; + i++; + } + + return { + completeRecords, + remainingBuffer: currentRecord, + isInsideQuotes: insideQuotes + }; + } + + /** + * Handle escaped quotes in quoted fields + * @param {string} buffer - The buffer content + * @param {number} index - Current index in buffer + * @param {boolean} insideQuotes - Whether currently inside quotes + * @returns {object} Result indicating if quote was escaped and new index + * @private + */ + _handleEscapedQuote(buffer, index, insideQuotes) { + if (insideQuotes && index + 1 < buffer.length && buffer[index + 1] === QUOTE_CHAR) { + return { wasEscaped: true, newIndex: index + 2 }; + } + return { wasEscaped: false, newIndex: index + 1 }; + } + + /** + * Check if character at index is a line ending + * @param {string} buffer - The buffer content + * @param {number} index - Current index + * @returns {boolean} True if line ending + * @private + */ + _isLineEnding(buffer, index) { + return this._getLineEndingLength(buffer, index) > 0; + } + + /** + * Get the length of line ending at current position + * @param {string} content - Content to check + * @param {number} index - Current index + * @returns {number} Length of line ending + * @private + */ + _getLineEndingLength(content, index) { + if (content.slice(index, index + 2) === CRLF) { + return 2; + } + if (content[index] === LF) { + return 1; + } + if (content[index] === CR && content[index + 1] !== LF) { + return 1; + } + return 0; + } + + /** + * Validate the final processing result + * @private + */ + _validateProcessingResult() { + if (!this.headers && this.parsedRecords.length === 0) { + // Empty stream - this is OK + return; + } + + if (!this.headers) { + throw CsvFormatError.missingHeader(); + } + } +} + +module.exports = StreamProcessor; +},{"./util/stringUtils":9}],6:[function(require,module,exports){ +'use strict'; + +/** + * Custom error classes following clean code principles + * Provides clear, actionable error messages with context + * @category Error Classes + */ + +/** + * Base class for all CSV parsing errors + * Provides consistent error formatting and context + * @category Error Classes + */ +class CsvParsingError extends Error { + /** + * Create a CSV parsing error + * @param {string} message - Error message + * @param {string} code - Error code for identification + * @param {object} context - Additional context information (default: {}) + */ + constructor(message, code, context = {}) { + super(message); + this.name = 'CsvParsingError'; + this.code = code; + this.context = context; + Error.captureStackTrace(this, this.constructor); + } + + /** + * Convert error to formatted string with context information + * @returns {string} Formatted error message including context + */ + toString() { + let output = `${this.name}: ${this.message}`; + + if (this.context && Object.keys(this.context).length > 0) { + output += '\n\nContext:'; + Object.entries(this.context).forEach(([key, value]) => { + output += `\n ${key}: ${this.formatValue(value)}`; + }); + } + + return output; + } + + /** + * Format a context value for display in error message + * @param {unknown} value - Value to format + * @returns {string} Formatted value string + * @private + */ + formatValue(value) { + if (value === null) return 'null'; + if (value === undefined) return 'undefined'; + if (typeof value === 'string') return `"${value}"`; + if (typeof value === 'object') return JSON.stringify(value); + return String(value); + } +} + +/** + * Input validation errors + * Thrown when function parameters don't meet expected type or value requirements + * @category Error Classes + */ +class InputValidationError extends CsvParsingError { + /** + * Create an input validation error + * @param {string} paramName - Name of the invalid parameter + * @param {string} expectedType - Expected type description + * @param {string} receivedType - Actual type received + * @param {string} details - Additional error details (optional) + */ + constructor(paramName, expectedType, receivedType, details = '') { + const message = + `Invalid input: Parameter '${paramName}' is required.\n` + + `Expected: ${expectedType}\n` + + `Received: ${receivedType}${details ? '\n' + details : ''}`; + + super(message, 'INPUT_VALIDATION_ERROR', { + parameter: paramName, + expectedType, + receivedType + }); + this.name = 'InputValidationError'; + } +} + +/** + * Configuration-related errors + * Thrown when configuration options conflict or are invalid + * @category Error Classes + */ +class ConfigurationError extends CsvParsingError { + /** + * Create a configuration error + * @param {string} message - Error message + * @param {object} conflictingOptions - Configuration options in conflict (optional) + */ + constructor(message, conflictingOptions = {}) { + super(message, 'CONFIGURATION_ERROR', conflictingOptions); + this.name = 'ConfigurationError'; + } + + /** + * Create error for quoted field configuration conflict + * Occurs when quote character is used as delimiter while quoted fields are enabled + * @param {string} optionName - Name of the conflicting option + * @param {string} value - Value that causes the conflict + * @returns {ConfigurationError} Configured error instance + * @static + */ + static quotedFieldConflict(optionName, value) { + return new ConfigurationError( + `Configuration conflict: supportQuotedField() is enabled, but ${optionName} is set to '${value}'.\n` + + `The quote character (") cannot be used as a field delimiter, separator, or sub-array delimiter when quoted field support is active.\n\n` + + `Solutions:\n` + + ` 1. Use a different character for ${optionName} (e.g., '|', '\\t', ';')\n` + + ` 2. Disable supportQuotedField() if your CSV doesn't contain quoted fields\n` + + ` 3. Refer to RFC 4180 for proper CSV formatting: https://tools.ietf.org/html/rfc4180`, + { optionName, value, conflictingOption: 'supportQuotedField' } + ); + } + + /** + * Create error for invalid header index + * Occurs when indexHeader() receives non-numeric value + * @param {unknown} value - Invalid header index value + * @returns {ConfigurationError} Configured error instance + * @static + */ + static invalidHeaderIndex(value) { + return new ConfigurationError( + `Invalid configuration: indexHeader() expects a numeric value.\n` + + `Received: ${typeof value} (${value})\n\n` + + `Solutions:\n` + + ` 1. Ensure indexHeader() receives a number: indexHeader(0), indexHeader(1), etc.\n` + + ` 2. Headers are typically found on row 0 (first line)\n` + + ` 3. Use indexHeader(2) if headers are on the 3rd line`, + { parameterName: 'indexHeader', value, type: typeof value } + ); + } +} + +/** + * CSV parsing errors with detailed context + * Thrown when CSV format is invalid or malformed + * @category Error Classes + */ +class CsvFormatError extends CsvParsingError { + /** + * Create a CSV format error + * @param {string} message - Error message + * @param {object} context - Additional context information (optional) + */ + constructor(message, context = {}) { + super(message, 'CSV_FORMAT_ERROR', context); + this.name = 'CsvFormatError'; + } + + /** + * Create error for missing CSV header row + * Occurs when no valid header row is found in CSV + * @returns {CsvFormatError} Configured error instance + * @static + */ + static missingHeader() { + return new CsvFormatError( + `CSV parsing error: No header row found.\n` + + `The CSV file appears to be empty or has no valid header line.\n\n` + + `Solutions:\n` + + ` 1. Ensure your CSV file contains at least one row (header row)\n` + + ` 2. Verify the file is not empty or contains only whitespace\n` + + ` 3. Check if you need to use indexHeader(n) to specify a non-standard header row\n` + + ` 4. Refer to RFC 4180 for proper CSV format: https://tools.ietf.org/html/rfc4180` + ); + } + + /** + * Create error for mismatched quotes in CSV + * Occurs when quoted fields are not properly closed + * @param {string} location - Where the error occurred (default: 'CSV') + * @returns {CsvFormatError} Configured error instance + * @static + */ + static mismatchedQuotes(location = 'CSV') { + return new CsvFormatError( + `CSV parsing error: Mismatched quotes detected in ${location}.\n` + + `A quoted field was not properly closed with a matching quote character.\n\n` + + `RFC 4180 rules for quoted fields:\n` + + ` • Fields containing delimiters or quotes MUST be enclosed in double quotes\n` + + ` • To include a quote within a quoted field, use two consecutive quotes: ""\n` + + ` • Example: "Smith, John" (name contains comma)\n` + + ` • Example: "He said ""Hello""" (text contains quotes)\n\n` + + `Solutions:\n` + + ` 1. Review your CSV for properly paired quote characters\n` + + ` 2. Use double quotes ("") to escape quotes within quoted fields\n` + + ` 3. Ensure all commas within field values are inside quotes\n` + + ` 4. Enable supportQuotedField(true) if you're using quoted fields`, + { location } + ); + } +} + +/** + * File operation errors + * Thrown when file read or write operations fail + * @category Error Classes + */ +class FileOperationError extends CsvParsingError { + /** + * Create a file operation error + * @param {string} operation - Type of operation that failed (e.g., 'read', 'write') + * @param {string} filePath - Path to the file where operation failed + * @param {Error} originalError - The underlying error object from Node.js + */ + constructor(operation, filePath, originalError) { + const message = + `File operation error: Failed to ${operation} file.\n` + + `File path: ${filePath}\n` + + `Reason: ${originalError.message}\n\n` + + `Solutions:\n` + + ` 1. Verify the file path is correct: ${filePath}\n` + + ` 2. Check file permissions (read access for input, write access for output)\n` + + ` 3. Ensure the directory exists and is writable for output files\n` + + ` 4. Verify the file is not in use by another process`; + + super(message, 'FILE_OPERATION_ERROR', { + operation, + filePath, + originalError: originalError.message + }); + this.name = 'FileOperationError'; + this.originalError = originalError; + } +} + +/** + * JSON validation errors + * Thrown when parsed CSV data cannot be converted to valid JSON + * @category Error Classes + */ +class JsonValidationError extends CsvParsingError { + /** + * Create a JSON validation error + * @param {string} csvData - The CSV data that failed validation + * @param {Error} originalError - The underlying JSON parsing error + */ + constructor(csvData, originalError) { + const message = + `JSON validation error: The parsed CSV data generated invalid JSON.\n` + + `This typically indicates malformed field names or values in the CSV.\n` + + `Original error: ${originalError.message}\n\n` + + `Solutions:\n` + + ` 1. Check that field names are valid JavaScript identifiers (or will be converted safely)\n` + + ` 2. Review the CSV data for special characters that aren't properly escaped\n` + + ` 3. Enable supportQuotedField(true) for fields containing special characters\n` + + ` 4. Verify that formatValueByType() isn't converting values incorrectly`; + + super(message, 'JSON_VALIDATION_ERROR', { + originalError: originalError.message, + csvPreview: csvData ? csvData.substring(0, 200) : 'N/A' + }); + this.name = 'JsonValidationError'; + this.originalError = originalError; + } +} + +/** + * Browser-specific errors + * Thrown when browser API operations fail + * @category Error Classes + */ +class BrowserApiError extends CsvParsingError { + /** + * Create a browser API error + * @param {string} message - Error message + * @param {object} context - Additional context information (optional) + */ + constructor(message, context = {}) { + super(message, 'BROWSER_API_ERROR', context); + this.name = 'BrowserApiError'; + } + + /** + * Create error for unavailable FileReader API + * Occurs when browser doesn't support FileReader + * @returns {BrowserApiError} Configured error instance + * @static + */ + static fileReaderNotAvailable() { + return new BrowserApiError( + `Browser compatibility error: FileReader API is not available.\n` + + `Your browser does not support the FileReader API required for file parsing.\n\n` + + `Solutions:\n` + + ` 1. Use a modern browser that supports FileReader (Chrome 13+, Firefox 10+, Safari 6+)\n` + + ` 2. Consider using csvStringToJson() or csvStringToJsonAsync() for string-based parsing\n` + + ` 3. Implement a polyfill or alternative file reading method` + ); + } + + /** + * Create error for file parsing failure in browser + * Occurs when file read or CSV parse fails + * @param {Error} originalError - The underlying error that occurred + * @returns {BrowserApiError} Configured error instance + * @static + */ + static parseFileError(originalError) { + return new BrowserApiError( + `Browser file parsing error: Failed to read and parse the file.\n` + + `Error details: ${originalError.message}\n\n` + + `Solutions:\n` + + ` 1. Verify the file is a valid CSV file\n` + + ` 2. Check the file encoding (UTF-8 is recommended)\n` + + ` 3. Try a smaller file to isolate the issue\n` + + ` 4. Check browser console for additional error details`, + { originalError: originalError.message } + ); + } + + /** + * Create error for unsupported streaming API in browser + * Occurs when browser doesn't support ReadableStream + * @returns {BrowserApiError} Configured error instance + * @static + */ + static streamingNotSupported() { + return new BrowserApiError( + `Browser compatibility error: ReadableStream API is not available.\n` + + `Your browser does not support the ReadableStream API required for streaming.\n\n` + + `Solutions:\n` + + ` 1. Use a modern browser that supports ReadableStream (Chrome 43+, Firefox 65+, Safari 10.1+)\n` + + ` 2. Use getJsonFromFileStreamingAsync() which falls back to regular file parsing\n` + + ` 3. Consider using parseFile() for non-streaming file parsing\n` + + ` 4. Implement a polyfill for ReadableStream support` + ); + } +} + +module.exports = { + CsvParsingError, + InputValidationError, + ConfigurationError, + CsvFormatError, + FileOperationError, + JsonValidationError, + BrowserApiError +}; + +},{}],7:[function(require,module,exports){ +'use strict'; + +const fs = require('fs'); +const { FileOperationError } = require('./errors'); + +/** + * File I/O utilities for reading and writing CSV/JSON files + * Provides both synchronous and asynchronous file operations + * @category Utilities + */ +class FileUtils { + + /** + * Read a file synchronously with specified encoding + * @param {string} fileInputName - Path to file to read + * @param {string} encoding - File encoding (e.g., 'utf8', 'latin1') + * @returns {string} File contents as string + * @throws {FileOperationError} If file read fails + */ + readFile(fileInputName, encoding) { + try { + return fs.readFileSync(fileInputName, encoding).toString(); + } catch (error) { + throw new FileOperationError('read', fileInputName, error); + } + } + + /** + * Read a file asynchronously with specified encoding + * Uses fs.promises when available, falls back to callback-based API + * @param {string} fileInputName - Path to file to read + * @param {string} encoding - File encoding (default: 'utf8') + * @returns {Promise} Promise resolving to file contents + * @throws {FileOperationError} If file read fails + */ + readFileAsync(fileInputName, encoding = 'utf8') { + // Use fs.promises when available for a Promise-based API + if (fs.promises && typeof fs.promises.readFile === 'function') { + return fs.promises.readFile(fileInputName, encoding) + .then(buf => buf.toString()) + .catch(error => { + throw new FileOperationError('read', fileInputName, error); + }); + } + return new Promise((resolve, reject) => { + fs.readFile(fileInputName, encoding, (err, data) => { + if (err) { + reject(new FileOperationError('read', fileInputName, err)); + return; + } + resolve(data.toString()); + }); + }); + } + + /** + * Write content to a file synchronously + * Logs confirmation message to console on success + * @param {string} json - Content to write to file + * @param {string} fileOutputName - Path to output file + * @throws {FileOperationError} If file write fails + */ + writeFile(json, fileOutputName) { + fs.writeFile(fileOutputName, json, function (err) { + if (err) { + throw new FileOperationError('write', fileOutputName, err); + } else { + console.log('File saved: ' + fileOutputName); + } + }); + } + + /** + * Write content to a file asynchronously + * Uses fs.promises when available, falls back to callback-based API + * @param {string} json - Content to write to file + * @param {string} fileOutputName - Path to output file + * @returns {Promise} Promise that resolves when write completes + * @throws {FileOperationError} If file write fails + */ + writeFileAsync(json, fileOutputName) { + if (fs.promises && typeof fs.promises.writeFile === 'function') { + return fs.promises.writeFile(fileOutputName, json) + .catch(error => { + throw new FileOperationError('write', fileOutputName, error); + }); + } + return new Promise((resolve, reject) => { + fs.writeFile(fileOutputName, json, (err) => { + if (err) return reject(new FileOperationError('write', fileOutputName, err)); + resolve(); + }); + }); + } + +} +module.exports = new FileUtils(); + +},{"./errors":6,"fs":2}],8:[function(require,module,exports){ +'use strict'; + +const { JsonValidationError } = require('./errors'); + +/** + * JSON validation utilities + * @category Utilities + */ +class JsonUtil { + + /** + * Validate that a string is valid JSON + * @param {string} json - JSON string to validate + * @throws {JsonValidationError} If JSON is invalid + */ + validateJson(json) { + try { + JSON.parse(json); + } catch (err) { + throw new JsonValidationError(json, err); + } + } + +} + +module.exports = new JsonUtil(); +},{"./errors":6}],9:[function(require,module,exports){ +'use strict'; + +/** + * String processing utilities for CSV parsing + * @category Utilities + */ +class StringUtils { + // Regular expressions as constants for better maintainability + static PATTERNS = { + INTEGER: /^-?\d+$/, + FLOAT: /^-?\d*\.\d+$/, + WHITESPACE: /\s/g + }; + + static BOOLEAN_VALUES = { + TRUE: 'true', + FALSE: 'false' + }; + + /** + * Removes whitespace from property names based on configuration + * @param {boolean} shouldTrimAll - If true, removes all whitespace, otherwise only trims edges + * @param {string} propertyName - The property name to process + * @returns {string} The processed property name + */ + trimPropertyName(shouldTrimAll, propertyName) { + if (!propertyName) { + return ''; + } + return shouldTrimAll ? + propertyName.replace(StringUtils.PATTERNS.WHITESPACE, '') : + propertyName.trim(); + } + + /** + * Converts a string value to its appropriate type while preserving data integrity + * @param {string} value - The input value to convert + * @returns {string|number|boolean} The converted value + */ + getValueFormatByType(value) { + if (this.isEmpty(value)) { + return String(); + } + + if (this.isBoolean(value)) { + return this.convertToBoolean(value); + } + + if (this.isInteger(value)) { + return this.convertInteger(value); + } + + if (this.isFloat(value)) { + return this.convertFloat(value); + } + + return String(value); + } + + /** + * Checks if a value array contains any non-empty values + * @param {Array} values - Array to check for content + * @returns {boolean} True if array has any non-empty values + */ + hasContent(values = []) { + return Array.isArray(values) && + values.some(value => Boolean(value)); + } + + // Private helper methods for type checking and conversion + /** + * Check if a value is empty (undefined or empty string) + * @param {unknown} value - Value to check + * @returns {boolean} True if value is undefined or empty string + * @private + */ + isEmpty(value) { + return value === undefined || value === ''; + } + + /** + * Check if a value is a boolean string ('true' or 'false', case-insensitive) + * @param {string} value - Value to check + * @returns {boolean} True if value is 'true' or 'false' + * @private + */ + isBoolean(value) { + const normalizedValue = value.toLowerCase(); + return normalizedValue === StringUtils.BOOLEAN_VALUES.TRUE || + normalizedValue === StringUtils.BOOLEAN_VALUES.FALSE; + } + + /** + * Check if a value is an integer string (with optional leading minus sign) + * @param {string} value - Value to check + * @returns {boolean} True if value matches integer pattern + * @private + */ + isInteger(value) { + return StringUtils.PATTERNS.INTEGER.test(value); + } + + /** + * Check if a value is a float string (decimal number with optional leading minus sign) + * @param {string} value - Value to check + * @returns {boolean} True if value matches float pattern + * @private + */ + isFloat(value) { + return StringUtils.PATTERNS.FLOAT.test(value); + } + + /** + * Check if a numeric string has a leading zero (e.g., '01' or '-01') + * Leading zeros indicate the value should be kept as a string to preserve formatting + * @param {string} value - Numeric string value to check + * @returns {boolean} True if value has a leading zero + * @private + */ + hasLeadingZero(value) { + const isPositiveWithLeadingZero = value.length > 1 && value[0] === '0'; + const isNegativeWithLeadingZero = value.length > 2 && value[0] === '-' && value[1] === '0'; + return isPositiveWithLeadingZero || isNegativeWithLeadingZero; + } + + /** + * Convert a boolean string to native boolean value + * Safely converts 'true' to true and 'false' to false + * @param {string} value - Boolean string ('true' or 'false') + * @returns {boolean} Native boolean value + * @private + */ + convertToBoolean(value) { + return JSON.parse(value.toLowerCase()); + } + + /** + * Convert an integer string to number or keep as string if it has leading zeros + * Preserves leading zeros in strings (e.g., '007' stays as string) + * @param {string} value - Integer string to convert + * @returns {number|string} Number if safe, otherwise string value + * @private + */ + convertInteger(value) { + if (this.hasLeadingZero(value)) { + return String(value); + } + + const num = Number(value); + return Number.isSafeInteger(num) ? num : String(value); + } + + /** + * Convert a float string to number or keep as string if conversion is unsafe + * @param {string} value - Float string to convert + * @returns {number|string} Number if finite and valid, otherwise string value + * @private + */ + convertFloat(value) { + const num = Number(value); + return Number.isFinite(num) ? num : String(value); + } +} + +module.exports = new StringUtils(); + +},{}]},{},[1]); diff --git a/docs/index.html b/docs/index.html index 60b829c..13106ca 100644 --- a/docs/index.html +++ b/docs/index.html @@ -417,6 +417,31 @@ gap: 10px; } } + + .chunked-options { + margin-left: 20px; + margin-top: 10px; + padding: 10px; + background-color: #f8f9fa; + border-radius: 4px; + border-left: 3px solid #007bff; + } + + .chunked-options label { + display: inline-block; + margin-right: 10px; + font-weight: normal; + } + + .chunked-options input { + width: 80px; + display: inline-block; + } + + .chunked-options small { + color: #6c757d; + font-size: 0.85em; + } @@ -449,6 +474,8 @@

CSV to JSON Converter

+ + @@ -479,6 +506,29 @@

Parsing Options

+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ +
@@ -532,6 +582,400 @@

Parsing Options

+ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 43576f3..f605f74 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@eslint/js": "^10.0.1", "@types/jest": "^30.0.0", "better-docs": "^2.7.3", + "browserify": "^17.0.1", "eslint": "^10.1.0", "eslint-plugin-jsdoc": "^62.8.0", "jest": "^30.2.0", @@ -1782,6 +1783,41 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "node_modules/acorn-node/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -1896,6 +1932,53 @@ "dev": true, "license": "MIT" }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/assert": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.1.tgz", + "integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "object.assign": "^4.1.4", + "util": "^0.10.4" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, "node_modules/ast-types": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", @@ -1909,6 +1992,22 @@ "node": ">=4" } }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/babel-jest": { "version": "30.3.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", @@ -2046,6 +2145,27 @@ "dev": true, "license": "MIT" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.8.30", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", @@ -2101,6 +2221,13 @@ "dev": true, "license": "MIT" }, + "node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "dev": true, + "license": "MIT" + }, "node_modules/brace": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", @@ -2117,6 +2244,243 @@ "balanced-match": "^1.0.0" } }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/browser-pack": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", + "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "combine-source-map": "~0.8.0", + "defined": "^1.0.0", + "JSONStream": "^1.0.3", + "safe-buffer": "^5.1.1", + "through2": "^2.0.0", + "umd": "^3.0.0" + }, + "bin": { + "browser-pack": "bin/cmd.js" + } + }, + "node_modules/browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.17.0" + } + }, + "node_modules/browserify": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-17.0.1.tgz", + "integrity": "sha512-pxhT00W3ylMhCHwG5yfqtZjNnFuX5h2IJdaBfSo4ChaaBsIp9VLrEMQ1bHV+Xr1uLPXuNDDM1GlJkjli0qkRsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^2.0.0", + "browserify-zlib": "~0.2.0", + "buffer": "~5.2.1", + "cached-path-relative": "^1.0.0", + "concat-stream": "^1.6.0", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.1", + "domain-browser": "^1.2.0", + "duplexer2": "~0.1.2", + "events": "^3.0.0", + "glob": "^7.1.0", + "hasown": "^2.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.2.1", + "JSONStream": "^1.0.3", + "labeled-stream-splicer": "^2.0.0", + "mkdirp-classic": "^0.5.2", + "module-deps": "^6.2.3", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "^1.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum-object": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^3.0.0", + "stream-http": "^3.0.0", + "string_decoder": "^1.1.1", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "0.0.1", + "url": "~0.11.0", + "util": "~0.12.0", + "vm-browserify": "^1.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "browserify": "bin/cmd.js" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", + "dev": true, + "license": "ISC", + "dependencies": { + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/browserify/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserify/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/browserify/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/browserify/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, "node_modules/browserslist": { "version": "4.28.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", @@ -2172,12 +2536,37 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/c8": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/c8/-/c8-7.14.0.tgz", @@ -2379,6 +2768,32 @@ "node": ">=10" } }, + "node_modules/cached-path-relative": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", + "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -2540,6 +2955,21 @@ "node": ">=8" } }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cjs-module-lexer": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", @@ -2665,6 +3095,43 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + } + }, + "node_modules/combine-source-map/node_modules/convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/combine-source-map/node_modules/lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/combine-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -2688,6 +3155,28 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, "node_modules/constantinople": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", @@ -2701,6 +3190,13 @@ "babylon": "^6.18.0" } }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -2716,20 +3212,108 @@ "hasInstallScript": true, "license": "MIT" }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true, + "license": "MIT" + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { "node": ">= 8" } }, + "node_modules/crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -2795,6 +3379,79 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deps-sort": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", + "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "JSONStream": "^1.0.3", + "shasum-object": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^2.0.0" + }, + "bin": { + "deps-sort": "bin/cmd.js" + } + }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2804,6 +3461,24 @@ "node": ">=8" } }, + "node_modules/detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "detective": "bin/detective.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/diff-match-patch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", @@ -2811,6 +3486,25 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2831,6 +3525,17 @@ "dev": true, "license": "MIT" }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2846,6 +3551,16 @@ "node": ">= 0.4" } }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2859,6 +3574,29 @@ "dev": true, "license": "ISC" }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -3313,6 +4051,27 @@ "node": ">=0.10.0" } }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3388,6 +4147,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -3445,6 +4211,22 @@ "dev": true, "license": "ISC" }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -3493,6 +4275,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3502,6 +4294,13 @@ "node": ">=6.9.0" } }, + "node_modules/get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3655,6 +4454,19 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3684,6 +4496,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/hash-sum": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", @@ -3691,6 +4517,17 @@ "dev": true, "license": "MIT" }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3714,6 +4551,18 @@ "he": "bin/he" } }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", @@ -3737,6 +4586,23 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/htmlescape": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "dev": true, + "license": "MIT" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3746,6 +4612,27 @@ "node": ">=10.17.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3804,6 +4691,65 @@ "dev": true, "license": "ISC" }, + "node_modules/inline-source-map": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.3.tgz", + "integrity": "sha512-1aVsPEsJWMJq/pdMU61CDlm1URcW702MTB4w9/zUjMus6H/Py8o7g68Pr9D4I6QluWGt/KdmswuRhaA05xVR1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.5.3" + } + }, + "node_modules/inline-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/insert-module-globals": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.1.tgz", + "integrity": "sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-node": "^1.5.2", + "combine-source-map": "^0.8.0", + "concat-stream": "^1.6.1", + "is-buffer": "^1.1.0", + "JSONStream": "^1.0.3", + "path-is-absolute": "^1.0.1", + "process": "~0.11.0", + "through2": "^2.0.0", + "undeclared-identifiers": "^1.1.2", + "xtend": "^4.0.0" + }, + "bin": { + "insert-module-globals": "bin/cmd.js" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3817,6 +4763,19 @@ "dev": true, "license": "MIT" }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -3885,6 +4844,26 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3936,6 +4915,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4763,6 +5765,33 @@ "node": ">=6" } }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", @@ -4807,6 +5836,17 @@ "graceful-fs": "^4.1.9" } }, + "node_modules/labeled-stream-splicer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", + "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "stream-splicer": "^2.0.0" + } + }, "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -5031,6 +6071,18 @@ "node": ">= 0.4" } }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -5044,6 +6096,27 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -5063,6 +6136,20 @@ "node": ">=4" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true, + "license": "MIT" + }, "node_modules/minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -5109,6 +6196,43 @@ "node": ">=10" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/module-deps": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", + "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browser-resolve": "^2.0.0", + "cached-path-relative": "^1.0.2", + "concat-stream": "~1.6.0", + "defined": "^1.0.0", + "detective": "^5.2.0", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "JSONStream": "^1.0.3", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.4.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "bin": { + "module-deps": "bin/cmd.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5231,6 +6355,50 @@ "dev": true, "license": "MIT" }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5274,6 +6442,13 @@ "node": ">= 0.8.0" } }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true, + "license": "MIT" + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5334,6 +6509,40 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha512-mXKF3xkoUt5td2DoxpLmtOmZvko9VfFpwRwkKDHSNvgmpLAeBo18YDhcPbBzJq+QLCHMbGOfzia2cX4U+0v9Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-platform": "~0.11.15" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/parse-imports-exports": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", @@ -5369,6 +6578,13 @@ "dev": true, "license": "MIT" }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5405,6 +6621,16 @@ "dev": true, "license": "MIT" }, + "node_modules/path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -5427,6 +6653,24 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true }, + "node_modules/pbkdf2": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -5467,6 +6711,16 @@ "node": ">=8" } }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5514,6 +6768,23 @@ "node": ">= 0.6" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -5550,6 +6821,28 @@ "dev": true, "license": "ISC" }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true, + "license": "MIT" + }, "node_modules/pug": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz", @@ -5799,6 +7092,52 @@ } ] }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "node_modules/react": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", @@ -5892,6 +7231,49 @@ "dev": true, "license": "MIT" }, + "node_modules/read-only-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/recast": { "version": "0.17.6", "resolved": "https://registry.npmjs.org/recast/-/recast-0.17.6.tgz", @@ -6085,6 +7467,75 @@ "node": "*" } }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripemd160/node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", @@ -6106,6 +7557,58 @@ "semver": "bin/semver.js" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shasum-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.1.tgz", + "integrity": "sha512-SsC+1tW7XKQ/94D4k1JhLmjDFpVGET/Nf54jVDtbavbALf8Zhp0Td9zTlxScjMW6nbEIrpADtPWfLk9iCXzHDQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "fast-safe-stringify": "^2.0.7" + }, + "bin": { + "shasum-object": "bin.js" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6127,6 +7630,95 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -6139,6 +7731,27 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -6181,35 +7794,121 @@ "dev": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, + "node_modules/stream-browserify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + } + }, + "node_modules/stream-http/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-splicer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", + "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" + "safe-buffer": "~5.2.0" } }, "node_modules/string-length": { @@ -6385,6 +8084,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.1.0" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -6428,6 +8137,16 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-node": "^1.2.0" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -6488,12 +8207,64 @@ "node": "*" } }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha512-PIxwAupJZiYU4JmVZYwXp9FKsHMXb5h0ZEFyuXTAn8WLHOlcij+FEcbrvDsom1o5dr1YggEtFbECvGCW2sT53Q==", + "dev": true, + "dependencies": { + "process": "~0.11.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/to-buffer/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, "node_modules/to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -6617,6 +8388,13 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, + "node_modules/tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true, + "license": "MIT" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6651,6 +8429,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true, + "license": "MIT" + }, "node_modules/typescript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", @@ -6692,6 +8492,33 @@ "license": "MIT", "optional": true }, + "node_modules/umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", + "dev": true, + "license": "MIT", + "bin": { + "umd": "bin/cli.js" + } + }, + "node_modules/undeclared-identifiers": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", + "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + }, + "bin": { + "undeclared-identifiers": "bin.js" + } + }, "node_modules/underscore": { "version": "1.13.8", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", @@ -6780,6 +8607,48 @@ "punycode": "^2.1.0" } }, + "node_modules/url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -6794,6 +8663,13 @@ "node": ">=10.12.0" } }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -6910,6 +8786,28 @@ "node": ">= 8" } }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -7075,6 +8973,16 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -8426,6 +10334,31 @@ "dev": true, "requires": {} }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "dev": true, + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + }, "ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -8504,6 +10437,52 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true + } + } + }, + "assert": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.1.tgz", + "integrity": "sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==", + "dev": true, + "requires": { + "object.assign": "^4.1.4", + "util": "^0.10.4" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dev": true, + "requires": { + "inherits": "2.0.3" + } + } + } + }, "ast-types": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", @@ -8513,6 +10492,15 @@ "tslib": "^2.0.1" } }, + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, "babel-jest": { "version": "30.3.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", @@ -8617,6 +10605,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true + }, "baseline-browser-mapping": { "version": "2.8.30", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", @@ -8653,6 +10647,12 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "dev": true + }, "brace": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", @@ -8668,6 +10668,206 @@ "balanced-match": "^1.0.0" } }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true + }, + "browser-pack": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", + "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", + "dev": true, + "requires": { + "combine-source-map": "~0.8.0", + "defined": "^1.0.0", + "JSONStream": "^1.0.3", + "safe-buffer": "^5.1.1", + "through2": "^2.0.0", + "umd": "^3.0.0" + } + }, + "browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "dev": true, + "requires": { + "resolve": "^1.17.0" + } + }, + "browserify": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-17.0.1.tgz", + "integrity": "sha512-pxhT00W3ylMhCHwG5yfqtZjNnFuX5h2IJdaBfSo4ChaaBsIp9VLrEMQ1bHV+Xr1uLPXuNDDM1GlJkjli0qkRsw==", + "dev": true, + "requires": { + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^2.0.0", + "browserify-zlib": "~0.2.0", + "buffer": "~5.2.1", + "cached-path-relative": "^1.0.0", + "concat-stream": "^1.6.0", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.1", + "domain-browser": "^1.2.0", + "duplexer2": "~0.1.2", + "events": "^3.0.0", + "glob": "^7.1.0", + "hasown": "^2.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.2.1", + "JSONStream": "^1.0.3", + "labeled-stream-splicer": "^2.0.0", + "mkdirp-classic": "^0.5.2", + "module-deps": "^6.2.3", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "^1.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum-object": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^3.0.0", + "stream-http": "^3.0.0", + "string_decoder": "^1.1.1", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "0.0.1", + "url": "~0.11.0", + "util": "~0.12.0", + "vm-browserify": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "dev": true, + "requires": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + } + }, + "browserify-sign": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.5.tgz", + "integrity": "sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==", + "dev": true, + "requires": { + "bn.js": "^5.2.2", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, "browserslist": { "version": "4.28.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", @@ -8696,7 +10896,17 @@ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, "requires": { - "node-int64": "^0.4.0" + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" } }, "buffer-from": { @@ -8705,6 +10915,18 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==", + "dev": true + }, "c8": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/c8/-/c8-7.14.0.tgz", @@ -8846,6 +11068,24 @@ } } }, + "cached-path-relative": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.1.0.tgz", + "integrity": "sha512-WF0LihfemtesFcJgO7xfOoOcnWzY/QHR4qeDqV44jPU3HTI54+LnfXK3SA27AVVGCdZFgjjFFaqUA9Jx7dMJZA==", + "dev": true + }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + } + }, "call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -8945,6 +11185,17 @@ "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "dev": true }, + "cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + } + }, "cjs-module-lexer": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", @@ -9043,6 +11294,38 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==", + "dev": true, + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + }, + "dependencies": { + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==", + "dev": true + }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -9061,6 +11344,24 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, "constantinople": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", @@ -9073,6 +11374,12 @@ "babylon": "^6.18.0" } }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==", + "dev": true + }, "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -9085,6 +11392,57 @@ "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", "dev": true }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, "cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -9096,6 +11454,32 @@ "which": "^2.0.1" } }, + "crypto-browserify": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.1.tgz", + "integrity": "sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.1", + "browserify-sign": "^4.2.3", + "create-ecdh": "^4.0.4", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "diffie-hellman": "^5.0.3", + "hash-base": "~3.0.4", + "inherits": "^2.0.4", + "pbkdf2": "^3.1.2", + "public-encrypt": "^4.0.3", + "randombytes": "^2.1.0", + "randomfill": "^1.0.4" + } + }, + "dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==", + "dev": true + }, "de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", @@ -9136,18 +11520,98 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "defined": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", + "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", + "dev": true + }, + "deps-sort": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.1.tgz", + "integrity": "sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==", + "dev": true, + "requires": { + "JSONStream": "^1.0.3", + "shasum-object": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^2.0.0" + } + }, + "des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true }, + "detective": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz", + "integrity": "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==", + "dev": true, + "requires": { + "acorn-node": "^1.8.2", + "defined": "^1.0.0", + "minimist": "^1.2.6" + } + }, "diff-match-patch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", "dev": true }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true + } + } + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -9163,6 +11627,12 @@ "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", "dev": true }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, "dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -9174,6 +11644,15 @@ "gopd": "^1.2.0" } }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -9186,6 +11665,29 @@ "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", "dev": true }, + "elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "dev": true, + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true + } + } + }, "emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -9470,6 +11972,22 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -9533,6 +12051,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true + }, "fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -9577,6 +12101,15 @@ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true }, + "for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "requires": { + "is-callable": "^1.2.7" + } + }, "foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -9606,12 +12139,24 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, + "generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, + "get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -9712,6 +12257,15 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "requires": { + "es-define-property": "^1.0.0" + } + }, "has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -9727,12 +12281,32 @@ "has-symbols": "^1.0.3" } }, + "hash-base": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + } + }, "hash-sum": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", "dev": true }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -9748,6 +12322,17 @@ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", @@ -9760,12 +12345,30 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "htmlescape": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==", + "dev": true + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==", + "dev": true + }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true + }, "ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -9804,6 +12407,51 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "inline-source-map": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.3.tgz", + "integrity": "sha512-1aVsPEsJWMJq/pdMU61CDlm1URcW702MTB4w9/zUjMus6H/Py8o7g68Pr9D4I6QluWGt/KdmswuRhaA05xVR1w==", + "dev": true, + "requires": { + "source-map": "~0.5.3" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, + "insert-module-globals": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.1.tgz", + "integrity": "sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==", + "dev": true, + "requires": { + "acorn-node": "^1.5.2", + "combine-source-map": "^0.8.0", + "concat-stream": "^1.6.1", + "is-buffer": "^1.1.0", + "JSONStream": "^1.0.3", + "path-is-absolute": "^1.0.1", + "process": "~0.11.0", + "through2": "^2.0.0", + "undeclared-identifiers": "^1.1.2", + "xtend": "^4.0.0" + } + }, + "is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -9816,6 +12464,12 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, "is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -9861,6 +12515,19 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true }, + "is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "requires": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + } + }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -9894,6 +12561,21 @@ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, + "is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "requires": { + "which-typed-array": "^1.1.16" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -10519,6 +13201,22 @@ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, "jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", @@ -10556,6 +13254,16 @@ "graceful-fs": "^4.1.9" } }, + "labeled-stream-splicer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", + "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "stream-splicer": "^2.0.0" + } + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -10723,6 +13431,17 @@ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -10735,6 +13454,24 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true + } + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -10747,6 +13484,18 @@ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", "dev": true }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true + }, "minimatch": { "version": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", @@ -10774,6 +13523,35 @@ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true + }, + "module-deps": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", + "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", + "dev": true, + "requires": { + "browser-resolve": "^2.0.0", + "cached-path-relative": "^1.0.2", + "concat-stream": "~1.6.0", + "defined": "^1.0.0", + "detective": "^5.2.0", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "JSONStream": "^1.0.3", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.4.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + } + }, "ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -10867,6 +13645,32 @@ "integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==", "dev": true }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "requires": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -10899,6 +13703,12 @@ "word-wrap": "^1.2.5" } }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==", + "dev": true + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -10940,6 +13750,34 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "dev": true }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha512-mXKF3xkoUt5td2DoxpLmtOmZvko9VfFpwRwkKDHSNvgmpLAeBo18YDhcPbBzJq+QLCHMbGOfzia2cX4U+0v9Mg==", + "dev": true, + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "dev": true, + "requires": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + } + }, "parse-imports-exports": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", @@ -10967,6 +13805,12 @@ "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", "dev": true }, + "path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -10991,6 +13835,12 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==", + "dev": true + }, "path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -11009,6 +13859,20 @@ } } }, + "pbkdf2": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", + "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", + "dev": true, + "requires": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.1" + } + }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -11036,6 +13900,12 @@ "find-up": "^4.0.0" } }, + "possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11067,6 +13937,18 @@ "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -11101,6 +13983,28 @@ "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", "dev": true }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.12.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.3.tgz", + "integrity": "sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==", + "dev": true + } + } + }, "pug": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.4.tgz", @@ -11299,6 +14203,40 @@ "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true }, + "qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "requires": { + "side-channel": "^1.1.0" + } + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "react": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", @@ -11366,6 +14304,47 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true }, + "read-only-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha512-3ALe0bjBVZtkdWKIcThYpQCLbBMd/+Tbh2CDSrAIDO3UsZ4Xs+tnyjv2MjCOMMgBG+AsUOeuP1cgtY1INISc8w==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "recast": { "version": "0.17.6", "resolved": "https://registry.npmjs.org/recast/-/recast-0.17.6.tgz", @@ -11498,6 +14477,47 @@ } } }, + "ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "dev": true, + "requires": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "dependencies": { + "hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + } + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + } + }, "scheduler": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", @@ -11515,6 +14535,40 @@ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + } + }, + "shasum-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/shasum-object/-/shasum-object-1.0.1.tgz", + "integrity": "sha512-SsC+1tW7XKQ/94D4k1JhLmjDFpVGET/Nf54jVDtbavbALf8Zhp0Td9zTlxScjMW6nbEIrpADtPWfLk9iCXzHDQ==", + "dev": true, + "requires": { + "fast-safe-stringify": "^2.0.7" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -11530,12 +14584,72 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true + }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + } + }, "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -11595,6 +14709,83 @@ "escape-string-regexp": "^2.0.0" } }, + "stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "dev": true, + "requires": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "stream-http": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "stream-splicer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", + "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -11720,6 +14911,15 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==", + "dev": true, + "requires": { + "minimist": "^1.1.0" + } + }, "supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -11744,6 +14944,15 @@ "@pkgr/core": "^0.2.9" } }, + "syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "dev": true, + "requires": { + "acorn-node": "^1.2.0" + } + }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -11790,12 +14999,56 @@ } } }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha512-PIxwAupJZiYU4JmVZYwXp9FKsHMXb5h0ZEFyuXTAn8WLHOlcij+FEcbrvDsom1o5dr1YggEtFbECvGCW2sT53Q==", + "dev": true, + "requires": { + "process": "~0.11.0" + } + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, + "to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "dev": true, + "requires": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -11861,6 +15114,12 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, + "tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", + "dev": true + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -11882,6 +15141,23 @@ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true }, + "typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "requires": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, "typescript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", @@ -11908,6 +15184,25 @@ "dev": true, "optional": true }, + "umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", + "dev": true + }, + "undeclared-identifiers": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", + "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", + "dev": true, + "requires": { + "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + } + }, "underscore": { "version": "1.13.8", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", @@ -11967,6 +15262,43 @@ "punycode": "^2.1.0" } }, + "url": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", + "dev": true, + "requires": { + "punycode": "^1.4.1", + "qs": "^6.12.3" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", + "dev": true + } + } + }, + "util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -11978,6 +15310,12 @@ "convert-source-map": "^2.0.0" } }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, "void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -12069,6 +15407,21 @@ "isexe": "^2.0.0" } }, + "which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + } + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -12191,6 +15544,12 @@ "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index f886e92..ac62157 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@eslint/js": "^10.0.1", "@types/jest": "^30.0.0", "better-docs": "^2.7.3", + "browserify": "^17.0.1", "eslint": "^10.1.0", "eslint-plugin-jsdoc": "^62.8.0", "jest": "^30.2.0", diff --git a/src/browserApi.js b/src/browserApi.js index 42d1c6c..1b91063 100644 --- a/src/browserApi.js +++ b/src/browserApi.js @@ -273,7 +273,7 @@ class BrowserApi { } // Check if the file supports streaming - if (typeof file.stream === 'function' && file.hasOwnProperty('stream')) { + if (typeof file.stream === 'function') { // Use native streaming if available const stream = file.stream(); return this.getJsonFromStreamAsync(stream); @@ -283,6 +283,144 @@ class BrowserApi { } } + /** + * Parse CSV from a File object using streaming with progress callbacks for large files + * Processes data in chunks to avoid memory issues with large datasets + * @param {File} file - File object containing CSV data + * @param {object} options - Processing options + * @param {function(Array, number, number): void} options.onChunk - Callback for each chunk of processed rows + * @param {function(Array): void} [options.onComplete] - Callback when processing is complete + * @param {function(Error): void} [options.onError] - Callback for errors + * @param {number} [options.chunkSize=1000] - Number of rows per chunk + * @returns {Promise} Promise that resolves when streaming starts + * @throws {InputValidationError} If file or options are invalid + * @example + * const csvToJson = require('convert-csv-to-json'); + * const fileInput = document.querySelector('#csvfile').files[0]; + * + * await csvToJson.browser.getJsonFromFileStreamingAsyncWithCallback(fileInput, { + * chunkSize: 500, + * onChunk: (rows, processed, total) => { + * console.log(`Processed ${processed}/${total} rows`); + * // Handle chunk of rows here + * }, + * onComplete: (allRows) => { + * console.log('Processing complete!'); + * }, + * onError: (error) => { + * console.error('Error:', error); + * } + * }); + */ + async getJsonFromFileStreamingAsyncWithCallback(file, options = {}) { + if (!file || !(file instanceof File)) { + throw new InputValidationError( + 'file', + 'File object', + typeof file, + 'Provide a valid File object.' + ); + } + + if (!options.onChunk || typeof options.onChunk !== 'function') { + throw new InputValidationError( + 'options.onChunk', + 'function', + typeof options.onChunk, + 'Provide a callback function to handle processed chunks.' + ); + } + + const chunkSize = options.chunkSize || 1000; + const streamProcessor = new StreamProcessor(this.csvToJson, { + isBrowser: true, + chunkSize, + onChunk: options.onChunk, + onComplete: options.onComplete, + onError: options.onError + }); + + // Check if the file supports streaming + if (typeof file.stream === 'function') { + console.log('Using native streaming'); + // Use native streaming if available + const stream = file.stream(); + return streamProcessor.processStreamWithCallbacks(stream); + } else { + console.log('Falling back to FileReader'); + // Fallback to regular file parsing for older browsers + return this.parseFileWithCallbacks(file, options); + } + } + + /** + * Parse a File object with progress callbacks (fallback for non-streaming browsers) + * @param {File} file - File object to parse + * @param {object} options - Processing options + * @private + */ + async parseFileWithCallbacks(file, options) { + const chunkSize = options.chunkSize || 1000; + const onChunk = options.onChunk; + const onComplete = options.onComplete; + const onError = options.onError; + + return new Promise((resolve, reject) => { + if (typeof FileReader === 'undefined') { + const error = BrowserApiError.fileReaderNotAvailable(); + if (onError) onError(error); + reject(error); + return; + } + + const reader = new FileReader(); + reader.onerror = () => { + const error = BrowserApiError.parseFileError( + reader.error || new Error('Unknown file reading error') + ); + if (onError) onError(error); + reject(error); + }; + + reader.onload = () => { + try { + const text = reader.result; + const allRows = this.csvToJson.csvToJson(String(text)); + + // Process in chunks + let processed = 0; + const total = allRows.length; + + const processChunk = () => { + const chunk = allRows.slice(processed, processed + chunkSize); + if (chunk.length > 0) { + onChunk(chunk, processed + chunk.length, total); + processed += chunk.length; + // Use setTimeout to avoid blocking the UI + setTimeout(processChunk, 0); + } else { + if (onComplete) onComplete(allRows); + resolve(); + } + }; + + processChunk(); + } catch (err) { + const error = BrowserApiError.parseFileError(err); + if (onError) onError(error); + reject(error); + } + }; + + reader.readAsText(file); + }); + } + } module.exports = new BrowserApi(); + +// Assign to window for browser demo +if (typeof window !== 'undefined') { + window.csvToJson = module.exports; +} diff --git a/src/streamProcessor.js b/src/streamProcessor.js index 107890f..76e3195 100644 --- a/src/streamProcessor.js +++ b/src/streamProcessor.js @@ -24,6 +24,10 @@ class StreamProcessor { * @param {object} csvConfig - The CSV configuration object * @param {object} options - Environment options * @param {boolean} options.isBrowser - Whether running in browser environment + * @param {number} options.chunkSize - Number of rows per chunk for callback processing + * @param {function} options.onChunk - Callback for each chunk + * @param {function} options.onComplete - Callback when processing complete + * @param {function} options.onError - Callback for errors */ constructor(csvConfig, options = {}) { this.csvConfig = csvConfig; @@ -35,6 +39,13 @@ class StreamProcessor { this.currentRecordIndex = 0; this.parsedRecords = []; this.dataRowIndex = 0; + + // Chunked processing options + this.chunkSize = options.chunkSize || 1000; + this.onChunk = options.onChunk; + this.onComplete = options.onComplete; + this.onError = options.onError; + this.allRecords = []; // For collecting all records when using callbacks } /** @@ -60,6 +71,113 @@ class StreamProcessor { this._processCompleteRecords(); } + /** + * Process a stream with chunked callbacks (for large files) + * @param {Readable|ReadableStream} stream - The stream to process + * @returns {Promise} Promise that resolves when streaming starts + */ + async processStreamWithCallbacks(stream) { + return new Promise((resolve, reject) => { + if (this.isBrowser) { + // Browser ReadableStream + if (!stream || typeof stream.getReader !== 'function') { + const error = new Error('Invalid ReadableStream provided'); + if (this.onError) this.onError(error); + reject(error); + return; + } + + const reader = stream.getReader(); + + const processChunk = async () => { + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + this.finalizeProcessing(); + this._sendRemainingChunks(); + if (this.onComplete) this.onComplete(this.allRecords); + resolve(); + return; + } + + this.processChunk(value); + this._sendPendingChunks(); + } + } catch (error) { + if (this.onError) this.onError(error); + reject(error); + } + }; + + processChunk(); + } else { + // Node.js Readable stream + if (!stream || typeof stream.pipe !== 'function') { + const error = new Error('Invalid Readable stream provided'); + if (this.onError) this.onError(error); + reject(error); + return; + } + + stream.on('data', (chunk) => { + try { + this.processChunk(chunk); + this._sendPendingChunks(); + } catch (error) { + if (this.onError) this.onError(error); + reject(error); + } + }); + + stream.on('end', () => { + try { + this.finalizeProcessing(); + this._sendRemainingChunks(); + if (this.onComplete) this.onComplete(this.allRecords); + resolve(); + } catch (error) { + if (this.onError) this.onError(error); + reject(error); + } + }); + + stream.on('error', (error) => { + if (this.onError) this.onError(error); + reject(error); + }); + } + }); + } + + /** + * Send pending chunks when they reach the chunk size + * @private + */ + _sendPendingChunks() { + if (!this.onChunk) return; + + while (this.parsedRecords.length >= this.chunkSize) { + const chunk = this.parsedRecords.splice(0, this.chunkSize); + this.allRecords.push(...chunk); + this.onChunk(chunk, this.allRecords.length, null); // null for total when streaming + } + } + + /** + * Send any remaining chunks at the end of processing + * @private + */ + _sendRemainingChunks() { + if (!this.onChunk || this.parsedRecords.length === 0) return; + + const chunk = [...this.parsedRecords]; + this.parsedRecords.length = 0; // Clear the array + this.allRecords.push(...chunk); + this.onChunk(chunk, this.allRecords.length, this.allRecords.length); + } + /** * Process a stream directly (unified interface for both environments) * @param {Readable|ReadableStream} stream - The stream to process diff --git a/test/browserApi.spec.js b/test/browserApi.spec.js index a1784fc..e7d1646 100644 --- a/test/browserApi.spec.js +++ b/test/browserApi.spec.js @@ -186,9 +186,17 @@ describe('Browser API', () => { } } - const mockFile = Object.create(File.prototype); - mockFile.text = csv; - // Don't add stream method when ReadableStream is not available + const mockFile = { + text: csv, + // Mock File interface without stream method + size: csv.length, + type: 'text/csv', + name: 'test.csv' + }; + // Make it pass instanceof File check but don't inherit stream method + mockFile.__proto__ = File.prototype; + // Explicitly remove stream method for this test + mockFile.stream = undefined; const originalFileReader = global.FileReader; const originalReadableStream = global.ReadableStream; From 666971d0b8ac2b68644faaad5803bc6ed1618cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:13:51 +0000 Subject: [PATCH 06/18] fix demo samples --- docs/index.html | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/index.html b/docs/index.html index 13106ca..dae38e6 100644 --- a/docs/index.html +++ b/docs/index.html @@ -506,6 +506,7 @@

Parsing Options

+<<<<<<< Updated upstream
@@ -514,6 +515,8 @@

Parsing Options

+======= +>>>>>>> Stashed changes
@@ -613,16 +616,32 @@

Parsing Options

document.getElementById('quoted-fields').addEventListener('change', updateOptions); document.getElementById('delimiter').addEventListener('input', updateOptions); document.getElementById('header-index').addEventListener('input', updateOptions); +<<<<<<< Updated upstream document.getElementById('parse-subarray').addEventListener('change', updateOptions); document.getElementById('map-rows').addEventListener('change', updateOptions); document.getElementById('use-streaming').addEventListener('change', updateOptions); document.getElementById('use-chunked').addEventListener('change', toggleChunkedOptions); +======= + document.getElementById('use-streaming').addEventListener('change', updateOptions); + document.getElementById('use-chunked').addEventListener('change', toggleChunkedOptions); + +>>>>>>> Stashed changes // File input change document.getElementById('csv-file').addEventListener('change', handleFileSelect); // Ensure file-only options are hidden on initial load toggleFileOnlyOptions(); +<<<<<<< Updated upstream +======= + + // Sample data buttons + document.querySelectorAll('.sample-btn').forEach(btn => { + btn.addEventListener('click', function() { + loadSample(this.getAttribute('data-sample')); + }); + }); +>>>>>>> Stashed changes }); function showTab(tabName) { @@ -673,8 +692,11 @@

Parsing Options

const quotedFields = document.getElementById('quoted-fields').checked; const delimiter = document.getElementById('delimiter').value; const headerIndex = parseInt(document.getElementById('header-index').value) || 0; +<<<<<<< Updated upstream const parseSubarray = document.getElementById('parse-subarray').checked; const mapRows = document.getElementById('map-rows').checked; +======= +>>>>>>> Stashed changes csvToJson.formatValueByType(formatValues); csvToJson.supportQuotedField(quotedFields); @@ -710,6 +732,7 @@

Parsing Options

} } +<<<<<<< Updated upstream function loadSample(sampleType) { // Load sample data const samples = { @@ -720,6 +743,33 @@

Parsing Options

document.getElementById('csv-input').value = samples[sampleType] || ''; showTab('text'); } +======= + function loadSample(sampleName) { + const samples = { + basic: `name,age,city +John,25,New York +Jane,30,London +Bob,35,Paris`, + + quoted: `"name","age","description" +"John Doe","25","Software Engineer" +"Jane Smith","30","Product Manager"`, + + numbers: `product,price,quantity +Widget A,19.99,100 +Widget B,29.99,50 +Widget C,9.99,200`, + + dates: `event,date,attendees +Conference,2024-01-15,150 +Workshop,2024-02-20,75 +Seminar,2024-03-10,200`, + }; + + document.getElementById('csv-input').value = samples[sampleName] || ''; + showTab('text'); + } +>>>>>>> Stashed changes async function convert() { const output = document.getElementById('output'); From e249663aac581368f19a924ca3d82766237dc74e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:30:29 +0000 Subject: [PATCH 07/18] demo refactoring --- docs/csvToJsonDemo.js | 411 +++++++++++++++++++++++++++++++++++++++ docs/index.html | 442 +----------------------------------------- 2 files changed, 412 insertions(+), 441 deletions(-) create mode 100644 docs/csvToJsonDemo.js diff --git a/docs/csvToJsonDemo.js b/docs/csvToJsonDemo.js new file mode 100644 index 0000000..44150c3 --- /dev/null +++ b/docs/csvToJsonDemo.js @@ -0,0 +1,411 @@ +// Demo JavaScript code + csvToJson = window.csvToJson; + +// Initialize when DOM is loaded + document.addEventListener('DOMContentLoaded', function() { + // Tab switching + document.querySelectorAll('.tab').forEach(tab => { + tab.addEventListener('click', function() { + showTab(this.getAttribute('data-tab')); + }); + }); + + // Output tab switching + document.querySelectorAll('.output-tab').forEach(tab => { + tab.addEventListener('click', function() { + showOutputTab(this.getAttribute('data-output')); + }); + }); + + // Convert button + document.getElementById('convert-btn').addEventListener('click', convert); + + // Clear button + document.getElementById('clear-btn').addEventListener('click', clearAll); + + // Options change listeners + document.getElementById('format-values').addEventListener('change', updateOptions); + document.getElementById('quoted-fields').addEventListener('change', updateOptions); + document.getElementById('delimiter').addEventListener('input', updateOptions); + document.getElementById('header-index').addEventListener('input', updateOptions); + document.getElementById('use-streaming').addEventListener('change', updateOptions); + document.getElementById('use-chunked').addEventListener('change', toggleChunkedOptions); + + + // File input change + document.getElementById('csv-file').addEventListener('change', handleFileSelect); + + // Ensure file-only options are hidden on initial load + toggleFileOnlyOptions(); + + // Sample data buttons + document.querySelectorAll('.sample-btn').forEach(btn => { + btn.addEventListener('click', function() { + loadSample(this.getAttribute('data-sample')); + }); + }); + }); + + function showTab(tabName) { + // Hide all tabs + document.querySelectorAll('.tab-content').forEach(content => { + content.classList.remove('active'); + }); + document.querySelectorAll('.tab').forEach(tab => { + tab.classList.remove('active'); + }); + + // Show selected tab + document.getElementById(tabName + '-tab').classList.add('active'); + document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); + + // Toggle file-only options visibility when switching tabs + toggleFileOnlyOptions(); + } + + function toggleFileOnlyOptions() { + const activeTab = document.querySelector('.tab-content.active').id; + const fileOnlyOptions = document.getElementById('file-only-options'); + if (!fileOnlyOptions) return; + + if (activeTab === 'file-tab') { + fileOnlyOptions.style.display = 'block'; + } else { + fileOnlyOptions.style.display = 'none'; + } + } + + function showOutputTab(tabName) { + // Hide all output tabs + document.querySelectorAll('.output-content').forEach(content => { + content.classList.remove('active'); + }); + document.querySelectorAll('.output-tab').forEach(tab => { + tab.classList.remove('active'); + }); + + // Show selected output tab + document.getElementById(tabName + '-content').classList.add('active'); + document.querySelector(`[data-output="${tabName}"]`).classList.add('active'); + } + + function updateOptions() { + const formatValues = document.getElementById('format-values').checked; + const quotedFields = document.getElementById('quoted-fields').checked; + const delimiter = document.getElementById('delimiter').value; + const headerIndex = parseInt(document.getElementById('header-index').value) || 0; + + csvToJson.formatValueByType(formatValues); + csvToJson.supportQuotedField(quotedFields); + csvToJson.fieldDelimiter(delimiter); + csvToJson.indexHeader(headerIndex); + + if (parseSubarray) { + csvToJson.parseSubArray('*', ','); + } + + if (mapRows) { + // Add mapping function if needed + } + } + + function toggleChunkedOptions() { + const useChunked = document.getElementById('use-chunked').checked; + const chunkedOptions = document.getElementById('chunked-options'); + if (useChunked) { + chunkedOptions.style.display = 'block'; + } else { + chunkedOptions.style.display = 'none'; + } + } + + function handleFileSelect(event) { + const file = event.target.files[0]; + if (file) { + const fileInfo = document.getElementById('file-info'); + const size = (file.size / 1024 / 1024).toFixed(2); + fileInfo.innerHTML = `Selected file: ${file.name} (${size} MB)`; + fileInfo.style.display = 'block'; + } + } + + function loadSample(sampleName) { + const samples = { + basic: `name,age,city +John,25,New York +Jane,30,London +Bob,35,Paris`, + + quoted: `"name","age","description" +"John Doe","25","Software Engineer" +"Jane Smith","30","Product Manager"`, + + numbers: `product,price,quantity +Widget A,19.99,100 +Widget B,29.99,50 +Widget C,9.99,200`, + + dates: `event,date,attendees +Conference,2024-01-15,150 +Workshop,2024-02-20,75 +Seminar,2024-03-10,200`, + }; + + document.getElementById('csv-input').value = samples[sampleName] || ''; + showTab('text'); + } + + async function convert() { + const output = document.getElementById('output'); + const convertBtn = document.getElementById('convert-btn'); + + // Clear previous output + clearOutput(); + + // Disable button during conversion + convertBtn.disabled = true; + convertBtn.textContent = 'Converting...'; + + try { + let result; + + // Check which tab is active + const activeTab = document.querySelector('.tab-content.active').id; + const useStreaming = document.getElementById('use-streaming').checked; + + if (activeTab === 'text-tab') { + // Text input + const csvText = document.getElementById('csv-input').value; + if (!csvText.trim()) { + throw new Error('Please enter CSV text'); + } + result = csvToJson.csvStringToJson(csvText); + displayResult(result); + } else { + // File input + const fileInput = document.getElementById('csv-file'); + if (!fileInput.files[0]) { + throw new Error('Please select a CSV file'); + } + + const useChunked = document.getElementById('use-chunked').checked; + + if (useChunked) { + // Use chunked processing for large files + const chunkSize = parseInt(document.getElementById('chunk-size').value) || 1000; + await processFileInChunks(fileInput.files[0], chunkSize); + } else if (useStreaming) { + // Use streaming API + result = await csvToJson.getJsonFromFileStreamingAsync(fileInput.files[0]); + displayResult(result); + } else { + // Use regular file parsing + result = await csvToJson.parseFile(fileInput.files[0]); + displayResult(result); + } + } + } catch (error) { + displayError(error); + } finally { + // Re-enable button + convertBtn.disabled = false; + convertBtn.textContent = 'Convert to JSON'; + } + } + + async function processFileInChunks(file, chunkSize) { + const output = document.getElementById('output'); + const jsonOutput = document.getElementById('json-output'); + const tableOutput = document.getElementById('table-output'); + const statsOutput = document.getElementById('stats-output'); + + // Show results container + output.classList.remove('hidden'); + clearOutput(); + + // Initialize progress tracking + let allRows = []; + let totalProcessed = 0; + + // Create progress display + const progressDiv = document.createElement('div'); + progressDiv.id = 'progress-display'; + progressDiv.innerHTML = '

Processing large file...

Initializing...
'; + output.insertBefore(progressDiv, output.firstChild); + + try { + await csvToJson.getJsonFromFileStreamingAsyncWithCallback(file, { + chunkSize: chunkSize, + onChunk: (rows, processed, total) => { + console.log('onChunk called', rows.length, processed, total); + // Accumulate rows + allRows.push(...rows); + totalProcessed = processed; + + // Update progress + const progressPercent = total ? Math.round((processed / total) * 100) : Math.min(Math.round((processed / 10000) * 100), 95); + document.getElementById('progress-fill').style.width = progressPercent + '%'; + document.getElementById('progress-text').textContent = `Processed ${processed} rows...`; + + // For very large files, show the first 1000 rows only + if (allRows.length <= 1000) { + displayTable(allRows); + } else if (allRows.length === 1001) { + displayTable(allRows); + } + }, + onComplete: (allRowsComplete) => { + console.log('onComplete called', allRowsComplete.length); + allRows = allRowsComplete; + + // Update progress to 100% + document.getElementById('progress-fill').style.width = '100%'; + document.getElementById('progress-text').textContent = `Complete! Processed ${allRows.length} rows.`; + + // Display final results + jsonOutput.textContent = JSON.stringify(allRows, null, 2); + displayStats(allRows); + displayTable(allRows); + showOutputTab('table'); + + // Remove progress after a delay + setTimeout(() => { + const progressDisplay = document.getElementById('progress-display'); + if (progressDisplay) progressDisplay.remove(); + }, 3000); + }, + onError: (error) => { + console.error('Error:', error); + displayError(error); + const progressDisplay = document.getElementById('progress-display'); + if (progressDisplay) progressDisplay.remove(); + } + }); + } catch (error) { + console.error('Catch error:', error); + displayError(error); + const progressDisplay = document.getElementById('progress-display'); + if (progressDisplay) progressDisplay.remove(); + } + } + + function displayResult(result) { + const output = document.getElementById('output'); + const jsonOutput = document.getElementById('json-output'); + const tableOutput = document.getElementById('table-output'); + const statsOutput = document.getElementById('stats-output'); + + // Show results container + output.classList.remove('hidden'); + + // Display JSON + jsonOutput.textContent = JSON.stringify(result, null, 2); + + // Display table + displayTable(result); + + // Display stats + displayStats(result); + + // Show table tab by default + showOutputTab('table'); + } + + function displayTable(data) { + const tableOutput = document.getElementById('table-output'); + if (!data || data.length === 0) { + tableOutput.innerHTML = '

No data to display

'; + return; + } + + const maxRows = 1000; + const displayData = data.length > maxRows ? data.slice(0, maxRows) : data; + let html = ''; + + // Header + if (displayData.length > 0) { + html += ''; + Object.keys(displayData[0]).forEach(key => { + html += ``; + }); + html += ''; + } + + // Body + html += ''; + displayData.forEach(row => { + html += ''; + Object.values(row).forEach(value => { + html += ``; + }); + html += ''; + }); + html += '
${key}
${value}
'; + + if (data.length > maxRows) { + html = `

Showing first ${maxRows} rows only. Full data is available in JSON view and row count is shown in stats.

${html}`; + } + + tableOutput.innerHTML = html; + } + + function displayStats(data) { + const statsOutput = document.getElementById('stats-output'); + const rowCount = data ? data.length : 0; + const colCount = data && data.length > 0 ? Object.keys(data[0]).length : 0; + + statsOutput.innerHTML = ` +
Rows: ${rowCount}
+
Columns: ${colCount}
+ `; + } + + function displayError(error) { + const output = document.getElementById('output'); + const errorOutput = document.getElementById('error-output'); + + // Show results container + output.classList.remove('hidden'); + + // Display error + errorOutput.textContent = `Error: ${error.message}`; + errorOutput.classList.remove('hidden'); + } + + function clearOutput() { + const output = document.getElementById('output'); + const jsonOutput = document.getElementById('json-output'); + const tableOutput = document.getElementById('table-output'); + const statsOutput = document.getElementById('stats-output'); + const errorOutput = document.getElementById('error-output'); + + jsonOutput.textContent = ''; + tableOutput.innerHTML = ''; + statsOutput.innerHTML = ''; + errorOutput.textContent = ''; + errorOutput.classList.add('hidden'); + } + + function clearAll() { + clearOutput(); + document.getElementById('output').classList.add('hidden'); + document.getElementById('csv-input').value = ''; + document.getElementById('csv-file').value = ''; + document.getElementById('file-info').style.display = 'none'; + } + + function downloadJSON() { + const jsonOutput = document.getElementById('json-output'); + const data = jsonOutput.textContent; + if (!data) return; + + const blob = new Blob([data], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'data.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index dae38e6..ca0d4ca 100644 --- a/docs/index.html +++ b/docs/index.html @@ -585,447 +585,7 @@

Parsing Options

- + \ No newline at end of file From 569b894cf6a62599e8517dd412f20c213c95a7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Sun, 5 Apr 2026 16:32:27 +0000 Subject: [PATCH 08/18] fix jsdoc --- docs/index.html | 11 ----------- src/streamProcessor.js | 4 ---- 2 files changed, 15 deletions(-) diff --git a/docs/index.html b/docs/index.html index ca0d4ca..39654d3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -506,17 +506,6 @@

Parsing Options

-<<<<<<< Updated upstream -
- - -
-
- - -
-======= ->>>>>>> Stashed changes
diff --git a/src/streamProcessor.js b/src/streamProcessor.js index 76e3195..f3d9776 100644 --- a/src/streamProcessor.js +++ b/src/streamProcessor.js @@ -1,8 +1,4 @@ /* globals CsvFormatError */ -/** - * @typedef {import('stream').Readable} Readable - * @typedef {ReadableStream} ReadableStream - */ 'use strict'; const stringUtils = require('./util/stringUtils'); From 703b2d653152a67548cabb6748977736dad1cdd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:35:04 +0000 Subject: [PATCH 09/18] do not allow JSON Preview with more than 1000 lines --- docs/csvToJsonDemo.js | 19 +++++++++++++------ docs/index.html | 5 +++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/csvToJsonDemo.js b/docs/csvToJsonDemo.js index 44150c3..fb24a79 100644 --- a/docs/csvToJsonDemo.js +++ b/docs/csvToJsonDemo.js @@ -30,14 +30,14 @@ document.getElementById('header-index').addEventListener('input', updateOptions); document.getElementById('use-streaming').addEventListener('change', updateOptions); document.getElementById('use-chunked').addEventListener('change', toggleChunkedOptions); - + // File input change document.getElementById('csv-file').addEventListener('change', handleFileSelect); // Ensure file-only options are hidden on initial load toggleFileOnlyOptions(); - + // Sample data buttons document.querySelectorAll('.sample-btn').forEach(btn => { btn.addEventListener('click', function() { @@ -99,11 +99,11 @@ csvToJson.supportQuotedField(quotedFields); csvToJson.fieldDelimiter(delimiter); csvToJson.indexHeader(headerIndex); - + if (parseSubarray) { csvToJson.parseSubArray('*', ','); } - + if (mapRows) { // Add mapping function if needed } @@ -321,7 +321,7 @@ Seminar,2024-03-10,200`, const maxRows = 1000; const displayData = data.length > maxRows ? data.slice(0, maxRows) : data; let html = ''; - + // Header if (displayData.length > 0) { html += ''; @@ -353,11 +353,18 @@ Seminar,2024-03-10,200`, const statsOutput = document.getElementById('stats-output'); const rowCount = data ? data.length : 0; const colCount = data && data.length > 0 ? Object.keys(data[0]).length : 0; - + statsOutput.innerHTML = `
Rows: ${rowCount}
Columns: ${colCount}
`; + + if(rowCount > 1000){ + document.getElementById('output-tab-json').disabled = true; + document.getElementById('output-tab-json').title = "JSON preview is only available for files with fewer than 1,000 lines. Click the Download JSON button to view the result in JSON format."; + } else{ + document.getElementById('output-tab-json').disabled = false; + } } function displayError(error) { diff --git a/docs/index.html b/docs/index.html index 39654d3..afddd35 100644 --- a/docs/index.html +++ b/docs/index.html @@ -535,7 +535,7 @@

Parsing Options

- +
@@ -544,8 +544,9 @@

Parsing Options

-
+ +
From 9ebd88bd359ae5ab1aa604b6f2e813716ea0c5a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ant=C3=B2?= <19825793+iuccio@users.noreply.github.com> Date: Tue, 7 Apr 2026 12:36:33 +0000 Subject: [PATCH 10/18] formatting --- docs/csvToJsonDemo.js | 732 +++++++++++++++++++++--------------------- docs/index.html | 225 +++++++------ 2 files changed, 486 insertions(+), 471 deletions(-) diff --git a/docs/csvToJsonDemo.js b/docs/csvToJsonDemo.js index fb24a79..f95dd3b 100644 --- a/docs/csvToJsonDemo.js +++ b/docs/csvToJsonDemo.js @@ -1,418 +1,418 @@ // Demo JavaScript code - csvToJson = window.csvToJson; +csvToJson = window.csvToJson; // Initialize when DOM is loaded - document.addEventListener('DOMContentLoaded', function() { - // Tab switching - document.querySelectorAll('.tab').forEach(tab => { - tab.addEventListener('click', function() { - showTab(this.getAttribute('data-tab')); - }); - }); - - // Output tab switching - document.querySelectorAll('.output-tab').forEach(tab => { - tab.addEventListener('click', function() { - showOutputTab(this.getAttribute('data-output')); - }); - }); - - // Convert button - document.getElementById('convert-btn').addEventListener('click', convert); - - // Clear button - document.getElementById('clear-btn').addEventListener('click', clearAll); - - // Options change listeners - document.getElementById('format-values').addEventListener('change', updateOptions); - document.getElementById('quoted-fields').addEventListener('change', updateOptions); - document.getElementById('delimiter').addEventListener('input', updateOptions); - document.getElementById('header-index').addEventListener('input', updateOptions); - document.getElementById('use-streaming').addEventListener('change', updateOptions); - document.getElementById('use-chunked').addEventListener('change', toggleChunkedOptions); - - - // File input change - document.getElementById('csv-file').addEventListener('change', handleFileSelect); - - // Ensure file-only options are hidden on initial load - toggleFileOnlyOptions(); - - // Sample data buttons - document.querySelectorAll('.sample-btn').forEach(btn => { - btn.addEventListener('click', function() { - loadSample(this.getAttribute('data-sample')); - }); - }); +document.addEventListener('DOMContentLoaded', function () { + // Tab switching + document.querySelectorAll('.tab').forEach(tab => { + tab.addEventListener('click', function () { + showTab(this.getAttribute('data-tab')); }); + }); - function showTab(tabName) { - // Hide all tabs - document.querySelectorAll('.tab-content').forEach(content => { - content.classList.remove('active'); - }); - document.querySelectorAll('.tab').forEach(tab => { - tab.classList.remove('active'); - }); - - // Show selected tab - document.getElementById(tabName + '-tab').classList.add('active'); - document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); - - // Toggle file-only options visibility when switching tabs - toggleFileOnlyOptions(); - } + // Output tab switching + document.querySelectorAll('.output-tab').forEach(tab => { + tab.addEventListener('click', function () { + showOutputTab(this.getAttribute('data-output')); + }); + }); - function toggleFileOnlyOptions() { - const activeTab = document.querySelector('.tab-content.active').id; - const fileOnlyOptions = document.getElementById('file-only-options'); - if (!fileOnlyOptions) return; + // Convert button + document.getElementById('convert-btn').addEventListener('click', convert); - if (activeTab === 'file-tab') { - fileOnlyOptions.style.display = 'block'; - } else { - fileOnlyOptions.style.display = 'none'; - } - } + // Clear button + document.getElementById('clear-btn').addEventListener('click', clearAll); - function showOutputTab(tabName) { - // Hide all output tabs - document.querySelectorAll('.output-content').forEach(content => { - content.classList.remove('active'); - }); - document.querySelectorAll('.output-tab').forEach(tab => { - tab.classList.remove('active'); - }); - - // Show selected output tab - document.getElementById(tabName + '-content').classList.add('active'); - document.querySelector(`[data-output="${tabName}"]`).classList.add('active'); - } + // Options change listeners + document.getElementById('format-values').addEventListener('change', updateOptions); + document.getElementById('quoted-fields').addEventListener('change', updateOptions); + document.getElementById('delimiter').addEventListener('input', updateOptions); + document.getElementById('header-index').addEventListener('input', updateOptions); + document.getElementById('use-streaming').addEventListener('change', updateOptions); + document.getElementById('use-chunked').addEventListener('change', toggleChunkedOptions); - function updateOptions() { - const formatValues = document.getElementById('format-values').checked; - const quotedFields = document.getElementById('quoted-fields').checked; - const delimiter = document.getElementById('delimiter').value; - const headerIndex = parseInt(document.getElementById('header-index').value) || 0; - csvToJson.formatValueByType(formatValues); - csvToJson.supportQuotedField(quotedFields); - csvToJson.fieldDelimiter(delimiter); - csvToJson.indexHeader(headerIndex); + // File input change + document.getElementById('csv-file').addEventListener('change', handleFileSelect); - if (parseSubarray) { - csvToJson.parseSubArray('*', ','); - } + // Ensure file-only options are hidden on initial load + toggleFileOnlyOptions(); - if (mapRows) { - // Add mapping function if needed - } - } - - function toggleChunkedOptions() { - const useChunked = document.getElementById('use-chunked').checked; - const chunkedOptions = document.getElementById('chunked-options'); - if (useChunked) { - chunkedOptions.style.display = 'block'; - } else { - chunkedOptions.style.display = 'none'; - } - } + // Sample data buttons + document.querySelectorAll('.sample-btn').forEach(btn => { + btn.addEventListener('click', function () { + loadSample(this.getAttribute('data-sample')); + }); + }); +}); + +function showTab(tabName) { + // Hide all tabs + document.querySelectorAll('.tab-content').forEach(content => { + content.classList.remove('active'); + }); + document.querySelectorAll('.tab').forEach(tab => { + tab.classList.remove('active'); + }); + + // Show selected tab + document.getElementById(tabName + '-tab').classList.add('active'); + document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); + + // Toggle file-only options visibility when switching tabs + toggleFileOnlyOptions(); +} + +function toggleFileOnlyOptions() { + const activeTab = document.querySelector('.tab-content.active').id; + const fileOnlyOptions = document.getElementById('file-only-options'); + if (!fileOnlyOptions) return; + + if (activeTab === 'file-tab') { + fileOnlyOptions.style.display = 'block'; + } else { + fileOnlyOptions.style.display = 'none'; + } +} + +function showOutputTab(tabName) { + // Hide all output tabs + document.querySelectorAll('.output-content').forEach(content => { + content.classList.remove('active'); + }); + document.querySelectorAll('.output-tab').forEach(tab => { + tab.classList.remove('active'); + }); + + // Show selected output tab + document.getElementById(tabName + '-content').classList.add('active'); + document.querySelector(`[data-output="${tabName}"]`).classList.add('active'); +} + +function updateOptions() { + const formatValues = document.getElementById('format-values').checked; + const quotedFields = document.getElementById('quoted-fields').checked; + const delimiter = document.getElementById('delimiter').value; + const headerIndex = parseInt(document.getElementById('header-index').value) || 0; + + csvToJson.formatValueByType(formatValues); + csvToJson.supportQuotedField(quotedFields); + csvToJson.fieldDelimiter(delimiter); + csvToJson.indexHeader(headerIndex); + + if (parseSubarray) { + csvToJson.parseSubArray('*', ','); + } - function handleFileSelect(event) { - const file = event.target.files[0]; - if (file) { - const fileInfo = document.getElementById('file-info'); - const size = (file.size / 1024 / 1024).toFixed(2); - fileInfo.innerHTML = `Selected file: ${file.name} (${size} MB)`; - fileInfo.style.display = 'block'; - } - } + if (mapRows) { + // Add mapping function if needed + } +} + +function toggleChunkedOptions() { + const useChunked = document.getElementById('use-chunked').checked; + const chunkedOptions = document.getElementById('chunked-options'); + if (useChunked) { + chunkedOptions.style.display = 'block'; + } else { + chunkedOptions.style.display = 'none'; + } +} + +function handleFileSelect(event) { + const file = event.target.files[0]; + if (file) { + const fileInfo = document.getElementById('file-info'); + const size = (file.size / 1024 / 1024).toFixed(2); + fileInfo.innerHTML = `Selected file: ${file.name} (${size} MB)`; + fileInfo.style.display = 'block'; + } +} - function loadSample(sampleName) { - const samples = { - basic: `name,age,city +function loadSample(sampleName) { + const samples = { + basic: `name,age,city John,25,New York Jane,30,London Bob,35,Paris`, - quoted: `"name","age","description" + quoted: `"name","age","description" "John Doe","25","Software Engineer" "Jane Smith","30","Product Manager"`, - numbers: `product,price,quantity + numbers: `product,price,quantity Widget A,19.99,100 Widget B,29.99,50 Widget C,9.99,200`, - dates: `event,date,attendees + dates: `event,date,attendees Conference,2024-01-15,150 Workshop,2024-02-20,75 Seminar,2024-03-10,200`, - }; + }; - document.getElementById('csv-input').value = samples[sampleName] || ''; - showTab('text'); - } + document.getElementById('csv-input').value = samples[sampleName] || ''; + showTab('text'); +} - async function convert() { - const output = document.getElementById('output'); - const convertBtn = document.getElementById('convert-btn'); - - // Clear previous output - clearOutput(); - - // Disable button during conversion - convertBtn.disabled = true; - convertBtn.textContent = 'Converting...'; - - try { - let result; - - // Check which tab is active - const activeTab = document.querySelector('.tab-content.active').id; - const useStreaming = document.getElementById('use-streaming').checked; - - if (activeTab === 'text-tab') { - // Text input - const csvText = document.getElementById('csv-input').value; - if (!csvText.trim()) { - throw new Error('Please enter CSV text'); - } - result = csvToJson.csvStringToJson(csvText); - displayResult(result); - } else { - // File input - const fileInput = document.getElementById('csv-file'); - if (!fileInput.files[0]) { - throw new Error('Please select a CSV file'); - } - - const useChunked = document.getElementById('use-chunked').checked; - - if (useChunked) { - // Use chunked processing for large files - const chunkSize = parseInt(document.getElementById('chunk-size').value) || 1000; - await processFileInChunks(fileInput.files[0], chunkSize); - } else if (useStreaming) { - // Use streaming API - result = await csvToJson.getJsonFromFileStreamingAsync(fileInput.files[0]); - displayResult(result); - } else { - // Use regular file parsing - result = await csvToJson.parseFile(fileInput.files[0]); - displayResult(result); - } - } - } catch (error) { - displayError(error); - } finally { - // Re-enable button - convertBtn.disabled = false; - convertBtn.textContent = 'Convert to JSON'; +async function convert() { + const output = document.getElementById('output'); + const convertBtn = document.getElementById('convert-btn'); + + // Clear previous output + clearOutput(); + + // Disable button during conversion + convertBtn.disabled = true; + convertBtn.textContent = 'Converting...'; + + try { + let result; + + // Check which tab is active + const activeTab = document.querySelector('.tab-content.active').id; + const useStreaming = document.getElementById('use-streaming').checked; + + if (activeTab === 'text-tab') { + // Text input + const csvText = document.getElementById('csv-input').value; + if (!csvText.trim()) { + throw new Error('Please enter CSV text'); } - } + result = csvToJson.csvStringToJson(csvText); + displayResult(result); + } else { + // File input + const fileInput = document.getElementById('csv-file'); + if (!fileInput.files[0]) { + throw new Error('Please select a CSV file'); + } + + const useChunked = document.getElementById('use-chunked').checked; - async function processFileInChunks(file, chunkSize) { - const output = document.getElementById('output'); - const jsonOutput = document.getElementById('json-output'); - const tableOutput = document.getElementById('table-output'); - const statsOutput = document.getElementById('stats-output'); - - // Show results container - output.classList.remove('hidden'); - clearOutput(); - - // Initialize progress tracking - let allRows = []; - let totalProcessed = 0; - - // Create progress display - const progressDiv = document.createElement('div'); - progressDiv.id = 'progress-display'; - progressDiv.innerHTML = '

Processing large file...

Initializing...
'; - output.insertBefore(progressDiv, output.firstChild); - - try { - await csvToJson.getJsonFromFileStreamingAsyncWithCallback(file, { - chunkSize: chunkSize, - onChunk: (rows, processed, total) => { - console.log('onChunk called', rows.length, processed, total); - // Accumulate rows - allRows.push(...rows); - totalProcessed = processed; - - // Update progress - const progressPercent = total ? Math.round((processed / total) * 100) : Math.min(Math.round((processed / 10000) * 100), 95); - document.getElementById('progress-fill').style.width = progressPercent + '%'; - document.getElementById('progress-text').textContent = `Processed ${processed} rows...`; - - // For very large files, show the first 1000 rows only - if (allRows.length <= 1000) { - displayTable(allRows); - } else if (allRows.length === 1001) { - displayTable(allRows); - } - }, - onComplete: (allRowsComplete) => { - console.log('onComplete called', allRowsComplete.length); - allRows = allRowsComplete; - - // Update progress to 100% - document.getElementById('progress-fill').style.width = '100%'; - document.getElementById('progress-text').textContent = `Complete! Processed ${allRows.length} rows.`; - - // Display final results - jsonOutput.textContent = JSON.stringify(allRows, null, 2); - displayStats(allRows); - displayTable(allRows); - showOutputTab('table'); - - // Remove progress after a delay - setTimeout(() => { - const progressDisplay = document.getElementById('progress-display'); - if (progressDisplay) progressDisplay.remove(); - }, 3000); - }, - onError: (error) => { - console.error('Error:', error); - displayError(error); - const progressDisplay = document.getElementById('progress-display'); - if (progressDisplay) progressDisplay.remove(); - } - }); - } catch (error) { - console.error('Catch error:', error); + if (useChunked) { + // Use chunked processing for large files + const chunkSize = parseInt(document.getElementById('chunk-size').value) || 1000; + await processFileInChunks(fileInput.files[0], chunkSize); + } else if (useStreaming) { + // Use streaming API + result = await csvToJson.getJsonFromFileStreamingAsync(fileInput.files[0]); + displayResult(result); + } else { + // Use regular file parsing + result = await csvToJson.parseFile(fileInput.files[0]); + displayResult(result); + } + } + } catch (error) { + displayError(error); + } finally { + // Re-enable button + convertBtn.disabled = false; + convertBtn.textContent = 'Convert to JSON'; + } +} + +async function processFileInChunks(file, chunkSize) { + const output = document.getElementById('output'); + const jsonOutput = document.getElementById('json-output'); + const tableOutput = document.getElementById('table-output'); + const statsOutput = document.getElementById('stats-output'); + + // Show results container + output.classList.remove('hidden'); + clearOutput(); + + // Initialize progress tracking + let allRows = []; + let totalProcessed = 0; + + // Create progress display + const progressDiv = document.createElement('div'); + progressDiv.id = 'progress-display'; + progressDiv.innerHTML = '

Processing large file...

Initializing...
'; + output.insertBefore(progressDiv, output.firstChild); + + try { + await csvToJson.getJsonFromFileStreamingAsyncWithCallback(file, { + chunkSize: chunkSize, + onChunk: (rows, processed, total) => { + console.log('onChunk called', rows.length, processed, total); + // Accumulate rows + allRows.push(...rows); + totalProcessed = processed; + + // Update progress + const progressPercent = total ? Math.round((processed / total) * 100) : Math.min(Math.round((processed / 10000) * 100), 95); + document.getElementById('progress-fill').style.width = progressPercent + '%'; + document.getElementById('progress-text').textContent = `Processed ${processed} rows...`; + + // For very large files, show the first 1000 rows only + if (allRows.length <= 1000) { + displayTable(allRows); + } else if (allRows.length === 1001) { + displayTable(allRows); + } + }, + onComplete: (allRowsComplete) => { + console.log('onComplete called', allRowsComplete.length); + allRows = allRowsComplete; + + // Update progress to 100% + document.getElementById('progress-fill').style.width = '100%'; + document.getElementById('progress-text').textContent = `Complete! Processed ${allRows.length} rows.`; + + // Display final results + jsonOutput.textContent = JSON.stringify(allRows, null, 2); + displayStats(allRows); + displayTable(allRows); + showOutputTab('table'); + + // Remove progress after a delay + setTimeout(() => { + const progressDisplay = document.getElementById('progress-display'); + if (progressDisplay) progressDisplay.remove(); + }, 3000); + }, + onError: (error) => { + console.error('Error:', error); displayError(error); const progressDisplay = document.getElementById('progress-display'); if (progressDisplay) progressDisplay.remove(); } - } + }); + } catch (error) { + console.error('Catch error:', error); + displayError(error); + const progressDisplay = document.getElementById('progress-display'); + if (progressDisplay) progressDisplay.remove(); + } +} - function displayResult(result) { - const output = document.getElementById('output'); - const jsonOutput = document.getElementById('json-output'); - const tableOutput = document.getElementById('table-output'); - const statsOutput = document.getElementById('stats-output'); +function displayResult(result) { + const output = document.getElementById('output'); + const jsonOutput = document.getElementById('json-output'); + const tableOutput = document.getElementById('table-output'); + const statsOutput = document.getElementById('stats-output'); - // Show results container - output.classList.remove('hidden'); + // Show results container + output.classList.remove('hidden'); - // Display JSON - jsonOutput.textContent = JSON.stringify(result, null, 2); + // Display JSON + jsonOutput.textContent = JSON.stringify(result, null, 2); - // Display table - displayTable(result); + // Display table + displayTable(result); - // Display stats - displayStats(result); + // Display stats + displayStats(result); - // Show table tab by default - showOutputTab('table'); - } + // Show table tab by default + showOutputTab('table'); +} - function displayTable(data) { - const tableOutput = document.getElementById('table-output'); - if (!data || data.length === 0) { - tableOutput.innerHTML = '

No data to display

'; - return; - } +function displayTable(data) { + const tableOutput = document.getElementById('table-output'); + if (!data || data.length === 0) { + tableOutput.innerHTML = '

No data to display

'; + return; + } - const maxRows = 1000; - const displayData = data.length > maxRows ? data.slice(0, maxRows) : data; - let html = '
'; - - // Header - if (displayData.length > 0) { - html += ''; - Object.keys(displayData[0]).forEach(key => { - html += ``; - }); - html += ''; - } + const maxRows = 1000; + const displayData = data.length > maxRows ? data.slice(0, maxRows) : data; + let html = '
${key}
'; - // Body - html += ''; - displayData.forEach(row => { - html += ''; - Object.values(row).forEach(value => { - html += ``; - }); - html += ''; - }); - html += '
${value}
'; - - if (data.length > maxRows) { - html = `

Showing first ${maxRows} rows only. Full data is available in JSON view and row count is shown in stats.

${html}`; - } + // Header + if (displayData.length > 0) { + html += ''; + Object.keys(displayData[0]).forEach(key => { + html += `${key}`; + }); + html += ''; + } - tableOutput.innerHTML = html; - } + // Body + html += ''; + displayData.forEach(row => { + html += ''; + Object.values(row).forEach(value => { + html += `${value}`; + }); + html += ''; + }); + html += ''; + + if (data.length > maxRows) { + html = `

Showing first ${maxRows} rows only. Full data is available in JSON view and row count is shown in stats.

${html}`; + } + + tableOutput.innerHTML = html; +} - function displayStats(data) { - const statsOutput = document.getElementById('stats-output'); - const rowCount = data ? data.length : 0; - const colCount = data && data.length > 0 ? Object.keys(data[0]).length : 0; +function displayStats(data) { + const statsOutput = document.getElementById('stats-output'); + const rowCount = data ? data.length : 0; + const colCount = data && data.length > 0 ? Object.keys(data[0]).length : 0; - statsOutput.innerHTML = ` + statsOutput.innerHTML = `
Rows: ${rowCount}
Columns: ${colCount}
`; - if(rowCount > 1000){ - document.getElementById('output-tab-json').disabled = true; - document.getElementById('output-tab-json').title = "JSON preview is only available for files with fewer than 1,000 lines. Click the Download JSON button to view the result in JSON format."; - } else{ - document.getElementById('output-tab-json').disabled = false; - } - } - - function displayError(error) { - const output = document.getElementById('output'); - const errorOutput = document.getElementById('error-output'); - - // Show results container - output.classList.remove('hidden'); - - // Display error - errorOutput.textContent = `Error: ${error.message}`; - errorOutput.classList.remove('hidden'); - } - - function clearOutput() { - const output = document.getElementById('output'); - const jsonOutput = document.getElementById('json-output'); - const tableOutput = document.getElementById('table-output'); - const statsOutput = document.getElementById('stats-output'); - const errorOutput = document.getElementById('error-output'); - - jsonOutput.textContent = ''; - tableOutput.innerHTML = ''; - statsOutput.innerHTML = ''; - errorOutput.textContent = ''; - errorOutput.classList.add('hidden'); - } - - function clearAll() { - clearOutput(); - document.getElementById('output').classList.add('hidden'); - document.getElementById('csv-input').value = ''; - document.getElementById('csv-file').value = ''; - document.getElementById('file-info').style.display = 'none'; - } - - function downloadJSON() { - const jsonOutput = document.getElementById('json-output'); - const data = jsonOutput.textContent; - if (!data) return; - - const blob = new Blob([data], { type: 'application/json' }); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = 'data.json'; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - } \ No newline at end of file + if (rowCount > 1000) { + document.getElementById('output-tab-json').disabled = true; + document.getElementById('output-tab-json').title = "JSON preview is only available for files with fewer than 1,000 lines. Click the Download JSON button to view the result in JSON format."; + } else { + document.getElementById('output-tab-json').disabled = false; + } +} + +function displayError(error) { + const output = document.getElementById('output'); + const errorOutput = document.getElementById('error-output'); + + // Show results container + output.classList.remove('hidden'); + + // Display error + errorOutput.textContent = `Error: ${error.message}`; + errorOutput.classList.remove('hidden'); +} + +function clearOutput() { + const output = document.getElementById('output'); + const jsonOutput = document.getElementById('json-output'); + const tableOutput = document.getElementById('table-output'); + const statsOutput = document.getElementById('stats-output'); + const errorOutput = document.getElementById('error-output'); + + jsonOutput.textContent = ''; + tableOutput.innerHTML = ''; + statsOutput.innerHTML = ''; + errorOutput.textContent = ''; + errorOutput.classList.add('hidden'); +} + +function clearAll() { + clearOutput(); + document.getElementById('output').classList.add('hidden'); + document.getElementById('csv-input').value = ''; + document.getElementById('csv-file').value = ''; + document.getElementById('file-info').style.display = 'none'; +} + +function downloadJSON() { + const jsonOutput = document.getElementById('json-output'); + const data = jsonOutput.textContent; + if (!data) return; + + const blob = new Blob([data], {type: 'application/json'}); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'data.json'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); +} \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index afddd35..00ece2c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,11 +4,14 @@ - + Convert CSV to JSON - - + +