web-csv-toolbox 0.10.3-next-10321180d04c1d67f731d55f6c6190691c0d7ba6 → 0.11.0-next-15ec1363a1f8064b33306871989088ab40f44a94

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
@@ -49,6 +49,9 @@ A CSV Toolbox utilizing Web Standard APIs.
49
49
 
50
50
  - 🌊 **Efficient CSV Parsing with Streams**
51
51
  - 💻 Leveraging the [WHATWG Streams API](https://streams.spec.whatwg.org/) and other Web APIs for seamless and efficient data processing.
52
+ - 🛑 **AbortSignal and Timeout Support**: Ensure your CSV processing is cancellable, including support for automatic timeouts.
53
+ - ✋ Integrate with [`AbortController`](https://developer.mozilla.org/docs/Web/API/AbortController) to manually cancel operations as needed.
54
+ - ⏳ Use [`AbortSignal.timeout`](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static) to automatically cancel operations that exceed a specified time limit.
52
55
  - 🎨 **Flexible Source Support**
53
56
  - 🧩 Parse CSVs directly from `string`s, `ReadableStream`s, or `Response` objects.
54
57
  - ⚙️ **Advanced Parsing Options**: Customize your experience with various delimiters and quotation marks.
@@ -57,10 +60,10 @@ A CSV Toolbox utilizing Web Standard APIs.
57
60
  - 🔄 Flexible BOM handling.
58
61
  - 🗜️ Supports various compression formats.
59
62
  - 🔤 Charset specification for diverse encoding.
60
- - 📦 **Lightweight and Zero Dependencies**: No external dependencies, only Web Standards APIs.
61
- - 📚 **Fully Typed and Documented**: Fully typed and documented with [TypeDoc](https://typedoc.org/).
62
63
  - 🚀 **Using WebAssembly for High Performance**: WebAssembly is used for high performance parsing. (_Experimental_)
63
64
  - 📦 WebAssembly is used for high performance parsing.
65
+ - 📦 **Lightweight and Zero Dependencies**: No external dependencies, only Web Standards APIs.
66
+ - 📚 **Fully Typed and Documented**: Fully typed and documented with [TypeDoc](https://typedoc.org/).
64
67
 
65
68
  ## Installation 📥
66
69
 
@@ -212,6 +215,67 @@ for await (const record of parse(csv, { headers: ['name', 'age'] })) {
212
215
  // { name: 'Bob', age: '69' }
213
216
  ```
214
217
 
218
+ ### `AbortSignal` / `AbortController` Support
219
+
220
+ Support for [`AbortSignal`](https://developer.mozilla.org/docs/Web/API/AbortSignal) / [`AbortController`](https://developer.mozilla.org/docs/Web/API/AbortController), enabling you to cancel ongoing asynchronous CSV processing tasks.
221
+
222
+ This feature is useful for scenarios where processing needs to be halted, such as when a user navigates away from the page or other conditions that require stopping the task early.
223
+
224
+ #### Example Use Case: Abort with user action
225
+
226
+ ```js
227
+ import { parse } from 'web-csv-toolbox';
228
+
229
+ const controller = new AbortController();
230
+ const csv = "name,age\nAlice,30\nBob,25";
231
+
232
+ try {
233
+ // Parse the CSV data then pass the AbortSignal to the parse function
234
+ for await (const record of parse(csv, { signal: controller.signal })) {
235
+ console.log(record);
236
+ }
237
+ } catch (error) {
238
+ if (error instanceof DOMException && error.name === 'AbortError') {
239
+ // The CSV processing was aborted by the user
240
+ console.log('CSV processing was aborted by the user.');
241
+ } else {
242
+ // An error occurred during CSV processing
243
+ console.error('An error occurred:', error);
244
+ }
245
+ }
246
+
247
+ // Some abort logic, like a cancel button
248
+ document.getElementById('cancel-button')
249
+ .addEventListener('click', () => {
250
+ controller.abort();
251
+ });
252
+ ```
253
+
254
+ #### Example Use Case: Abort with timeout
255
+
256
+ ```js
257
+ import { parse } from 'web-csv-toolbox';
258
+
259
+ // Set up a timeout of 5 seconds (5000 milliseconds)
260
+ const signal = AbortSignal.timeout(5000);
261
+
262
+ const csv = "name,age\nAlice,30\nBob,25";
263
+
264
+ try {
265
+ // Pass the AbortSignal to the parse function
266
+ const result = await parse.toArray(csv, { signal });
267
+ console.log(result);
268
+ } catch (error) {
269
+ if (error instanceof DOMException && error.name === 'TimeoutError') {
270
+ // Handle the case where the processing was aborted due to timeout
271
+ console.log('CSV processing was aborted due to timeout.');
272
+ } else {
273
+ // Handle other errors
274
+ console.error('An error occurred during CSV processing:', error);
275
+ }
276
+ }
277
+ ```
278
+
215
279
  ## Supported Runtimes 💻
216
280
 
217
281
  ### Works on Node.js
@@ -320,11 +384,12 @@ console.log(result);
320
384
 
321
385
  ### Common Options ⚙️
322
386
 
323
- | Option | Description | Default | Notes |
324
- | ----------- | ------------------------------------- | --------- | ------------------------------------------------- |
325
- | `delimiter` | Character to separate fields | `,` | |
326
- | `quotation` | Character used for quoting fields | `"` | |
327
- | `headers` | Custom headers for the parsed records | First row | If not provided, the first row is used as headers |
387
+ | Option | Description | Default | Notes |
388
+ | ----------- | ------------------------------------- | ----------- | ------------------------------------------------- |
389
+ | `delimiter` | Character to separate fields | `,` | |
390
+ | `quotation` | Character used for quoting fields | `"` | |
391
+ | `headers` | Custom headers for the parsed records | First row | If not provided, the first row is used as headers |
392
+ | `signal` | AbortSignal to cancel processing | `undefined` | Allows aborting of long-running operations |
328
393
 
329
394
  ### Advanced Options (Binary-Specific) 🧬
330
395
 
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./assertCommonOptions.cjs"),e=require("./common/constants.cjs"),r=require("./common/errors.cjs"),s=require("./constants.cjs"),i=require("./utils/escapeRegExp.cjs");exports.Lexer=class{#t;#e;#r="";#s=!1;#i;#u;#o={line:1,column:1,offset:0};#h=1;constructor({delimiter:e=s.COMMA,quotation:r=s.DOUBLE_QUOTE}={}){t.assertCommonOptions({delimiter:e,quotation:r}),this.#t=e,this.#e=r,this.#u=e.length;const u=i.escapeRegExp(e),o=i.escapeRegExp(r);this.#i=new RegExp(`^(?:(?!${o})(?!${u})(?![\\r\\n]))([\\S\\s\\uFEFF\\xA0]+?)(?=${o}|${u}|\\r|\\n|$)`)}lex(t,e=!1){return e||(this.#s=!0),"string"==typeof t&&0!==t.length&&(this.#r+=t),this.#f()}flush(){return this.#s=!0,[...this.#f()]}*#f(){let t;for(this.#s&&(this.#r.endsWith(s.CRLF)?this.#r=this.#r.slice(0,-2):this.#r.endsWith(s.LF)&&(this.#r=this.#r.slice(0,-1)));t=this.#n();)yield t}#n(){if(0===this.#r.length)return null;if(!1===this.#s&&(this.#r===s.CRLF||this.#r===s.LF))return null;if(this.#r.startsWith(s.CRLF)){this.#r=this.#r.slice(2);const t={...this.#o};this.#o.line++,this.#o.column=1,this.#o.offset+=2;return{type:e.RecordDelimiter,value:s.CRLF,location:{start:t,end:{...this.#o},rowNumber:this.#h++}}}if(this.#r.startsWith(s.LF)){this.#r=this.#r.slice(1);const t={...this.#o};this.#o.line++,this.#o.column=1,this.#o.offset+=1;return{type:e.RecordDelimiter,value:s.LF,location:{start:t,end:{...this.#o},rowNumber:this.#h++}}}if(this.#r.startsWith(this.#t)){this.#r=this.#r.slice(1);const t={...this.#o};return this.#o.column+=this.#u,this.#o.offset+=this.#u,{type:e.FieldDelimiter,value:this.#t,location:{start:t,end:{...this.#o},rowNumber:this.#h}}}if(this.#r.startsWith(this.#e)){let t="",i=1,u=2,o=0,h=this.#r[i],f=this.#r[i+1];do{if(h===this.#e){if(f===this.#e){t+=this.#e,i+=2,h=this.#r[i],f=this.#r[i+1],u+=2;continue}if(void 0===f&&!1===this.#s)return null;i++,this.#r=this.#r.slice(i);const r={...this.#o};return this.#o.column+=u,this.#o.offset+=i,this.#o.line+=o,{type:e.Field,value:t,location:{start:r,end:{...this.#o},rowNumber:this.#h}}}t+=h,h===s.LF?(o++,u=1):u++,i++,h=f,f=this.#r[i+1]}while(void 0!==h);if(this.#s)throw new r.ParseError("Unexpected EOF while parsing quoted field.",{position:{...this.#o}});return null}const t=this.#i.exec(this.#r);if(t){if(!1===this.#s&&t[0].length===this.#r.length)return null;const r=t[1];this.#r=this.#r.slice(r.length);const s={...this.#o};return this.#o.column+=r.length,this.#o.offset+=r.length,{type:e.Field,value:r,location:{start:s,end:{...this.#o},rowNumber:this.#h}}}return null}};
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("./assertCommonOptions.cjs"),e=require("./common/constants.cjs"),s=require("./common/errors.cjs"),r=require("./constants.cjs"),i=require("./utils/escapeRegExp.cjs");exports.Lexer=class{#t;#e;#s="";#r=!1;#i;#u;#o={line:1,column:1,offset:0};#h=1;#f;constructor({delimiter:e=r.COMMA,quotation:s=r.DOUBLE_QUOTE,signal:u}={}){t.assertCommonOptions({delimiter:e,quotation:s}),this.#t=e,this.#e=s,this.#u=e.length;const o=i.escapeRegExp(e),h=i.escapeRegExp(s);this.#i=new RegExp(`^(?:(?!${h})(?!${o})(?![\\r\\n]))([\\S\\s\\uFEFF\\xA0]+?)(?=${h}|${o}|\\r|\\n|$)`),u&&(this.#f=u)}lex(t,e=!1){return e||(this.#r=!0),"string"==typeof t&&0!==t.length&&(this.#s+=t),this.#n()}flush(){return this.#r=!0,[...this.#n()]}*#n(){let t;for(this.#r&&(this.#s.endsWith(r.CRLF)?this.#s=this.#s.slice(0,-2):this.#s.endsWith(r.LF)&&(this.#s=this.#s.slice(0,-1)));t=this.#l();)yield t}#l(){if(this.#f?.throwIfAborted(),0===this.#s.length)return null;if(!1===this.#r&&(this.#s===r.CRLF||this.#s===r.LF))return null;if(this.#s.startsWith(r.CRLF)){this.#s=this.#s.slice(2);const t={...this.#o};this.#o.line++,this.#o.column=1,this.#o.offset+=2;return{type:e.RecordDelimiter,value:r.CRLF,location:{start:t,end:{...this.#o},rowNumber:this.#h++}}}if(this.#s.startsWith(r.LF)){this.#s=this.#s.slice(1);const t={...this.#o};this.#o.line++,this.#o.column=1,this.#o.offset+=1;return{type:e.RecordDelimiter,value:r.LF,location:{start:t,end:{...this.#o},rowNumber:this.#h++}}}if(this.#s.startsWith(this.#t)){this.#s=this.#s.slice(1);const t={...this.#o};return this.#o.column+=this.#u,this.#o.offset+=this.#u,{type:e.FieldDelimiter,value:this.#t,location:{start:t,end:{...this.#o},rowNumber:this.#h}}}if(this.#s.startsWith(this.#e)){let t="",i=1,u=2,o=0,h=this.#s[i],f=this.#s[i+1];do{if(h===this.#e){if(f===this.#e){t+=this.#e,i+=2,h=this.#s[i],f=this.#s[i+1],u+=2;continue}if(void 0===f&&!1===this.#r)return null;i++,this.#s=this.#s.slice(i);const s={...this.#o};return this.#o.column+=u,this.#o.offset+=i,this.#o.line+=o,{type:e.Field,value:t,location:{start:s,end:{...this.#o},rowNumber:this.#h}}}t+=h,h===r.LF?(o++,u=1):u++,i++,h=f,f=this.#s[i+1]}while(void 0!==h);if(this.#r)throw new s.ParseError("Unexpected EOF while parsing quoted field.",{position:{...this.#o}});return null}const t=this.#i.exec(this.#s);if(t){if(!1===this.#r&&t[0].length===this.#s.length)return null;const s=t[1];this.#s=this.#s.slice(s.length);const r={...this.#o};return this.#o.column+=s.length,this.#o.offset+=s.length,{type:e.Field,value:s,location:{start:r,end:{...this.#o},rowNumber:this.#h}}}return null}};
2
2
  //# sourceMappingURL=Lexer.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Lexer.cjs","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 CommonOptions,\n Position,\n RecordDelimiterToken,\n Token,\n} from \"./common/types.ts\";\nimport { COMMA, CRLF, DOUBLE_QUOTE, LF } from \"./constants.ts\";\nimport { escapeRegExp } from \"./utils/escapeRegExp.ts\";\n\n/**\n * CSV Lexer.\n *\n * Lexter tokenizes CSV data into fields and records.\n */\nexport class Lexer {\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 /**\n * Constructs a new Lexer instance.\n * @param options - The common options for the lexer.\n */\n constructor({\n delimiter = COMMA,\n quotation = DOUBLE_QUOTE,\n }: CommonOptions = {}) {\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 }\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 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":["delimiter","quotation","buffer","flush","matcher","fieldDelimiterLength","cursor","line","column","offset","rowNumber","constructor","COMMA","DOUBLE_QUOTE","assertCommonOptions","this","length","d","escapeRegExp","q","RegExp","lex","chunk","buffering","tokens","token","endsWith","CRLF","slice","LF","nextToken","startsWith","start","type","RecordDelimiter","value","location","end","FieldDelimiter","cur","next","Field","ParseError","position","match","exec"],"mappings":"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"}
1
+ {"version":3,"file":"Lexer.cjs","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 { COMMA, CRLF, DOUBLE_QUOTE, LF } from \"./constants.ts\";\nimport { escapeRegExp } from \"./utils/escapeRegExp.ts\";\n\n/**\n * CSV Lexer.\n *\n * Lexter tokenizes CSV data into fields and records.\n */\nexport class Lexer {\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 delimiter = COMMA,\n quotation = DOUBLE_QUOTE,\n signal,\n }: CommonOptions & AbortSignalOptions = {}) {\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":["delimiter","quotation","buffer","flush","matcher","fieldDelimiterLength","cursor","line","column","offset","rowNumber","signal","constructor","COMMA","DOUBLE_QUOTE","assertCommonOptions","this","length","d","escapeRegExp","q","RegExp","lex","chunk","buffering","tokens","token","endsWith","CRLF","slice","LF","nextToken","throwIfAborted","startsWith","start","type","RecordDelimiter","value","location","end","FieldDelimiter","cur","next","Field","ParseError","position","match","exec"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./common/constants.cjs"),r=require("./common/errors.cjs");exports.RecordAssembler=class{#e=0;#r=[];#t;#i=!1;constructor(e={}){void 0!==e.header&&Array.isArray(e.header)&&this.#s(e.header)}*assemble(r,t=!0){for(const i of r)switch(i.type){case e.FieldDelimiter:this.#e++,this.#i=!0;break;case e.RecordDelimiter:void 0===this.#t?this.#s(this.#r):this.#i?yield Object.fromEntries(this.#t.map(((e,r)=>[e,this.#r.at(r)]))):yield Object.fromEntries(this.#t.map((e=>[e,""]))),this.#e=0,this.#r=new Array(this.#t?.length).fill(""),this.#i=!1;break;default:this.#i=!0,this.#r[this.#e]=i.value}t&&(yield*this.flush())}*flush(){void 0!==this.#t&&this.#i&&(yield Object.fromEntries(this.#t.filter((e=>e)).map(((e,r)=>[e,this.#r.at(r)]))))}#s(e){if(this.#t=e,0===this.#t.length)throw new r.ParseError("The header must not be empty.");if(new Set(this.#t).size!==this.#t.length)throw new r.ParseError("The header must not contain duplicate fields.")}};
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./common/constants.cjs"),t=require("./common/errors.cjs");exports.RecordAssembler=class{#e=0;#t=[];#r;#i=!1;#s;constructor(e={}){void 0!==e.header&&Array.isArray(e.header)&&this.#h(e.header),e.signal&&(this.#s=e.signal)}*assemble(t,r=!0){for(const i of t)switch(this.#s?.throwIfAborted(),i.type){case e.FieldDelimiter:this.#e++,this.#i=!0;break;case e.RecordDelimiter:void 0===this.#r?this.#h(this.#t):this.#i?yield Object.fromEntries(this.#r.map(((e,t)=>[e,this.#t.at(t)]))):yield Object.fromEntries(this.#r.map((e=>[e,""]))),this.#e=0,this.#t=new Array(this.#r?.length).fill(""),this.#i=!1;break;default:this.#i=!0,this.#t[this.#e]=i.value}r&&(yield*this.flush())}*flush(){void 0!==this.#r&&this.#i&&(yield Object.fromEntries(this.#r.filter((e=>e)).map(((e,t)=>[e,this.#t.at(t)]))))}#h(e){if(this.#r=e,0===this.#r.length)throw new t.ParseError("The header must not be empty.");if(new Set(this.#r).size!==this.#r.length)throw new t.ParseError("The header must not contain duplicate fields.")}};
2
2
  //# sourceMappingURL=RecordAssembler.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"RecordAssembler.cjs","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\n constructor(options: RecordAssemblerOptions<Header> = {}) {\n if (options.header !== undefined && Array.isArray(options.header)) {\n this.#setHeader(options.header);\n }\n }\n\n public *assemble(\n tokens: Iterable<Token>,\n flush = true,\n ): IterableIterator<CSVRecord<Header>> {\n for (const token of tokens) {\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":["fieldIndex","row","header","dirty","constructor","options","Array","isArray","this","setHeader","assemble","tokens","flush","token","type","FieldDelimiter","RecordDelimiter","Object","fromEntries","map","index","at","length","fill","value","filter","v","ParseError","Set","size"],"mappings":"mLAQO,MACLA,GAAc,EACdC,GAAiB,GACjBC,GACAC,IAAS,EAET,WAAAC,CAAYC,EAA0C,SAC7B,IAAnBA,EAAQH,QAAwBI,MAAMC,QAAQF,EAAQH,SACnDM,MAAAC,EAAWJ,EAAQH,OAE5B,CAEA,SAAQQ,CACNC,EACAC,GAAQ,GAER,IAAA,MAAWC,KAASF,EAClB,OAAQE,EAAMC,MACZ,KAAKC,EAAAA,eACEP,MAAAR,IACLQ,MAAKL,GAAS,EACd,MACF,KAAKa,EAAAA,qBACkB,IAAjBR,MAAKN,EACFM,MAAAC,EAAWD,MAAKP,GAEjBO,MAAKL,QACDc,OAAOC,YACXV,MAAKN,EAAQiB,KAAI,CAACjB,EAAQkB,IAAU,CAClClB,EACAM,MAAKP,EAAKoB,GAAGD,aAIXH,OAAOC,YACXV,MAAKN,EAAQiB,KAAKjB,GAAW,CAACA,EAAQ,OAK5CM,MAAKR,EAAc,EACdQ,MAAAP,EAAO,IAAIK,MAAME,MAAKN,GAASoB,QAAQC,KAAK,IACjDf,MAAKL,GAAS,EACd,MACF,QACEK,MAAKL,GAAS,EACdK,MAAKP,EAAKO,MAAKR,GAAea,EAAMW,MAKtCZ,UACKJ,KAAKI,QAEhB,CAEA,MAAQA,QACe,IAAjBJ,MAAKN,GACHM,MAAKL,UACDc,OAAOC,YACXV,MAAKN,EACFuB,QAAQC,GAAMA,IACdP,KAAI,CAACjB,EAAQkB,IAAU,CAAClB,EAAQM,MAAKP,EAAKoB,GAAGD,OAIxD,CAEA,EAAAX,CAAWP,GAEL,GADJM,MAAKN,EAAUA,EACa,IAAxBM,MAAKN,EAAQoB,OACT,MAAA,IAAIK,EAAAA,WAAW,iCAEnB,GAAA,IAAIC,IAAIpB,MAAKN,GAAS2B,OAASrB,MAAKN,EAAQoB,OACxC,MAAA,IAAIK,EAAAA,WAAW,gDAEzB"}
1
+ {"version":3,"file":"RecordAssembler.cjs","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":["fieldIndex","row","header","dirty","signal","constructor","options","Array","isArray","this","setHeader","assemble","tokens","flush","token","throwIfAborted","type","FieldDelimiter","RecordDelimiter","Object","fromEntries","map","index","at","length","fill","value","filter","v","ParseError","Set","size"],"mappings":"mLAQO,MACLA,GAAc,EACdC,GAAiB,GACjBC,GACAC,IAAS,EACTC,GAEA,WAAAC,CAAYC,EAA0C,SAC7B,IAAnBA,EAAQJ,QAAwBK,MAAMC,QAAQF,EAAQJ,SACnDO,MAAAC,EAAWJ,EAAQJ,QAEtBI,EAAQF,SACVK,MAAKL,EAAUE,EAAQF,OAE3B,CAEA,SAAQO,CACNC,EACAC,GAAQ,GAER,IAAA,MAAWC,KAASF,EAElB,OADAH,MAAKL,GAASW,iBACND,EAAME,MACZ,KAAKC,EAAAA,eACER,MAAAT,IACLS,MAAKN,GAAS,EACd,MACF,KAAKe,EAAAA,qBACkB,IAAjBT,MAAKP,EACFO,MAAAC,EAAWD,MAAKR,GAEjBQ,MAAKN,QACDgB,OAAOC,YACXX,MAAKP,EAAQmB,KAAI,CAACnB,EAAQoB,IAAU,CAClCpB,EACAO,MAAKR,EAAKsB,GAAGD,aAIXH,OAAOC,YACXX,MAAKP,EAAQmB,KAAKnB,GAAW,CAACA,EAAQ,OAK5CO,MAAKT,EAAc,EACdS,MAAAR,EAAO,IAAIM,MAAME,MAAKP,GAASsB,QAAQC,KAAK,IACjDhB,MAAKN,GAAS,EACd,MACF,QACEM,MAAKN,GAAS,EACdM,MAAKR,EAAKQ,MAAKT,GAAec,EAAMY,MAKtCb,UACKJ,KAAKI,QAEhB,CAEA,MAAQA,QACe,IAAjBJ,MAAKP,GACHO,MAAKN,UACDgB,OAAOC,YACXX,MAAKP,EACFyB,QAAQC,GAAMA,IACdP,KAAI,CAACnB,EAAQoB,IAAU,CAACpB,EAAQO,MAAKR,EAAKsB,GAAGD,OAIxD,CAEA,EAAAZ,CAAWR,GAEL,GADJO,MAAKP,EAAUA,EACa,IAAxBO,MAAKP,EAAQsB,OACT,MAAA,IAAIK,EAAAA,WAAW,iCAEnB,GAAA,IAAIC,IAAIrB,MAAKP,GAAS6B,OAAStB,MAAKP,EAAQsB,OACxC,MAAA,IAAIK,EAAAA,WAAW,gDAEzB"}