web-csv-toolbox 0.12.1-next-53113e3c3ee5a6ec969f90ac8d8c87cc929fe86b → 0.13.0-next-c1addc26c62ad2577b7b9eaaa3a44fde79d5e05c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -54,6 +54,11 @@ A CSV Toolbox utilizing Web Standard APIs.
54
54
  - 🛑 **AbortSignal and Timeout Support**: Ensure your CSV processing is cancellable, including support for automatic timeouts.
55
55
  - ✋ Integrate with [`AbortController`](https://developer.mozilla.org/docs/Web/API/AbortController) to manually cancel operations as needed.
56
56
  - ⏳ Use [`AbortSignal.timeout`](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static) to automatically cancel operations that exceed a specified time limit.
57
+ - 🛡️ **Memory Safety Protection**: Built-in limits prevent memory exhaustion attacks.
58
+ - 🔒 Configurable maximum buffer size (default: 10M characters) to prevent DoS attacks via unbounded input.
59
+ - 🚨 Throws `RangeError` when buffer exceeds the limit.
60
+ - 📊 Configurable maximum field count (default: 100,000 fields/record) to prevent excessive column attacks.
61
+ - ⚠️ Throws `RangeError` when field count exceeds the limit.
57
62
  - 🎨 **Flexible Source Support**
58
63
  - 🧩 Parse CSVs directly from `string`s, `ReadableStream`s, or `Response` objects.
59
64
  - ⚙️ **Advanced Parsing Options**: Customize your experience with various delimiters and quotation marks.
@@ -367,21 +372,24 @@ console.log(result);
367
372
 
368
373
  ### Common Options ⚙️
369
374
 
370
- | Option | Description | Default | Notes |
371
- | ----------- | ------------------------------------- | ----------- | ------------------------------------------------- |
372
- | `delimiter` | Character to separate fields | `,` | |
373
- | `quotation` | Character used for quoting fields | `"` | |
374
- | `headers` | Custom headers for the parsed records | First row | If not provided, the first row is used as headers |
375
- | `signal` | AbortSignal to cancel processing | `undefined` | Allows aborting of long-running operations |
375
+ | Option | Description | Default | Notes |
376
+ | ---------------- | ------------------------------------- | ------------ | ---------------------------------------------------------------------------------- |
377
+ | `delimiter` | Character to separate fields | `,` | |
378
+ | `quotation` | Character used for quoting fields | `"` | |
379
+ | `maxBufferSize` | Maximum internal buffer size (characters) | `10 * 1024 * 1024` | Set to `Number.POSITIVE_INFINITY` to disable (not recommended for untrusted input). Measured in UTF-16 code units. |
380
+ | `maxFieldCount` | Maximum fields allowed per record | `100000` | Set to `Number.POSITIVE_INFINITY` to disable (not recommended for untrusted input) |
381
+ | `headers` | Custom headers for the parsed records | First row | If not provided, the first row is used as headers |
382
+ | `signal` | AbortSignal to cancel processing | `undefined` | Allows aborting of long-running operations |
376
383
 
377
384
  ### Advanced Options (Binary-Specific) 🧬
378
385
 
379
- | Option | Description | Default | Notes |
380
- | --------------- | ------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
381
- | `charset` | Character encoding for binary CSV inputs | `utf-8` | See [Encoding API Compatibility](https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings) for the encoding formats that can be specified. |
382
- | `decompression` | Decompression algorithm for compressed CSV inputs | | See [DecompressionStream Compatibility](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream#browser_compatibilit). |
383
- | `ignoreBOM` | Whether to ignore Byte Order Mark (BOM) | `false` | See [TextDecoderOptions.ignoreBOM](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream/ignoreBOM) for more information about the BOM. |
384
- | `fatal` | Throw an error on invalid characters | `false` | See [TextDecoderOptions.fatal](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream/fatal) for more information. |
386
+ | Option | Description | Default | Notes |
387
+ | --------------------------------- | ------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
388
+ | `charset` | Character encoding for binary CSV inputs | `utf-8` | See [Encoding API Compatibility](https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings) for the encoding formats that can be specified. |
389
+ | `decompression` | Decompression algorithm for compressed CSV inputs | | See [DecompressionStream Compatibility](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream#browser_compatibilit). Supports: gzip, deflate, deflate-raw |
390
+ | `ignoreBOM` | Whether to ignore Byte Order Mark (BOM) | `false` | See [TextDecoderOptions.ignoreBOM](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream/ignoreBOM) for more information about the BOM. |
391
+ | `fatal` | Throw an error on invalid characters | `false` | See [TextDecoderOptions.fatal](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoderStream/fatal) for more information. |
392
+ | `allowExperimentalCompressions` | Allow experimental/future compression formats | `false` | When enabled, passes unknown compression formats to runtime. Use cautiously. See example below. |
385
393
 
386
394
  ## Performance & Best Practices ⚡
387
395
 
@@ -507,6 +515,81 @@ For production use with untrusted input, consider:
507
515
  - Implementing file size limits at the application level
508
516
  - Validating parsed data before use
509
517
 
518
+ #### Implementing Size Limits for Untrusted Sources
519
+
520
+ When processing CSV files from untrusted sources (especially compressed files), you can implement size limits using a custom TransformStream:
521
+
522
+ ```js
523
+ import { parse } from 'web-csv-toolbox';
524
+
525
+ // Create a size-limiting TransformStream
526
+ class SizeLimitStream extends TransformStream {
527
+ constructor(maxBytes) {
528
+ let bytesRead = 0;
529
+ super({
530
+ transform(chunk, controller) {
531
+ bytesRead += chunk.length;
532
+ if (bytesRead > maxBytes) {
533
+ controller.error(new Error(`Size limit exceeded: ${maxBytes} bytes`));
534
+ } else {
535
+ controller.enqueue(chunk);
536
+ }
537
+ }
538
+ });
539
+ }
540
+ }
541
+
542
+ // Example: Limit decompressed data to 10MB
543
+ const response = await fetch('https://untrusted-source.com/data.csv.gz');
544
+ const limitedStream = response.body
545
+ .pipeThrough(new DecompressionStream('gzip'))
546
+ .pipeThrough(new SizeLimitStream(10 * 1024 * 1024)); // 10MB limit
547
+
548
+ try {
549
+ for await (const record of parse(limitedStream)) {
550
+ console.log(record);
551
+ }
552
+ } catch (error) {
553
+ if (error.message.includes('Size limit exceeded')) {
554
+ console.error('File too large - possible compression bomb attack');
555
+ }
556
+ }
557
+ ```
558
+
559
+ **Note**: The library automatically validates Content-Encoding headers when parsing Response objects, rejecting unsupported compression formats.
560
+
561
+ #### Using Experimental Compression Formats
562
+
563
+ By default, the library only supports well-tested compression formats: `gzip`, `deflate`, and `deflate-raw`. If you need to use newer formats (like Brotli) that your runtime supports but the library hasn't explicitly added yet, you can enable experimental mode:
564
+
565
+ ```js
566
+ import { parse } from 'web-csv-toolbox';
567
+
568
+ // ✅ Default behavior: Only known formats
569
+ const response = await fetch('data.csv.gz');
570
+ await parse(response); // Works
571
+
572
+ // ⚠️ Experimental: Allow future formats
573
+ const response2 = await fetch('data.csv.br'); // Brotli compression
574
+ try {
575
+ await parse(response2, { allowExperimentalCompressions: true });
576
+ // Works if runtime supports Brotli
577
+ } catch (error) {
578
+ // Runtime will throw if format is unsupported
579
+ console.error('Runtime does not support this compression format');
580
+ }
581
+ ```
582
+
583
+ **When to use this:**
584
+ - Your runtime supports a newer compression format (e.g., Brotli in modern browsers)
585
+ - You want to use the format before this library explicitly supports it
586
+ - You trust the compression format source
587
+
588
+ **Cautions:**
589
+ - Error messages will come from the runtime, not this library
590
+ - No library-level validation for unknown formats
591
+ - You must verify your runtime supports the format
592
+
510
593
  ## How to Contribute 💪
511
594
 
512
595
  ## Star ⭐
package/dist/Lexer.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { AbortSignalOptions, CommonOptions, Token } from './common/types.ts';
2
2
  import { DEFAULT_DELIMITER, DEFAULT_QUOTATION } from './constants.ts';
3
+ /**
4
+ * Default maximum buffer size in characters (UTF-16 code units).
5
+ * Approximately 10MB for ASCII text, but may vary for non-ASCII characters.
6
+ */
7
+ export declare const DEFAULT_MAX_BUFFER_SIZE: number;
3
8
  /**
4
9
  * CSV Lexer.
5
10
  *
package/dist/Lexer.js CHANGED
@@ -4,6 +4,7 @@ import { ParseError } from './common/errors.js';
4
4
  import { DEFAULT_DELIMITER, DEFAULT_QUOTATION, CRLF, LF } from './constants.js';
5
5
  import { escapeRegExp } from './utils/escapeRegExp.js';
6
6
 
7
+ const DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;
7
8
  class Lexer {
8
9
  #delimiter;
9
10
  #quotation;
@@ -11,6 +12,7 @@ class Lexer {
11
12
  #flush = false;
12
13
  #matcher;
13
14
  #fieldDelimiterLength;
15
+ #maxBufferSize;
14
16
  #cursor = {
15
17
  line: 1,
16
18
  column: 1,
@@ -26,12 +28,14 @@ class Lexer {
26
28
  const {
27
29
  delimiter = DEFAULT_DELIMITER,
28
30
  quotation = DEFAULT_QUOTATION,
31
+ maxBufferSize = DEFAULT_MAX_BUFFER_SIZE,
29
32
  signal
30
33
  } = options;
31
- assertCommonOptions({ delimiter, quotation });
34
+ assertCommonOptions({ delimiter, quotation, maxBufferSize });
32
35
  this.#delimiter = delimiter;
33
36
  this.#quotation = quotation;
34
37
  this.#fieldDelimiterLength = delimiter.length;
38
+ this.#maxBufferSize = maxBufferSize;
35
39
  const d = escapeRegExp(delimiter);
36
40
  const q = escapeRegExp(quotation);
37
41
  this.#matcher = new RegExp(
@@ -53,6 +57,7 @@ class Lexer {
53
57
  }
54
58
  if (typeof chunk === "string" && chunk.length !== 0) {
55
59
  this.#buffer += chunk;
60
+ this.#checkBufferSize();
56
61
  }
57
62
  return this.#tokens();
58
63
  }
@@ -89,6 +94,17 @@ class Lexer {
89
94
  yield token;
90
95
  }
91
96
  }
97
+ /**
98
+ * Checks if the buffer size exceeds the maximum allowed size.
99
+ * @throws {RangeError} If the buffer size exceeds the maximum.
100
+ */
101
+ #checkBufferSize() {
102
+ if (this.#buffer.length > this.#maxBufferSize) {
103
+ throw new RangeError(
104
+ `Buffer size (${this.#buffer.length} characters) exceeded maximum allowed size of ${this.#maxBufferSize} characters`
105
+ );
106
+ }
107
+ }
92
108
  /**
93
109
  * Retrieves the next token from the buffered CSV data.
94
110
  * @returns The next token or null if there are no more tokens.
@@ -228,5 +244,5 @@ class Lexer {
228
244
  }
229
245
  }
230
246
 
231
- export { Lexer };
247
+ export { DEFAULT_MAX_BUFFER_SIZE, Lexer };
232
248
  //# sourceMappingURL=Lexer.js.map
package/dist/Lexer.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Lexer.js","sources":["../src/Lexer.ts"],"sourcesContent":["import { assertCommonOptions } from \"./assertCommonOptions.ts\";\nimport { Field, FieldDelimiter, RecordDelimiter } from \"./common/constants.ts\";\nimport { ParseError } from \"./common/errors.ts\";\nimport type {\n AbortSignalOptions,\n CommonOptions,\n Position,\n RecordDelimiterToken,\n Token,\n} from \"./common/types.ts\";\nimport { CRLF, DEFAULT_DELIMITER, DEFAULT_QUOTATION, LF } from \"./constants.ts\";\nimport { escapeRegExp } from \"./utils/escapeRegExp.ts\";\n\n/**\n * CSV Lexer.\n *\n * Lexer tokenizes CSV data into fields and records.\n */\nexport class Lexer<\n Delimiter extends string = DEFAULT_DELIMITER,\n Quotation extends string = DEFAULT_QUOTATION,\n> {\n #delimiter: string;\n #quotation: string;\n #buffer = \"\";\n #flush = false;\n #matcher: RegExp;\n #fieldDelimiterLength: number;\n\n #cursor: Position = {\n line: 1,\n column: 1,\n offset: 0,\n };\n #rowNumber = 1;\n\n #signal?: AbortSignal;\n\n /**\n * Constructs a new Lexer instance.\n * @param options - The common options for the lexer.\n */\n constructor(\n options: CommonOptions<Delimiter, Quotation> & AbortSignalOptions = {},\n ) {\n const {\n delimiter = DEFAULT_DELIMITER,\n quotation = DEFAULT_QUOTATION,\n signal,\n } = options;\n assertCommonOptions({ delimiter, quotation });\n this.#delimiter = delimiter;\n this.#quotation = quotation;\n this.#fieldDelimiterLength = delimiter.length;\n const d = escapeRegExp(delimiter);\n const q = escapeRegExp(quotation);\n this.#matcher = new RegExp(\n `^(?:(?!${q})(?!${d})(?![\\\\r\\\\n]))([\\\\S\\\\s\\\\uFEFF\\\\xA0]+?)(?=${q}|${d}|\\\\r|\\\\n|$)`,\n );\n if (signal) {\n this.#signal = signal;\n }\n }\n\n /**\n * Lexes the given chunk of CSV data.\n * @param chunk - The chunk of CSV data to be lexed.\n * @param buffering - Indicates whether the lexer is buffering or not.\n * @returns An iterable iterator of tokens.\n */\n public lex(chunk: string | null, buffering = false): IterableIterator<Token> {\n if (!buffering) {\n this.#flush = true;\n }\n if (typeof chunk === \"string\" && chunk.length !== 0) {\n this.#buffer += chunk;\n }\n\n return this.#tokens();\n }\n\n /**\n * Flushes the lexer and returns any remaining tokens.\n * @returns An array of tokens.\n */\n public flush(): Token[] {\n this.#flush = true;\n return [...this.#tokens()];\n }\n\n /**\n * Generates tokens from the buffered CSV data.\n * @yields Tokens from the buffered CSV data.\n */\n *#tokens(): Generator<Token> {\n if (this.#flush) {\n // Trim the last CRLF or LF\n if (this.#buffer.endsWith(CRLF)) {\n this.#buffer = this.#buffer.slice(0, -2 /* -CRLF.length */);\n } else if (this.#buffer.endsWith(LF)) {\n this.#buffer = this.#buffer.slice(0, -1 /* -LF.length */);\n }\n }\n let token: Token | null;\n while ((token = this.#nextToken())) {\n yield token;\n }\n }\n\n /**\n * Retrieves the next token from the buffered CSV data.\n * @returns The next token or null if there are no more tokens.\n */\n #nextToken(): Token | null {\n this.#signal?.throwIfAborted();\n if (this.#buffer.length === 0) {\n return null;\n }\n // Buffer is Record Delimiter, defer to the next iteration.\n if (\n this.#flush === false &&\n (this.#buffer === CRLF || this.#buffer === LF)\n ) {\n return null;\n }\n\n // Check for CRLF\n if (this.#buffer.startsWith(CRLF)) {\n this.#buffer = this.#buffer.slice(2);\n const start: Position = { ...this.#cursor };\n this.#cursor.line++;\n this.#cursor.column = 1;\n this.#cursor.offset += 2; // CRLF.length\n const token: RecordDelimiterToken = {\n type: RecordDelimiter,\n value: CRLF,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber++,\n },\n };\n return token;\n }\n\n // Check for LF\n if (this.#buffer.startsWith(LF)) {\n this.#buffer = this.#buffer.slice(1);\n const start: Position = { ...this.#cursor };\n this.#cursor.line++;\n this.#cursor.column = 1;\n this.#cursor.offset += 1; // LF.length\n const token: RecordDelimiterToken = {\n type: RecordDelimiter,\n value: LF,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber++,\n },\n };\n return token;\n }\n\n // Check for Delimiter\n if (this.#buffer.startsWith(this.#delimiter)) {\n this.#buffer = this.#buffer.slice(1);\n const start: Position = { ...this.#cursor };\n this.#cursor.column += this.#fieldDelimiterLength;\n this.#cursor.offset += this.#fieldDelimiterLength;\n return {\n type: FieldDelimiter,\n value: this.#delimiter,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber,\n },\n };\n }\n\n // Check for Quoted String\n if (this.#buffer.startsWith(this.#quotation)) {\n /**\n * Extract Quoted field.\n *\n * The following code is equivalent to the following:\n *\n * If the next character is a quote:\n * - If the character after that is a quote, then append a quote to the value and skip two characters.\n * - Otherwise, return the quoted string.\n * Otherwise, append the character to the value and skip one character.\n *\n * ```plaintext\n * | `i` | `i + 1` | `i + 2` |\n * |------------|------------|----------|\n * | cur | next | | => Variable names\n * | #quotation | #quotation | | => Escaped quote\n * | #quotation | (EOF) | | => Closing quote\n * | #quotation | undefined | | => End of buffer\n * | undefined | | | => End of buffer\n * ```\n */\n let value = \"\";\n let offset = 1; // Skip the opening quote\n let column = 2; // Skip the opening quote\n let line = 0;\n\n // Define variables\n let cur: string = this.#buffer[offset];\n let next: string | undefined = this.#buffer[offset + 1];\n do {\n // If the current character is a quote, check the next characters for closing quotes.\n if (cur === this.#quotation) {\n // If the cur character is a quote and the next character is a quote,\n // then append a quote to the value and skip two characters.\n if (next === this.#quotation) {\n // Append a quote to the value and skip two characters.\n value += this.#quotation;\n offset += 2;\n cur = this.#buffer[offset];\n next = this.#buffer[offset + 1];\n\n // Update the diff\n column += 2;\n continue;\n }\n\n // If the cur character is a quote and the next character is undefined,\n // then return null.\n if (next === undefined && this.#flush === false) {\n return null;\n }\n\n // Otherwise, return the quoted string.\n // Update the buffer and return the token\n offset++;\n this.#buffer = this.#buffer.slice(offset);\n const start: Position = { ...this.#cursor };\n this.#cursor.column += column;\n this.#cursor.offset += offset;\n this.#cursor.line += line;\n return {\n type: Field,\n value,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber,\n },\n };\n }\n\n // Append the character to the value.\n value += cur;\n\n // Prepare for the next iteration\n if (cur === LF) {\n // If the current character is a LF,\n // then increment the line number and reset the column number.\n line++;\n column = 1;\n } else {\n // Otherwise, increment the column number and offset.\n column++;\n }\n\n offset++;\n cur = next;\n next = this.#buffer[offset + 1];\n } while (cur !== undefined);\n\n if (this.#flush) {\n throw new ParseError(\"Unexpected EOF while parsing quoted field.\", {\n position: { ...this.#cursor },\n });\n }\n return null;\n }\n\n // Check for Unquoted String\n const match = this.#matcher.exec(this.#buffer);\n if (match) {\n // If we're flushing and the match doesn't consume the entire buffer,\n // then return null\n if (this.#flush === false && match[0].length === this.#buffer.length) {\n return null;\n }\n const value = match[1];\n this.#buffer = this.#buffer.slice(value.length);\n const start: Position = { ...this.#cursor };\n this.#cursor.column += value.length;\n this.#cursor.offset += value.length;\n return {\n type: Field,\n value,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber,\n },\n };\n }\n\n // Otherwise, return null\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Lexer.js","sources":["../src/Lexer.ts"],"sourcesContent":["import { assertCommonOptions } from \"./assertCommonOptions.ts\";\nimport { Field, FieldDelimiter, RecordDelimiter } from \"./common/constants.ts\";\nimport { ParseError } from \"./common/errors.ts\";\nimport type {\n AbortSignalOptions,\n CommonOptions,\n Position,\n RecordDelimiterToken,\n Token,\n} from \"./common/types.ts\";\nimport { CRLF, DEFAULT_DELIMITER, DEFAULT_QUOTATION, LF } from \"./constants.ts\";\nimport { escapeRegExp } from \"./utils/escapeRegExp.ts\";\n\n/**\n * Default maximum buffer size in characters (UTF-16 code units).\n * Approximately 10MB for ASCII text, but may vary for non-ASCII characters.\n */\nexport const DEFAULT_MAX_BUFFER_SIZE = 10 * 1024 * 1024;\n\n/**\n * CSV Lexer.\n *\n * Lexer tokenizes CSV data into fields and records.\n */\nexport class Lexer<\n Delimiter extends string = DEFAULT_DELIMITER,\n Quotation extends string = DEFAULT_QUOTATION,\n> {\n #delimiter: string;\n #quotation: string;\n #buffer = \"\";\n #flush = false;\n #matcher: RegExp;\n #fieldDelimiterLength: number;\n #maxBufferSize: number;\n\n #cursor: Position = {\n line: 1,\n column: 1,\n offset: 0,\n };\n #rowNumber = 1;\n\n #signal?: AbortSignal;\n\n /**\n * Constructs a new Lexer instance.\n * @param options - The common options for the lexer.\n */\n constructor(\n options: CommonOptions<Delimiter, Quotation> & AbortSignalOptions = {},\n ) {\n const {\n delimiter = DEFAULT_DELIMITER,\n quotation = DEFAULT_QUOTATION,\n maxBufferSize = DEFAULT_MAX_BUFFER_SIZE,\n signal,\n } = options;\n assertCommonOptions({ delimiter, quotation, maxBufferSize });\n this.#delimiter = delimiter;\n this.#quotation = quotation;\n this.#fieldDelimiterLength = delimiter.length;\n this.#maxBufferSize = maxBufferSize;\n const d = escapeRegExp(delimiter);\n const q = escapeRegExp(quotation);\n this.#matcher = new RegExp(\n `^(?:(?!${q})(?!${d})(?![\\\\r\\\\n]))([\\\\S\\\\s\\\\uFEFF\\\\xA0]+?)(?=${q}|${d}|\\\\r|\\\\n|$)`,\n );\n if (signal) {\n this.#signal = signal;\n }\n }\n\n /**\n * Lexes the given chunk of CSV data.\n * @param chunk - The chunk of CSV data to be lexed.\n * @param buffering - Indicates whether the lexer is buffering or not.\n * @returns An iterable iterator of tokens.\n */\n public lex(chunk: string | null, buffering = false): IterableIterator<Token> {\n if (!buffering) {\n this.#flush = true;\n }\n if (typeof chunk === \"string\" && chunk.length !== 0) {\n this.#buffer += chunk;\n this.#checkBufferSize();\n }\n\n return this.#tokens();\n }\n\n /**\n * Flushes the lexer and returns any remaining tokens.\n * @returns An array of tokens.\n */\n public flush(): Token[] {\n this.#flush = true;\n return [...this.#tokens()];\n }\n\n /**\n * Generates tokens from the buffered CSV data.\n * @yields Tokens from the buffered CSV data.\n */\n *#tokens(): Generator<Token> {\n if (this.#flush) {\n // Trim the last CRLF or LF\n if (this.#buffer.endsWith(CRLF)) {\n this.#buffer = this.#buffer.slice(0, -2 /* -CRLF.length */);\n } else if (this.#buffer.endsWith(LF)) {\n this.#buffer = this.#buffer.slice(0, -1 /* -LF.length */);\n }\n }\n let token: Token | null;\n while ((token = this.#nextToken())) {\n yield token;\n }\n }\n\n /**\n * Checks if the buffer size exceeds the maximum allowed size.\n * @throws {RangeError} If the buffer size exceeds the maximum.\n */\n #checkBufferSize(): void {\n if (this.#buffer.length > this.#maxBufferSize) {\n throw new RangeError(\n `Buffer size (${this.#buffer.length} characters) exceeded maximum allowed size of ${this.#maxBufferSize} characters`,\n );\n }\n }\n\n /**\n * Retrieves the next token from the buffered CSV data.\n * @returns The next token or null if there are no more tokens.\n */\n #nextToken(): Token | null {\n this.#signal?.throwIfAborted();\n if (this.#buffer.length === 0) {\n return null;\n }\n // Buffer is Record Delimiter, defer to the next iteration.\n if (\n this.#flush === false &&\n (this.#buffer === CRLF || this.#buffer === LF)\n ) {\n return null;\n }\n\n // Check for CRLF\n if (this.#buffer.startsWith(CRLF)) {\n this.#buffer = this.#buffer.slice(2);\n const start: Position = { ...this.#cursor };\n this.#cursor.line++;\n this.#cursor.column = 1;\n this.#cursor.offset += 2; // CRLF.length\n const token: RecordDelimiterToken = {\n type: RecordDelimiter,\n value: CRLF,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber++,\n },\n };\n return token;\n }\n\n // Check for LF\n if (this.#buffer.startsWith(LF)) {\n this.#buffer = this.#buffer.slice(1);\n const start: Position = { ...this.#cursor };\n this.#cursor.line++;\n this.#cursor.column = 1;\n this.#cursor.offset += 1; // LF.length\n const token: RecordDelimiterToken = {\n type: RecordDelimiter,\n value: LF,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber++,\n },\n };\n return token;\n }\n\n // Check for Delimiter\n if (this.#buffer.startsWith(this.#delimiter)) {\n this.#buffer = this.#buffer.slice(1);\n const start: Position = { ...this.#cursor };\n this.#cursor.column += this.#fieldDelimiterLength;\n this.#cursor.offset += this.#fieldDelimiterLength;\n return {\n type: FieldDelimiter,\n value: this.#delimiter,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber,\n },\n };\n }\n\n // Check for Quoted String\n if (this.#buffer.startsWith(this.#quotation)) {\n /**\n * Extract Quoted field.\n *\n * The following code is equivalent to the following:\n *\n * If the next character is a quote:\n * - If the character after that is a quote, then append a quote to the value and skip two characters.\n * - Otherwise, return the quoted string.\n * Otherwise, append the character to the value and skip one character.\n *\n * ```plaintext\n * | `i` | `i + 1` | `i + 2` |\n * |------------|------------|----------|\n * | cur | next | | => Variable names\n * | #quotation | #quotation | | => Escaped quote\n * | #quotation | (EOF) | | => Closing quote\n * | #quotation | undefined | | => End of buffer\n * | undefined | | | => End of buffer\n * ```\n */\n let value = \"\";\n let offset = 1; // Skip the opening quote\n let column = 2; // Skip the opening quote\n let line = 0;\n\n // Define variables\n let cur: string = this.#buffer[offset];\n let next: string | undefined = this.#buffer[offset + 1];\n do {\n // If the current character is a quote, check the next characters for closing quotes.\n if (cur === this.#quotation) {\n // If the cur character is a quote and the next character is a quote,\n // then append a quote to the value and skip two characters.\n if (next === this.#quotation) {\n // Append a quote to the value and skip two characters.\n value += this.#quotation;\n offset += 2;\n cur = this.#buffer[offset];\n next = this.#buffer[offset + 1];\n\n // Update the diff\n column += 2;\n continue;\n }\n\n // If the cur character is a quote and the next character is undefined,\n // then return null.\n if (next === undefined && this.#flush === false) {\n return null;\n }\n\n // Otherwise, return the quoted string.\n // Update the buffer and return the token\n offset++;\n this.#buffer = this.#buffer.slice(offset);\n const start: Position = { ...this.#cursor };\n this.#cursor.column += column;\n this.#cursor.offset += offset;\n this.#cursor.line += line;\n return {\n type: Field,\n value,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber,\n },\n };\n }\n\n // Append the character to the value.\n value += cur;\n\n // Prepare for the next iteration\n if (cur === LF) {\n // If the current character is a LF,\n // then increment the line number and reset the column number.\n line++;\n column = 1;\n } else {\n // Otherwise, increment the column number and offset.\n column++;\n }\n\n offset++;\n cur = next;\n next = this.#buffer[offset + 1];\n } while (cur !== undefined);\n\n if (this.#flush) {\n throw new ParseError(\"Unexpected EOF while parsing quoted field.\", {\n position: { ...this.#cursor },\n });\n }\n return null;\n }\n\n // Check for Unquoted String\n const match = this.#matcher.exec(this.#buffer);\n if (match) {\n // If we're flushing and the match doesn't consume the entire buffer,\n // then return null\n if (this.#flush === false && match[0].length === this.#buffer.length) {\n return null;\n }\n const value = match[1];\n this.#buffer = this.#buffer.slice(value.length);\n const start: Position = { ...this.#cursor };\n this.#cursor.column += value.length;\n this.#cursor.offset += value.length;\n return {\n type: Field,\n value,\n location: {\n start,\n end: { ...this.#cursor },\n rowNumber: this.#rowNumber,\n },\n };\n }\n\n // Otherwise, return null\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,13 +1,22 @@
1
1
  import { RecordDelimiter, FieldDelimiter } from './common/constants.js';
2
2
  import { ParseError } from './common/errors.js';
3
3
 
4
+ const DEFAULT_MAX_FIELD_COUNT = 1e5;
4
5
  class RecordAssembler {
5
6
  #fieldIndex = 0;
6
7
  #row = [];
7
8
  #header;
8
9
  #dirty = false;
9
10
  #signal;
11
+ #maxFieldCount;
10
12
  constructor(options = {}) {
13
+ const mfc = options.maxFieldCount ?? DEFAULT_MAX_FIELD_COUNT;
14
+ if (!(Number.isFinite(mfc) || mfc === Number.POSITIVE_INFINITY) || Number.isFinite(mfc) && (mfc < 1 || !Number.isInteger(mfc))) {
15
+ throw new RangeError(
16
+ "maxFieldCount must be a positive integer or Number.POSITIVE_INFINITY"
17
+ );
18
+ }
19
+ this.#maxFieldCount = mfc;
11
20
  if (options.header !== void 0 && Array.isArray(options.header)) {
12
21
  this.#setHeader(options.header);
13
22
  }
@@ -21,6 +30,7 @@ class RecordAssembler {
21
30
  switch (token.type) {
22
31
  case FieldDelimiter:
23
32
  this.#fieldIndex++;
33
+ this.#checkFieldCount();
24
34
  this.#dirty = true;
25
35
  break;
26
36
  case RecordDelimiter:
@@ -63,7 +73,19 @@ class RecordAssembler {
63
73
  }
64
74
  }
65
75
  }
76
+ #checkFieldCount() {
77
+ if (this.#fieldIndex + 1 > this.#maxFieldCount) {
78
+ throw new RangeError(
79
+ `Field count (${this.#fieldIndex + 1}) exceeded maximum allowed count of ${this.#maxFieldCount}`
80
+ );
81
+ }
82
+ }
66
83
  #setHeader(header) {
84
+ if (header.length > this.#maxFieldCount) {
85
+ throw new RangeError(
86
+ `Header field count (${header.length}) exceeded maximum allowed count of ${this.#maxFieldCount}`
87
+ );
88
+ }
67
89
  this.#header = header;
68
90
  if (this.#header.length === 0) {
69
91
  throw new ParseError("The header must not be empty.");
@@ -1 +1 @@
1
- {"version":3,"file":"RecordAssembler.js","sources":["../src/RecordAssembler.ts"],"sourcesContent":["import { FieldDelimiter, RecordDelimiter } from \"./common/constants.ts\";\nimport { ParseError } from \"./common/errors.ts\";\nimport type {\n CSVRecord,\n RecordAssemblerOptions,\n Token,\n} from \"./common/types.ts\";\n\nexport class RecordAssembler<Header extends ReadonlyArray<string>> {\n #fieldIndex = 0;\n #row: string[] = [];\n #header: Header | undefined;\n #dirty = false;\n #signal?: AbortSignal;\n\n constructor(options: RecordAssemblerOptions<Header> = {}) {\n if (options.header !== undefined && Array.isArray(options.header)) {\n this.#setHeader(options.header);\n }\n if (options.signal) {\n this.#signal = options.signal;\n }\n }\n\n public *assemble(\n tokens: Iterable<Token>,\n flush = true,\n ): IterableIterator<CSVRecord<Header>> {\n for (const token of tokens) {\n this.#signal?.throwIfAborted();\n switch (token.type) {\n case FieldDelimiter:\n this.#fieldIndex++;\n this.#dirty = true;\n break;\n case RecordDelimiter:\n if (this.#header === undefined) {\n this.#setHeader(this.#row as unknown as Header);\n } else {\n if (this.#dirty) {\n yield Object.fromEntries(\n this.#header.map((header, index) => [\n header,\n this.#row.at(index),\n ]),\n ) as unknown as CSVRecord<Header>;\n } else {\n yield Object.fromEntries(\n this.#header.map((header) => [header, \"\"]),\n ) as CSVRecord<Header>;\n }\n }\n // Reset the row fields buffer.\n this.#fieldIndex = 0;\n this.#row = new Array(this.#header?.length).fill(\"\");\n this.#dirty = false;\n break;\n default:\n this.#dirty = true;\n this.#row[this.#fieldIndex] = token.value;\n break;\n }\n }\n\n if (flush) {\n yield* this.flush();\n }\n }\n\n public *flush(): Generator<CSVRecord<Header>> {\n if (this.#header !== undefined) {\n if (this.#dirty) {\n yield Object.fromEntries(\n this.#header\n .filter((v) => v)\n .map((header, index) => [header, this.#row.at(index)]),\n ) as unknown as CSVRecord<Header>;\n }\n }\n }\n\n #setHeader(header: Header) {\n this.#header = header;\n if (this.#header.length === 0) {\n throw new ParseError(\"The header must not be empty.\");\n }\n if (new Set(this.#header).size !== this.#header.length) {\n throw new ParseError(\"The header must not contain duplicate fields.\");\n }\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"RecordAssembler.js","sources":["../src/RecordAssembler.ts"],"sourcesContent":["import { FieldDelimiter, RecordDelimiter } from \"./common/constants.ts\";\nimport { ParseError } from \"./common/errors.ts\";\nimport type {\n CSVRecord,\n RecordAssemblerOptions,\n Token,\n} from \"./common/types.ts\";\n\n/**\n * Default maximum field count per record (100,000 fields).\n */\nconst DEFAULT_MAX_FIELD_COUNT = 100_000;\n\nexport class RecordAssembler<Header extends ReadonlyArray<string>> {\n #fieldIndex = 0;\n #row: string[] = [];\n #header: Header | undefined;\n #dirty = false;\n #signal?: AbortSignal;\n #maxFieldCount: number;\n\n constructor(options: RecordAssemblerOptions<Header> = {}) {\n const mfc = options.maxFieldCount ?? DEFAULT_MAX_FIELD_COUNT;\n // Validate maxFieldCount\n if (\n !(Number.isFinite(mfc) || mfc === Number.POSITIVE_INFINITY) ||\n (Number.isFinite(mfc) && (mfc < 1 || !Number.isInteger(mfc)))\n ) {\n throw new RangeError(\n \"maxFieldCount must be a positive integer or Number.POSITIVE_INFINITY\",\n );\n }\n this.#maxFieldCount = mfc;\n if (options.header !== undefined && Array.isArray(options.header)) {\n this.#setHeader(options.header);\n }\n if (options.signal) {\n this.#signal = options.signal;\n }\n }\n\n public *assemble(\n tokens: Iterable<Token>,\n flush = true,\n ): IterableIterator<CSVRecord<Header>> {\n for (const token of tokens) {\n this.#signal?.throwIfAborted();\n switch (token.type) {\n case FieldDelimiter:\n this.#fieldIndex++;\n this.#checkFieldCount();\n this.#dirty = true;\n break;\n case RecordDelimiter:\n if (this.#header === undefined) {\n this.#setHeader(this.#row as unknown as Header);\n } else {\n if (this.#dirty) {\n yield Object.fromEntries(\n this.#header.map((header, index) => [\n header,\n this.#row.at(index),\n ]),\n ) as unknown as CSVRecord<Header>;\n } else {\n yield Object.fromEntries(\n this.#header.map((header) => [header, \"\"]),\n ) as CSVRecord<Header>;\n }\n }\n // Reset the row fields buffer.\n this.#fieldIndex = 0;\n this.#row = new Array(this.#header?.length).fill(\"\");\n this.#dirty = false;\n break;\n default:\n this.#dirty = true;\n this.#row[this.#fieldIndex] = token.value;\n break;\n }\n }\n\n if (flush) {\n yield* this.flush();\n }\n }\n\n public *flush(): Generator<CSVRecord<Header>> {\n if (this.#header !== undefined) {\n if (this.#dirty) {\n yield Object.fromEntries(\n this.#header\n .filter((v) => v)\n .map((header, index) => [header, this.#row.at(index)]),\n ) as unknown as CSVRecord<Header>;\n }\n }\n }\n\n #checkFieldCount(): void {\n if (this.#fieldIndex + 1 > this.#maxFieldCount) {\n throw new RangeError(\n `Field count (${this.#fieldIndex + 1}) exceeded maximum allowed count of ${this.#maxFieldCount}`,\n );\n }\n }\n\n #setHeader(header: Header) {\n if (header.length > this.#maxFieldCount) {\n throw new RangeError(\n `Header field count (${header.length}) exceeded maximum allowed count of ${this.#maxFieldCount}`,\n );\n }\n this.#header = header;\n if (this.#header.length === 0) {\n throw new ParseError(\"The header must not be empty.\");\n }\n if (new Set(this.#header).size !== this.#header.length) {\n throw new ParseError(\"The header must not contain duplicate fields.\");\n }\n }\n}\n"],"names":[],"mappings":"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"}
@@ -24,6 +24,12 @@ function assertCommonOptions(options) {
24
24
  "delimiter must not be the same as quotation, use different characters"
25
25
  );
26
26
  }
27
+ const mbs = options.maxBufferSize;
28
+ if (!(Number.isFinite(mbs) || mbs === Number.POSITIVE_INFINITY) || Number.isFinite(mbs) && (mbs < 1 || !Number.isInteger(mbs))) {
29
+ throw new RangeError(
30
+ "maxBufferSize must be a positive integer (in characters) or Number.POSITIVE_INFINITY"
31
+ );
32
+ }
27
33
  }
28
34
 
29
35
  export { assertCommonOptions };
@@ -1 +1 @@
1
- {"version":3,"file":"assertCommonOptions.js","sources":["../src/assertCommonOptions.ts"],"sourcesContent":["import type { CommonOptions } from \"./common/types.ts\";\nimport { CR, LF } from \"./constants.ts\";\n\n/**\n * Asserts that the provided value is a string and satisfies certain conditions.\n * @param value - The value to be checked.\n * @param name - The name of the option.\n * @throws {RangeError} If the value is empty, longer than 1 byte, or includes CR or LF.\n * @throws {TypeError} If the value is not a string.\n */\nfunction assertOptionValue(\n value: string,\n name: string,\n): asserts value is string {\n if (typeof value === \"string\") {\n switch (true) {\n case value.length === 0:\n throw new RangeError(`${name} must not be empty`);\n case value.length > 1:\n throw new RangeError(`${name} must be a single character`);\n case value === LF:\n case value === CR:\n throw new RangeError(`${name} must not include CR or LF`);\n default:\n break;\n }\n } else {\n throw new TypeError(`${name} must be a string`);\n }\n}\n\n/**\n * Asserts that the provided options object contains all the required properties.\n * Throws an error if any required property is missing\n * or if the delimiter and quotation length is not 1 byte character,\n * or if the delimiter is the same as the quotation.\n *\n * @example\n *\n * ```ts\n * assertCommonOptions({\n * quotation: '\"',\n * delimiter: ',',\n * });\n * ```\n *\n * @param options - The options object to be validated.\n * @throws {RangeError} If any required property is missing or if the delimiter is the same as the quotation.\n * @throws {TypeError} If any required property is not a string.\n */\nexport function assertCommonOptions<\n Delimiter extends string,\n Quotation extends string,\n>(\n options: Required<CommonOptions<Delimiter, Quotation>>,\n): asserts options is Required<CommonOptions<Delimiter, Quotation>> {\n for (const name of [\"delimiter\", \"quotation\"] as const) {\n assertOptionValue(options[name], name);\n }\n // @ts-ignore: TS doesn't understand that the values are strings\n if (options.delimiter === options.quotation) {\n throw new RangeError(\n \"delimiter must not be the same as quotation, use different characters\",\n );\n }\n}\n"],"names":[],"mappings":";;AAUA,SAAS,iBAAA,CACP,OACA,IACyB,EAAA;AACzB,EAAI,IAAA,OAAO,UAAU,QAAU,EAAA;AAC7B,IAAA,QAAQ,IAAM;AAAA,MACZ,KAAK,MAAM,MAAW,KAAA,CAAA;AACpB,QAAA,MAAM,IAAI,UAAA,CAAW,CAAG,EAAA,IAAI,CAAoB,kBAAA,CAAA,CAAA;AAAA,MAClD,KAAK,MAAM,MAAS,GAAA,CAAA;AAClB,QAAA,MAAM,IAAI,UAAA,CAAW,CAAG,EAAA,IAAI,CAA6B,2BAAA,CAAA,CAAA;AAAA,MAC3D,KAAK,KAAU,KAAA,EAAA;AAAA,MACf,KAAK,KAAU,KAAA,EAAA;AACb,QAAA,MAAM,IAAI,UAAA,CAAW,CAAG,EAAA,IAAI,CAA4B,0BAAA,CAAA,CAAA;AAExD;AACJ,GACK,MAAA;AACL,IAAA,MAAM,IAAI,SAAA,CAAU,CAAG,EAAA,IAAI,CAAmB,iBAAA,CAAA,CAAA;AAAA;AAElD;AAqBO,SAAS,oBAId,OACkE,EAAA;AAClE,EAAA,KAAA,MAAW,IAAQ,IAAA,CAAC,WAAa,EAAA,WAAW,CAAY,EAAA;AACtD,IAAkB,iBAAA,CAAA,OAAA,CAAQ,IAAI,CAAA,EAAG,IAAI,CAAA;AAAA;AAGvC,EAAI,IAAA,OAAA,CAAQ,SAAc,KAAA,OAAA,CAAQ,SAAW,EAAA;AAC3C,IAAA,MAAM,IAAI,UAAA;AAAA,MACR;AAAA,KACF;AAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"assertCommonOptions.js","sources":["../src/assertCommonOptions.ts"],"sourcesContent":["import type { CommonOptions } from \"./common/types.ts\";\nimport { CR, LF } from \"./constants.ts\";\n\n/**\n * Asserts that the provided value is a string and satisfies certain conditions.\n * @param value - The value to be checked.\n * @param name - The name of the option.\n * @throws {RangeError} If the value is empty, longer than 1 byte, or includes CR or LF.\n * @throws {TypeError} If the value is not a string.\n */\nfunction assertOptionValue(\n value: string,\n name: string,\n): asserts value is string {\n if (typeof value === \"string\") {\n switch (true) {\n case value.length === 0:\n throw new RangeError(`${name} must not be empty`);\n case value.length > 1:\n throw new RangeError(`${name} must be a single character`);\n case value === LF:\n case value === CR:\n throw new RangeError(`${name} must not include CR or LF`);\n default:\n break;\n }\n } else {\n throw new TypeError(`${name} must be a string`);\n }\n}\n\n/**\n * Asserts that the provided options object contains all the required properties.\n * Throws an error if any required property is missing\n * or if the delimiter and quotation length is not 1 byte character,\n * or if the delimiter is the same as the quotation.\n *\n * @example\n *\n * ```ts\n * assertCommonOptions({\n * quotation: '\"',\n * delimiter: ',',\n * });\n * ```\n *\n * @param options - The options object to be validated.\n * @throws {RangeError} If any required property is missing or if the delimiter is the same as the quotation.\n * @throws {TypeError} If any required property is not a string.\n */\nexport function assertCommonOptions<\n Delimiter extends string,\n Quotation extends string,\n>(\n options: Required<CommonOptions<Delimiter, Quotation>>,\n): asserts options is Required<CommonOptions<Delimiter, Quotation>> {\n for (const name of [\"delimiter\", \"quotation\"] as const) {\n assertOptionValue(options[name], name);\n }\n // @ts-ignore: TS doesn't understand that the values are strings\n if (options.delimiter === options.quotation) {\n throw new RangeError(\n \"delimiter must not be the same as quotation, use different characters\",\n );\n }\n\n // Validate maxBufferSize\n const mbs = options.maxBufferSize;\n if (\n !(Number.isFinite(mbs) || mbs === Number.POSITIVE_INFINITY) ||\n (Number.isFinite(mbs) && (mbs < 1 || !Number.isInteger(mbs)))\n ) {\n throw new RangeError(\n \"maxBufferSize must be a positive integer (in characters) or Number.POSITIVE_INFINITY\",\n );\n }\n}\n"],"names":[],"mappings":";;AAUA,SAAS,iBAAA,CACP,OACA,IACyB,EAAA;AACzB,EAAI,IAAA,OAAO,UAAU,QAAU,EAAA;AAC7B,IAAA,QAAQ,IAAM;AAAA,MACZ,KAAK,MAAM,MAAW,KAAA,CAAA;AACpB,QAAA,MAAM,IAAI,UAAA,CAAW,CAAG,EAAA,IAAI,CAAoB,kBAAA,CAAA,CAAA;AAAA,MAClD,KAAK,MAAM,MAAS,GAAA,CAAA;AAClB,QAAA,MAAM,IAAI,UAAA,CAAW,CAAG,EAAA,IAAI,CAA6B,2BAAA,CAAA,CAAA;AAAA,MAC3D,KAAK,KAAU,KAAA,EAAA;AAAA,MACf,KAAK,KAAU,KAAA,EAAA;AACb,QAAA,MAAM,IAAI,UAAA,CAAW,CAAG,EAAA,IAAI,CAA4B,0BAAA,CAAA,CAAA;AAExD;AACJ,GACK,MAAA;AACL,IAAA,MAAM,IAAI,SAAA,CAAU,CAAG,EAAA,IAAI,CAAmB,iBAAA,CAAA,CAAA;AAAA;AAElD;AAqBO,SAAS,oBAId,OACkE,EAAA;AAClE,EAAA,KAAA,MAAW,IAAQ,IAAA,CAAC,WAAa,EAAA,WAAW,CAAY,EAAA;AACtD,IAAkB,iBAAA,CAAA,OAAA,CAAQ,IAAI,CAAA,EAAG,IAAI,CAAA;AAAA;AAGvC,EAAI,IAAA,OAAA,CAAQ,SAAc,KAAA,OAAA,CAAQ,SAAW,EAAA;AAC3C,IAAA,MAAM,IAAI,UAAA;AAAA,MACR;AAAA,KACF;AAAA;AAIF,EAAA,MAAM,MAAM,OAAQ,CAAA,aAAA;AACpB,EAAA,IACE,EAAE,MAAO,CAAA,QAAA,CAAS,GAAG,CAAK,IAAA,GAAA,KAAQ,OAAO,iBACxC,CAAA,IAAA,MAAA,CAAO,QAAS,CAAA,GAAG,MAAM,GAAM,GAAA,CAAA,IAAK,CAAC,MAAO,CAAA,SAAA,CAAU,GAAG,CAC1D,CAAA,EAAA;AACA,IAAA,MAAM,IAAI,UAAA;AAAA,MACR;AAAA,KACF;AAAA;AAEJ;;;;"}
@@ -155,6 +155,20 @@ export interface CommonOptions<Delimiter extends string, Quotation extends strin
155
155
  * @default '"'
156
156
  */
157
157
  quotation?: Quotation;
158
+ /**
159
+ * Maximum internal buffer size in characters.
160
+ *
161
+ * @remarks
162
+ * This option limits the size of the internal buffer used during lexing
163
+ * to prevent memory exhaustion attacks. The buffer size is measured in
164
+ * UTF-16 code units (JavaScript string length). When the buffer exceeds
165
+ * this limit, a `RangeError` will be thrown.
166
+ *
167
+ * Set to `Infinity` to disable the limit (not recommended for untrusted input).
168
+ *
169
+ * @default 10 * 1024 * 1024 (approximately 10MB for ASCII, but may vary for non-ASCII)
170
+ */
171
+ maxBufferSize?: number;
158
172
  }
159
173
  /**
160
174
  * CSV Parsing Options for binary.
@@ -208,6 +222,36 @@ export interface BinaryOptions {
208
222
  * @default false
209
223
  */
210
224
  fatal?: boolean;
225
+ /**
226
+ * Allow experimental or future compression formats not explicitly supported by this library.
227
+ *
228
+ * @remarks
229
+ * When `true`, unknown compression formats from Content-Encoding headers will be passed
230
+ * to the runtime's DecompressionStream without validation. This allows using newer
231
+ * compression formats (like Brotli) if your runtime supports them, even before this
232
+ * library is updated to explicitly support them.
233
+ *
234
+ * When `false` (default), only known formats are allowed: gzip, deflate, deflate-raw.
235
+ *
236
+ * **Use with caution**: Enabling this bypasses library validation and relies entirely
237
+ * on runtime error handling. If the runtime doesn't support the format, you'll get
238
+ * a runtime error instead of a clear validation error from this library.
239
+ *
240
+ * @default false
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * // Safe mode (default): Only known formats
245
+ * const response = await fetch('data.csv.gz');
246
+ * await parse(response); // ✓ Works
247
+ *
248
+ * // Experimental mode: Allow future formats
249
+ * const response = await fetch('data.csv.br'); // Brotli
250
+ * await parse(response, { allowExperimentalCompressions: true });
251
+ * // Works if runtime supports Brotli, otherwise throws runtime error
252
+ * ```
253
+ */
254
+ allowExperimentalCompressions?: boolean;
211
255
  }
212
256
  /**
213
257
  * Record Assembler Options for CSV.
@@ -234,6 +278,19 @@ export interface RecordAssemblerOptions<Header extends ReadonlyArray<string>> ex
234
278
  * @default undefined
235
279
  */
236
280
  header?: Header;
281
+ /**
282
+ * Maximum number of fields allowed per record.
283
+ *
284
+ * @remarks
285
+ * This option limits the number of columns/fields in a CSV record
286
+ * to prevent memory exhaustion attacks. When a record exceeds this limit,
287
+ * a {@link FieldCountLimitError} will be thrown.
288
+ *
289
+ * Set to `Number.POSITIVE_INFINITY` to disable the limit (not recommended for untrusted input).
290
+ *
291
+ * @default 100000
292
+ */
293
+ maxFieldCount?: number;
237
294
  }
238
295
  /**
239
296
  * Parse options for CSV string.
@@ -5,6 +5,6 @@ import { ParseBinaryOptions } from './common/types.ts';
5
5
  * @param response - The response object from which to extract the options.
6
6
  * @param options - The options to merge with the extracted options.
7
7
  * @returns The options extracted from the response.
8
- * @throws {RangeError} - The content type is not supported.
8
+ * @throws {TypeError} - The content type is not supported or the content-encoding is invalid.
9
9
  */
10
10
  export declare function getOptionsFromResponse<Header extends ReadonlyArray<string>>(response: Response, options?: ParseBinaryOptions<Header>): ParseBinaryOptions<Header>;
@@ -1,13 +1,38 @@
1
1
  import { parseMime } from './utils/parseMime.js';
2
2
 
3
+ const SUPPORTED_COMPRESSIONS = /* @__PURE__ */ new Set([
4
+ "gzip",
5
+ "deflate",
6
+ "deflate-raw"
7
+ ]);
3
8
  function getOptionsFromResponse(response, options = {}) {
4
9
  const { headers } = response;
5
10
  const contentType = headers.get("content-type") ?? "text/csv";
6
11
  const mime = parseMime(contentType);
7
12
  if (mime.type !== "text/csv") {
8
- throw new RangeError(`Invalid mime type: "${contentType}"`);
13
+ throw new TypeError(`Invalid mime type: "${contentType}"`);
14
+ }
15
+ const contentEncoding = headers.get("content-encoding");
16
+ let decomposition;
17
+ if (contentEncoding) {
18
+ const normalizedEncoding = contentEncoding.trim().toLowerCase();
19
+ if (normalizedEncoding.includes(",")) {
20
+ throw new TypeError(
21
+ `Multiple content-encodings are not supported: "${contentEncoding}"`
22
+ );
23
+ }
24
+ if (SUPPORTED_COMPRESSIONS.has(normalizedEncoding)) {
25
+ decomposition = normalizedEncoding;
26
+ } else if (normalizedEncoding) {
27
+ if (options.allowExperimentalCompressions) {
28
+ decomposition = normalizedEncoding;
29
+ } else {
30
+ throw new TypeError(
31
+ `Unsupported content-encoding: "${contentEncoding}". Supported formats: ${Array.from(SUPPORTED_COMPRESSIONS).join(", ")}. To use experimental formats, set allowExperimentalCompressions: true`
32
+ );
33
+ }
34
+ }
9
35
  }
10
- const decomposition = headers.get("content-encoding") ?? void 0;
11
36
  const charset = mime.parameters.charset ?? "utf-8";
12
37
  return {
13
38
  decomposition,
@@ -1 +1 @@
1
- {"version":3,"file":"getOptionsFromResponse.js","sources":["../src/getOptionsFromResponse.ts"],"sourcesContent":["import type { ParseBinaryOptions } from \"./common/types.ts\";\nimport { parseMime } from \"./utils/parseMime.ts\";\n\n/**\n * Extracts the options from the response object.\n *\n * @param response - The response object from which to extract the options.\n * @param options - The options to merge with the extracted options.\n * @returns The options extracted from the response.\n * @throws {RangeError} - The content type is not supported.\n */\nexport function getOptionsFromResponse<Header extends ReadonlyArray<string>>(\n response: Response,\n options: ParseBinaryOptions<Header> = {},\n): ParseBinaryOptions<Header> {\n const { headers } = response;\n const contentType = headers.get(\"content-type\") ?? \"text/csv\";\n const mime = parseMime(contentType);\n if (mime.type !== \"text/csv\") {\n throw new RangeError(`Invalid mime type: \"${contentType}\"`);\n }\n const decomposition =\n (headers.get(\"content-encoding\") as CompressionFormat) ?? undefined;\n const charset = mime.parameters.charset ?? \"utf-8\";\n // TODO: Support header=present and header=absent\n // const header = mime.parameters.header ?? \"present\";\n return {\n decomposition,\n charset,\n ...options,\n };\n}\n"],"names":[],"mappings":";;AAWO,SAAS,sBACd,CAAA,QAAA,EACA,OAAsC,GAAA,EACV,EAAA;AAC5B,EAAM,MAAA,EAAE,SAAY,GAAA,QAAA;AACpB,EAAA,MAAM,WAAc,GAAA,OAAA,CAAQ,GAAI,CAAA,cAAc,CAAK,IAAA,UAAA;AACnD,EAAM,MAAA,IAAA,GAAO,UAAU,WAAW,CAAA;AAClC,EAAI,IAAA,IAAA,CAAK,SAAS,UAAY,EAAA;AAC5B,IAAA,MAAM,IAAI,UAAA,CAAW,CAAuB,oBAAA,EAAA,WAAW,CAAG,CAAA,CAAA,CAAA;AAAA;AAE5D,EAAA,MAAM,aACH,GAAA,OAAA,CAAQ,GAAI,CAAA,kBAAkB,CAA2B,IAAA,MAAA;AAC5D,EAAM,MAAA,OAAA,GAAU,IAAK,CAAA,UAAA,CAAW,OAAW,IAAA,OAAA;AAG3C,EAAO,OAAA;AAAA,IACL,aAAA;AAAA,IACA,OAAA;AAAA,IACA,GAAG;AAAA,GACL;AACF;;;;"}
1
+ {"version":3,"file":"getOptionsFromResponse.js","sources":["../src/getOptionsFromResponse.ts"],"sourcesContent":["import type { ParseBinaryOptions } from \"./common/types.ts\";\nimport { parseMime } from \"./utils/parseMime.ts\";\n\n/**\n * Supported compression formats for CSV decompression.\n * These correspond to the Web Standard CompressionFormat values.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/CompressionFormat | CompressionFormat}\n */\nconst SUPPORTED_COMPRESSIONS: ReadonlySet<CompressionFormat> = new Set([\n \"gzip\",\n \"deflate\",\n \"deflate-raw\",\n]);\n\n/**\n * Extracts the options from the response object.\n *\n * @param response - The response object from which to extract the options.\n * @param options - The options to merge with the extracted options.\n * @returns The options extracted from the response.\n * @throws {TypeError} - The content type is not supported or the content-encoding is invalid.\n */\nexport function getOptionsFromResponse<Header extends ReadonlyArray<string>>(\n response: Response,\n options: ParseBinaryOptions<Header> = {},\n): ParseBinaryOptions<Header> {\n const { headers } = response;\n const contentType = headers.get(\"content-type\") ?? \"text/csv\";\n const mime = parseMime(contentType);\n if (mime.type !== \"text/csv\") {\n throw new TypeError(`Invalid mime type: \"${contentType}\"`);\n }\n\n const contentEncoding = headers.get(\"content-encoding\");\n let decomposition: CompressionFormat | undefined;\n\n if (contentEncoding) {\n const normalizedEncoding = contentEncoding.trim().toLowerCase();\n\n if (normalizedEncoding.includes(\",\")) {\n throw new TypeError(\n `Multiple content-encodings are not supported: \"${contentEncoding}\"`,\n );\n }\n\n if (SUPPORTED_COMPRESSIONS.has(normalizedEncoding as CompressionFormat)) {\n decomposition = normalizedEncoding as CompressionFormat;\n } else if (normalizedEncoding) {\n // Unknown compression format\n if (options.allowExperimentalCompressions) {\n // Allow runtime to handle experimental/future formats\n decomposition = normalizedEncoding as CompressionFormat;\n } else {\n throw new TypeError(\n `Unsupported content-encoding: \"${contentEncoding}\". Supported formats: ${Array.from(SUPPORTED_COMPRESSIONS).join(\", \")}. To use experimental formats, set allowExperimentalCompressions: true`,\n );\n }\n }\n }\n\n const charset = mime.parameters.charset ?? \"utf-8\";\n // TODO: Support header=present and header=absent\n // const header = mime.parameters.header ?? \"present\";\n return {\n decomposition,\n charset,\n ...options,\n };\n}\n"],"names":[],"mappings":";;AASA,MAAM,sBAAA,uBAA6D,GAAI,CAAA;AAAA,EACrE,MAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAC,CAAA;AAUM,SAAS,sBACd,CAAA,QAAA,EACA,OAAsC,GAAA,EACV,EAAA;AAC5B,EAAM,MAAA,EAAE,SAAY,GAAA,QAAA;AACpB,EAAA,MAAM,WAAc,GAAA,OAAA,CAAQ,GAAI,CAAA,cAAc,CAAK,IAAA,UAAA;AACnD,EAAM,MAAA,IAAA,GAAO,UAAU,WAAW,CAAA;AAClC,EAAI,IAAA,IAAA,CAAK,SAAS,UAAY,EAAA;AAC5B,IAAA,MAAM,IAAI,SAAA,CAAU,CAAuB,oBAAA,EAAA,WAAW,CAAG,CAAA,CAAA,CAAA;AAAA;AAG3D,EAAM,MAAA,eAAA,GAAkB,OAAQ,CAAA,GAAA,CAAI,kBAAkB,CAAA;AACtD,EAAI,IAAA,aAAA;AAEJ,EAAA,IAAI,eAAiB,EAAA;AACnB,IAAA,MAAM,kBAAqB,GAAA,eAAA,CAAgB,IAAK,EAAA,CAAE,WAAY,EAAA;AAE9D,IAAI,IAAA,kBAAA,CAAmB,QAAS,CAAA,GAAG,CAAG,EAAA;AACpC,MAAA,MAAM,IAAI,SAAA;AAAA,QACR,kDAAkD,eAAe,CAAA,CAAA;AAAA,OACnE;AAAA;AAGF,IAAI,IAAA,sBAAA,CAAuB,GAAI,CAAA,kBAAuC,CAAG,EAAA;AACvE,MAAgB,aAAA,GAAA,kBAAA;AAAA,eACP,kBAAoB,EAAA;AAE7B,MAAA,IAAI,QAAQ,6BAA+B,EAAA;AAEzC,QAAgB,aAAA,GAAA,kBAAA;AAAA,OACX,MAAA;AACL,QAAA,MAAM,IAAI,SAAA;AAAA,UACR,CAAA,+BAAA,EAAkC,eAAe,CAAyB,sBAAA,EAAA,KAAA,CAAM,KAAK,sBAAsB,CAAA,CAAE,IAAK,CAAA,IAAI,CAAC,CAAA,sEAAA;AAAA,SACzH;AAAA;AACF;AACF;AAGF,EAAM,MAAA,OAAA,GAAU,IAAK,CAAA,UAAA,CAAW,OAAW,IAAA,OAAA;AAG3C,EAAO,OAAA;AAAA,IACL,aAAA;AAAA,IACA,OAAA;AAAA,IACA,GAAG;AAAA,GACL;AACF;;;;"}
@@ -8,7 +8,7 @@ function parseResponse(response, options) {
8
8
  try {
9
9
  const options_ = getOptionsFromResponse(response, options);
10
10
  if (response.body === null) {
11
- throw new RangeError("Response body is null");
11
+ throw new TypeError("Response body is null");
12
12
  }
13
13
  return parseUint8ArrayStream(response.body, options_);
14
14
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"parseResponse.js","sources":["../src/parseResponse.ts"],"sourcesContent":["import type { CSVRecord, ParseOptions } from \"./common/types.ts\";\nimport { commonParseErrorHandling } from \"./commonParseErrorHandling.ts\";\nimport { getOptionsFromResponse } from \"./getOptionsFromResponse.ts\";\nimport { parseResponseToStream } from \"./parseResponseToStream.ts\";\nimport { parseUint8ArrayStream } from \"./parseUint8ArrayStream.ts\";\nimport * as internal from \"./utils/convertThisAsyncIterableIteratorToArray.ts\";\n\n/**\n * Parse HTTP Response what contains CSV to records,\n * ideal for smaller data sets.\n *\n * @remarks\n * This function automatically treats response headers.\n *\n * - If `Content-Type` header is not set, it assumes `text/csv`.\n * - If `Content-Type` header is not `text/csv`, it throws an error.\n * - If `Content-Type` header has charset parameter, it uses it for decoding.\n * - If `Content-Encoding` header is set, it decompresses the response.\n * - Should there be any conflicting information between the header and the options, the option's value will take precedence.\n *\n * @category Middle-level API\n * @param response\n * @param options\n * @returns Async iterable iterator of records.\n *\n * If you want array of records, use {@link parseResponse.toArray} function.\n *\n * @example Parsing CSV Response\n *\n * ```ts\n * import { parseResponse } from 'web-csv-toolbox';\n *\n * const response = await fetch('https://example.com/data.csv');\n *\n * for await (const record of parseResponse(response)) {\n * console.log(record);\n * }\n * ```\n */\nexport function parseResponse<Header extends ReadonlyArray<string>>(\n response: Response,\n options?: ParseOptions<Header>,\n): AsyncIterableIterator<CSVRecord<Header>> {\n try {\n const options_ = getOptionsFromResponse(response, options);\n if (response.body === null) {\n throw new RangeError(\"Response body is null\");\n }\n return parseUint8ArrayStream(response.body, options_);\n } catch (error) {\n commonParseErrorHandling(error);\n }\n}\n\nexport declare namespace parseResponse {\n /**\n * Parse CSV Response to array of records.\n *\n * @returns Array of records\n *\n * @example Parsing CSV Response\n *\n * ```ts\n * import { parseResponse } from 'web-csv-toolbox';\n *\n * const response = await fetch('https://example.com/data.csv');\n *\n * const records = await parseResponse.toArray(response);\n * console.log(records);\n * ```\n */\n export function toArray<Header extends ReadonlyArray<string>>(\n response: Response,\n options?: ParseOptions<Header>,\n ): Promise<CSVRecord<Header>[]>;\n /**\n * Parse CSV Response to stream of records.\n *\n * @param response Response to parse\n * @returns Stream of records\n *\n * @example Parsing CSV Response\n *\n * ```ts\n * import { parseResponse } from 'web-csv-toolbox';\n *\n * const response = await fetch('https://example.com/data.csv');\n *\n * await parseResponse.toStream(response)\n * .pipeTo(\n * new WritableStream({\n * write(record) {\n * console.log(record);\n * },\n * }),\n * );\n * // Prints:\n * // { name: 'Alice', age: '42' }\n * // { name: 'Bob', age: '69' }\n * ```\n */\n export function toStream<Header extends ReadonlyArray<string>>(\n response: Response,\n options?: ParseOptions<Header>,\n ): ReadableStream<CSVRecord<Header>[]>;\n}\n\nObject.defineProperties(parseResponse, {\n toArray: {\n enumerable: true,\n writable: false,\n value: internal.convertThisAsyncIterableIteratorToArray,\n },\n toStreamSync: {\n enumerable: true,\n writable: false,\n value: parseResponseToStream,\n },\n});\n"],"names":["internal.convertThisAsyncIterableIteratorToArray"],"mappings":";;;;;;AAuCgB,SAAA,aAAA,CACd,UACA,OAC0C,EAAA;AAC1C,EAAI,IAAA;AACF,IAAM,MAAA,QAAA,GAAW,sBAAuB,CAAA,QAAA,EAAU,OAAO,CAAA;AACzD,IAAI,IAAA,QAAA,CAAS,SAAS,IAAM,EAAA;AAC1B,MAAM,MAAA,IAAI,WAAW,uBAAuB,CAAA;AAAA;AAE9C,IAAO,OAAA,qBAAA,CAAsB,QAAS,CAAA,IAAA,EAAM,QAAQ,CAAA;AAAA,WAC7C,KAAO,EAAA;AACd,IAAA,wBAAA,CAAyB,KAAK,CAAA;AAAA;AAElC;AAuDA,MAAA,CAAO,iBAAiB,aAAe,EAAA;AAAA,EACrC,OAAS,EAAA;AAAA,IACP,UAAY,EAAA,IAAA;AAAA,IACZ,QAAU,EAAA,KAAA;AAAA,IACV,OAAOA;AAAS,GAClB;AAAA,EACA,YAAc,EAAA;AAAA,IACZ,UAAY,EAAA,IAAA;AAAA,IACZ,QAAU,EAAA,KAAA;AAAA,IACV,KAAO,EAAA;AAAA;AAEX,CAAC,CAAA;;;;"}
1
+ {"version":3,"file":"parseResponse.js","sources":["../src/parseResponse.ts"],"sourcesContent":["import type { CSVRecord, ParseOptions } from \"./common/types.ts\";\nimport { commonParseErrorHandling } from \"./commonParseErrorHandling.ts\";\nimport { getOptionsFromResponse } from \"./getOptionsFromResponse.ts\";\nimport { parseResponseToStream } from \"./parseResponseToStream.ts\";\nimport { parseUint8ArrayStream } from \"./parseUint8ArrayStream.ts\";\nimport * as internal from \"./utils/convertThisAsyncIterableIteratorToArray.ts\";\n\n/**\n * Parse HTTP Response what contains CSV to records,\n * ideal for smaller data sets.\n *\n * @remarks\n * This function automatically treats response headers.\n *\n * - If `Content-Type` header is not set, it assumes `text/csv`.\n * - If `Content-Type` header is not `text/csv`, it throws an error.\n * - If `Content-Type` header has charset parameter, it uses it for decoding.\n * - If `Content-Encoding` header is set, it decompresses the response.\n * - Should there be any conflicting information between the header and the options, the option's value will take precedence.\n *\n * @category Middle-level API\n * @param response\n * @param options\n * @returns Async iterable iterator of records.\n *\n * If you want array of records, use {@link parseResponse.toArray} function.\n *\n * @example Parsing CSV Response\n *\n * ```ts\n * import { parseResponse } from 'web-csv-toolbox';\n *\n * const response = await fetch('https://example.com/data.csv');\n *\n * for await (const record of parseResponse(response)) {\n * console.log(record);\n * }\n * ```\n */\nexport function parseResponse<Header extends ReadonlyArray<string>>(\n response: Response,\n options?: ParseOptions<Header>,\n): AsyncIterableIterator<CSVRecord<Header>> {\n try {\n const options_ = getOptionsFromResponse(response, options);\n if (response.body === null) {\n throw new TypeError(\"Response body is null\");\n }\n return parseUint8ArrayStream(response.body, options_);\n } catch (error) {\n commonParseErrorHandling(error);\n }\n}\n\nexport declare namespace parseResponse {\n /**\n * Parse CSV Response to array of records.\n *\n * @returns Array of records\n *\n * @example Parsing CSV Response\n *\n * ```ts\n * import { parseResponse } from 'web-csv-toolbox';\n *\n * const response = await fetch('https://example.com/data.csv');\n *\n * const records = await parseResponse.toArray(response);\n * console.log(records);\n * ```\n */\n export function toArray<Header extends ReadonlyArray<string>>(\n response: Response,\n options?: ParseOptions<Header>,\n ): Promise<CSVRecord<Header>[]>;\n /**\n * Parse CSV Response to stream of records.\n *\n * @param response Response to parse\n * @returns Stream of records\n *\n * @example Parsing CSV Response\n *\n * ```ts\n * import { parseResponse } from 'web-csv-toolbox';\n *\n * const response = await fetch('https://example.com/data.csv');\n *\n * await parseResponse.toStream(response)\n * .pipeTo(\n * new WritableStream({\n * write(record) {\n * console.log(record);\n * },\n * }),\n * );\n * // Prints:\n * // { name: 'Alice', age: '42' }\n * // { name: 'Bob', age: '69' }\n * ```\n */\n export function toStream<Header extends ReadonlyArray<string>>(\n response: Response,\n options?: ParseOptions<Header>,\n ): ReadableStream<CSVRecord<Header>[]>;\n}\n\nObject.defineProperties(parseResponse, {\n toArray: {\n enumerable: true,\n writable: false,\n value: internal.convertThisAsyncIterableIteratorToArray,\n },\n toStreamSync: {\n enumerable: true,\n writable: false,\n value: parseResponseToStream,\n },\n});\n"],"names":["internal.convertThisAsyncIterableIteratorToArray"],"mappings":";;;;;;AAuCgB,SAAA,aAAA,CACd,UACA,OAC0C,EAAA;AAC1C,EAAI,IAAA;AACF,IAAM,MAAA,QAAA,GAAW,sBAAuB,CAAA,QAAA,EAAU,OAAO,CAAA;AACzD,IAAI,IAAA,QAAA,CAAS,SAAS,IAAM,EAAA;AAC1B,MAAM,MAAA,IAAI,UAAU,uBAAuB,CAAA;AAAA;AAE7C,IAAO,OAAA,qBAAA,CAAsB,QAAS,CAAA,IAAA,EAAM,QAAQ,CAAA;AAAA,WAC7C,KAAO,EAAA;AACd,IAAA,wBAAA,CAAyB,KAAK,CAAA;AAAA;AAElC;AAuDA,MAAA,CAAO,iBAAiB,aAAe,EAAA;AAAA,EACrC,OAAS,EAAA;AAAA,IACP,UAAY,EAAA,IAAA;AAAA,IACZ,QAAU,EAAA,KAAA;AAAA,IACV,OAAOA;AAAS,GAClB;AAAA,EACA,YAAc,EAAA;AAAA,IACZ,UAAY,EAAA,IAAA;AAAA,IACZ,QAAU,EAAA,KAAA;AAAA,IACV,KAAO,EAAA;AAAA;AAEX,CAAC,CAAA;;;;"}
@@ -6,7 +6,7 @@ function parseResponseToStream(response, options) {
6
6
  try {
7
7
  const options_ = getOptionsFromResponse(response, options);
8
8
  if (response.body === null) {
9
- throw new RangeError("Response body is null");
9
+ throw new TypeError("Response body is null");
10
10
  }
11
11
  return parseUint8ArrayStreamToStream(response.body, options_);
12
12
  } catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"parseResponseToStream.js","sources":["../src/parseResponseToStream.ts"],"sourcesContent":["import type { CSVRecord, ParseBinaryOptions } from \"./common/types.ts\";\nimport { commonParseErrorHandling } from \"./commonParseErrorHandling.ts\";\nimport { getOptionsFromResponse } from \"./getOptionsFromResponse.ts\";\nimport { parseUint8ArrayStreamToStream } from \"./parseUint8ArrayStreamToStream.ts\";\n\nexport function parseResponseToStream<Header extends ReadonlyArray<string>>(\n response: Response,\n options?: ParseBinaryOptions<Header>,\n): ReadableStream<CSVRecord<Header>> {\n try {\n const options_ = getOptionsFromResponse(response, options);\n if (response.body === null) {\n throw new RangeError(\"Response body is null\");\n }\n return parseUint8ArrayStreamToStream(response.body, options_);\n } catch (error) {\n commonParseErrorHandling(error);\n }\n}\n"],"names":[],"mappings":";;;;AAKgB,SAAA,qBAAA,CACd,UACA,OACmC,EAAA;AACnC,EAAI,IAAA;AACF,IAAM,MAAA,QAAA,GAAW,sBAAuB,CAAA,QAAA,EAAU,OAAO,CAAA;AACzD,IAAI,IAAA,QAAA,CAAS,SAAS,IAAM,EAAA;AAC1B,MAAM,MAAA,IAAI,WAAW,uBAAuB,CAAA;AAAA;AAE9C,IAAO,OAAA,6BAAA,CAA8B,QAAS,CAAA,IAAA,EAAM,QAAQ,CAAA;AAAA,WACrD,KAAO,EAAA;AACd,IAAA,wBAAA,CAAyB,KAAK,CAAA;AAAA;AAElC;;;;"}
1
+ {"version":3,"file":"parseResponseToStream.js","sources":["../src/parseResponseToStream.ts"],"sourcesContent":["import type { CSVRecord, ParseBinaryOptions } from \"./common/types.ts\";\nimport { commonParseErrorHandling } from \"./commonParseErrorHandling.ts\";\nimport { getOptionsFromResponse } from \"./getOptionsFromResponse.ts\";\nimport { parseUint8ArrayStreamToStream } from \"./parseUint8ArrayStreamToStream.ts\";\n\nexport function parseResponseToStream<Header extends ReadonlyArray<string>>(\n response: Response,\n options?: ParseBinaryOptions<Header>,\n): ReadableStream<CSVRecord<Header>> {\n try {\n const options_ = getOptionsFromResponse(response, options);\n if (response.body === null) {\n throw new TypeError(\"Response body is null\");\n }\n return parseUint8ArrayStreamToStream(response.body, options_);\n } catch (error) {\n commonParseErrorHandling(error);\n }\n}\n"],"names":[],"mappings":";;;;AAKgB,SAAA,qBAAA,CACd,UACA,OACmC,EAAA;AACnC,EAAI,IAAA;AACF,IAAM,MAAA,QAAA,GAAW,sBAAuB,CAAA,QAAA,EAAU,OAAO,CAAA;AACzD,IAAI,IAAA,QAAA,CAAS,SAAS,IAAM,EAAA;AAC1B,MAAM,MAAA,IAAI,UAAU,uBAAuB,CAAA;AAAA;AAE7C,IAAO,OAAA,6BAAA,CAA8B,QAAS,CAAA,IAAA,EAAM,QAAQ,CAAA;AAAA,WACrD,KAAO,EAAA;AACd,IAAA,wBAAA,CAAyB,KAAK,CAAA;AAAA;AAElC;;;;"}
@@ -1,9 +1,13 @@
1
1
  import { parseStringToArraySync } from './_virtual/_web-csv-toolbox-wasm.js';
2
2
  import { assertCommonOptions } from './assertCommonOptions.js';
3
- import { DEFAULT_QUOTATION, DEFAULT_DELIMITER, DOUBLE_QUOTE } from './constants.js';
3
+ import { DEFAULT_DELIMITER, DEFAULT_QUOTATION, DOUBLE_QUOTE } from './constants.js';
4
4
 
5
5
  function parseStringToArraySyncWASM(csv, options = {}) {
6
- const { delimiter = DEFAULT_DELIMITER, quotation = DEFAULT_QUOTATION } = options;
6
+ const {
7
+ delimiter = DEFAULT_DELIMITER,
8
+ quotation = DEFAULT_QUOTATION,
9
+ maxBufferSize = 10485760
10
+ } = options;
7
11
  if (typeof delimiter !== "string" || delimiter.length !== 1) {
8
12
  throw new RangeError(
9
13
  "Invalid delimiter, must be a single character on WASM."
@@ -12,7 +16,7 @@ function parseStringToArraySyncWASM(csv, options = {}) {
12
16
  if (quotation !== DOUBLE_QUOTE) {
13
17
  throw new RangeError("Invalid quotation, must be double quote on WASM.");
14
18
  }
15
- assertCommonOptions({ delimiter, quotation });
19
+ assertCommonOptions({ delimiter, quotation, maxBufferSize });
16
20
  const demiliterCode = delimiter.charCodeAt(0);
17
21
  return JSON.parse(parseStringToArraySync(csv, demiliterCode));
18
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"parseStringToArraySyncWASM.js","sources":["../src/parseStringToArraySyncWASM.ts"],"sourcesContent":["import { parseStringToArraySync } from \"web-csv-toolbox-wasm\";\nimport { assertCommonOptions } from \"./assertCommonOptions.ts\";\nimport type { CSVRecord, CommonOptions } from \"./common/types.ts\";\nimport {\n DEFAULT_DELIMITER,\n DEFAULT_QUOTATION,\n DOUBLE_QUOTE,\n} from \"./constants.ts\";\nimport type { loadWASM } from \"./loadWASM.ts\";\nimport type { PickCSVHeader } from \"./utils/types.ts\";\n\n/**\n * Parse CSV string to record of arrays.\n *\n * @param csv CSV string\n * @param options Parse options\n * @returns Record of arrays\n *\n * @remarks\n * This function uses WebAssembly to parse CSV string.\n * Before calling this function, you must call {@link loadWASM} function.\n *\n * **Performance Characteristics:**\n * - **Speed**: 2-3x faster than JavaScript parser for large CSV strings\n * - **Memory usage**: O(n) - proportional to file size (loads entire result into memory)\n * - **Suitable for**: CPU-intensive workloads, large CSV strings on server-side\n * - **Recommended max**: ~100MB (Node.js/Deno)\n *\n * **Limitations:**\n * - Only supports UTF-8 string (not UTF-16)\n * - Only supports double quote (`\"`) as quotation character\n * - Only supports single character as delimiter\n *\n * This function only supports UTF-8 string.\n * If you pass a string that is not UTF-8, like UTF-16, it throws an error.\n * This function only supports double quote as quotation.\n * So, `options.quotation` must be `\"` (double quote). Otherwise, it throws an error.\n *\n * And this function only supports single character as delimiter.\n * So, `options.delimiter` must be a single character. Otherwise, it throws an error.\n *\n * @example\n *\n * ```ts\n * import { loadWASM, parseStringWASM } from \"web-csv-toolbox\";\n *\n * await loadWASM();\n *\n * const csv = \"a,b,c\\n1,2,3\";\n *\n * const result = parseStringToArraySyncWASM(csv);\n * console.log(result);\n * // Prints:\n * // [{ a: \"1\", b: \"2\", c: \"3\" }]\n * ```\n * @beta\n * @throws {RangeError | TypeError} - If provided options are invalid.\n */\nexport function parseStringToArraySyncWASM<\n const CSVSource extends string,\n const Delimiter extends string = DEFAULT_DELIMITER,\n const Quotation extends string = DEFAULT_QUOTATION,\n const Header extends ReadonlyArray<string> = PickCSVHeader<\n CSVSource,\n Delimiter,\n Quotation\n >,\n>(\n csv: CSVSource,\n options: CommonOptions<Delimiter, Quotation>,\n): CSVRecord<Header>[];\nexport function parseStringToArraySyncWASM<\n const CSVSource extends string,\n const Delimiter extends string = DEFAULT_DELIMITER,\n const Quotation extends string = DEFAULT_QUOTATION,\n const Header extends ReadonlyArray<string> = PickCSVHeader<CSVSource>,\n>(\n csv: CSVSource,\n options?: CommonOptions<Delimiter, Quotation>,\n): CSVRecord<Header>[];\nexport function parseStringToArraySyncWASM<\n const Header extends ReadonlyArray<string>,\n const Delimiter extends string = DEFAULT_DELIMITER,\n const Quotation extends string = DEFAULT_QUOTATION,\n>(\n csv: string,\n options?: CommonOptions<Delimiter, Quotation>,\n): CSVRecord<Header>[];\nexport function parseStringToArraySyncWASM<\n const Header extends readonly string[],\n const Delimiter extends string = DEFAULT_DELIMITER,\n const Quotation extends string = DEFAULT_QUOTATION,\n>(\n csv: string,\n options: CommonOptions<Delimiter, Quotation> = {},\n): CSVRecord<Header>[] {\n const { delimiter = DEFAULT_DELIMITER, quotation = DEFAULT_QUOTATION } =\n options;\n if (typeof delimiter !== \"string\" || delimiter.length !== 1) {\n throw new RangeError(\n \"Invalid delimiter, must be a single character on WASM.\",\n );\n }\n if (quotation !== DOUBLE_QUOTE) {\n throw new RangeError(\"Invalid quotation, must be double quote on WASM.\");\n }\n assertCommonOptions({ delimiter, quotation });\n const demiliterCode = delimiter.charCodeAt(0);\n return JSON.parse(parseStringToArraySync(csv, demiliterCode));\n}\n"],"names":[],"mappings":";;;;AAwFO,SAAS,0BAKd,CAAA,GAAA,EACA,OAA+C,GAAA,EAC1B,EAAA;AACrB,EAAA,MAAM,EAAE,SAAA,GAAY,iBAAmB,EAAA,SAAA,GAAY,mBACjD,GAAA,OAAA;AACF,EAAA,IAAI,OAAO,SAAA,KAAc,QAAY,IAAA,SAAA,CAAU,WAAW,CAAG,EAAA;AAC3D,IAAA,MAAM,IAAI,UAAA;AAAA,MACR;AAAA,KACF;AAAA;AAEF,EAAA,IAAI,cAAc,YAAc,EAAA;AAC9B,IAAM,MAAA,IAAI,WAAW,kDAAkD,CAAA;AAAA;AAEzE,EAAoB,mBAAA,CAAA,EAAE,SAAW,EAAA,SAAA,EAAW,CAAA;AAC5C,EAAM,MAAA,aAAA,GAAgB,SAAU,CAAA,UAAA,CAAW,CAAC,CAAA;AAC5C,EAAA,OAAO,IAAK,CAAA,KAAA,CAAM,sBAAuB,CAAA,GAAA,EAAK,aAAa,CAAC,CAAA;AAC9D;;;;"}
1
+ {"version":3,"file":"parseStringToArraySyncWASM.js","sources":["../src/parseStringToArraySyncWASM.ts"],"sourcesContent":["import { parseStringToArraySync } from \"web-csv-toolbox-wasm\";\nimport { assertCommonOptions } from \"./assertCommonOptions.ts\";\nimport type { CSVRecord, CommonOptions } from \"./common/types.ts\";\nimport {\n DEFAULT_DELIMITER,\n DEFAULT_QUOTATION,\n DOUBLE_QUOTE,\n} from \"./constants.ts\";\nimport type { loadWASM } from \"./loadWASM.ts\";\nimport type { PickCSVHeader } from \"./utils/types.ts\";\n\n/**\n * Parse CSV string to record of arrays.\n *\n * @param csv CSV string\n * @param options Parse options\n * @returns Record of arrays\n *\n * @remarks\n * This function uses WebAssembly to parse CSV string.\n * Before calling this function, you must call {@link loadWASM} function.\n *\n * **Performance Characteristics:**\n * - **Speed**: 2-3x faster than JavaScript parser for large CSV strings\n * - **Memory usage**: O(n) - proportional to file size (loads entire result into memory)\n * - **Suitable for**: CPU-intensive workloads, large CSV strings on server-side\n * - **Recommended max**: ~100MB (Node.js/Deno)\n *\n * **Limitations:**\n * - Only supports UTF-8 string (not UTF-16)\n * - Only supports double quote (`\"`) as quotation character\n * - Only supports single character as delimiter\n *\n * This function only supports UTF-8 string.\n * If you pass a string that is not UTF-8, like UTF-16, it throws an error.\n * This function only supports double quote as quotation.\n * So, `options.quotation` must be `\"` (double quote). Otherwise, it throws an error.\n *\n * And this function only supports single character as delimiter.\n * So, `options.delimiter` must be a single character. Otherwise, it throws an error.\n *\n * @example\n *\n * ```ts\n * import { loadWASM, parseStringWASM } from \"web-csv-toolbox\";\n *\n * await loadWASM();\n *\n * const csv = \"a,b,c\\n1,2,3\";\n *\n * const result = parseStringToArraySyncWASM(csv);\n * console.log(result);\n * // Prints:\n * // [{ a: \"1\", b: \"2\", c: \"3\" }]\n * ```\n * @beta\n * @throws {RangeError | TypeError} - If provided options are invalid.\n */\nexport function parseStringToArraySyncWASM<\n const CSVSource extends string,\n const Delimiter extends string = DEFAULT_DELIMITER,\n const Quotation extends string = DEFAULT_QUOTATION,\n const Header extends ReadonlyArray<string> = PickCSVHeader<\n CSVSource,\n Delimiter,\n Quotation\n >,\n>(\n csv: CSVSource,\n options: CommonOptions<Delimiter, Quotation>,\n): CSVRecord<Header>[];\nexport function parseStringToArraySyncWASM<\n const CSVSource extends string,\n const Delimiter extends string = DEFAULT_DELIMITER,\n const Quotation extends string = DEFAULT_QUOTATION,\n const Header extends ReadonlyArray<string> = PickCSVHeader<CSVSource>,\n>(\n csv: CSVSource,\n options?: CommonOptions<Delimiter, Quotation>,\n): CSVRecord<Header>[];\nexport function parseStringToArraySyncWASM<\n const Header extends ReadonlyArray<string>,\n const Delimiter extends string = DEFAULT_DELIMITER,\n const Quotation extends string = DEFAULT_QUOTATION,\n>(\n csv: string,\n options?: CommonOptions<Delimiter, Quotation>,\n): CSVRecord<Header>[];\nexport function parseStringToArraySyncWASM<\n const Header extends readonly string[],\n const Delimiter extends string = DEFAULT_DELIMITER,\n const Quotation extends string = DEFAULT_QUOTATION,\n>(\n csv: string,\n options: CommonOptions<Delimiter, Quotation> = {},\n): CSVRecord<Header>[] {\n const {\n delimiter = DEFAULT_DELIMITER,\n quotation = DEFAULT_QUOTATION,\n maxBufferSize = 10485760,\n } = options;\n if (typeof delimiter !== \"string\" || delimiter.length !== 1) {\n throw new RangeError(\n \"Invalid delimiter, must be a single character on WASM.\",\n );\n }\n if (quotation !== DOUBLE_QUOTE) {\n throw new RangeError(\"Invalid quotation, must be double quote on WASM.\");\n }\n assertCommonOptions({ delimiter, quotation, maxBufferSize });\n const demiliterCode = delimiter.charCodeAt(0);\n return JSON.parse(parseStringToArraySync(csv, demiliterCode));\n}\n"],"names":[],"mappings":";;;;AAwFO,SAAS,0BAKd,CAAA,GAAA,EACA,OAA+C,GAAA,EAC1B,EAAA;AACrB,EAAM,MAAA;AAAA,IACJ,SAAY,GAAA,iBAAA;AAAA,IACZ,SAAY,GAAA,iBAAA;AAAA,IACZ,aAAgB,GAAA;AAAA,GACd,GAAA,OAAA;AACJ,EAAA,IAAI,OAAO,SAAA,KAAc,QAAY,IAAA,SAAA,CAAU,WAAW,CAAG,EAAA;AAC3D,IAAA,MAAM,IAAI,UAAA;AAAA,MACR;AAAA,KACF;AAAA;AAEF,EAAA,IAAI,cAAc,YAAc,EAAA;AAC9B,IAAM,MAAA,IAAI,WAAW,kDAAkD,CAAA;AAAA;AAEzE,EAAA,mBAAA,CAAoB,EAAE,SAAA,EAAW,SAAW,EAAA,aAAA,EAAe,CAAA;AAC3D,EAAM,MAAA,aAAA,GAAgB,SAAU,CAAA,UAAA,CAAW,CAAC,CAAA;AAC5C,EAAA,OAAO,IAAK,CAAA,KAAA,CAAM,sBAAuB,CAAA,GAAA,EAAK,aAAa,CAAC,CAAA;AAC9D;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-csv-toolbox",
3
- "version": "0.12.1-next-53113e3c3ee5a6ec969f90ac8d8c87cc929fe86b",
3
+ "version": "0.13.0-next-c1addc26c62ad2577b7b9eaaa3a44fde79d5e05c",
4
4
  "description": "A CSV Toolbox utilizing Web Standard APIs.",
5
5
  "type": "module",
6
6
  "module": "dist/web-csv-toolbox.js",