convert-buddy-js 0.9.2 → 0.9.5

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.
Files changed (31) hide show
  1. package/README.md +142 -5
  2. package/dist/src/browser.d.ts +74 -0
  3. package/dist/src/browser.js +140 -0
  4. package/dist/src/browser.js.map +1 -0
  5. package/dist/src/index.d.ts +23 -2
  6. package/dist/src/index.js +82 -8
  7. package/dist/src/index.js.map +1 -1
  8. package/dist/src/node.d.ts +83 -2
  9. package/dist/src/node.js +270 -2
  10. package/dist/src/node.js.map +1 -1
  11. package/dist/tests/edge-cases/control-features.test.d.ts +2 -0
  12. package/dist/tests/edge-cases/control-features.test.js +87 -0
  13. package/dist/tests/edge-cases/control-features.test.js.map +1 -0
  14. package/dist/tests/edge-cases/csv-edge-cases.test.d.ts +2 -0
  15. package/dist/tests/edge-cases/csv-edge-cases.test.js +547 -0
  16. package/dist/tests/edge-cases/csv-edge-cases.test.js.map +1 -0
  17. package/dist/tests/edge-cases/detection.test.d.ts +2 -0
  18. package/dist/tests/edge-cases/detection.test.js +129 -0
  19. package/dist/tests/edge-cases/detection.test.js.map +1 -0
  20. package/dist/tests/edge-cases/error-handling.test.d.ts +2 -0
  21. package/dist/tests/edge-cases/error-handling.test.js +448 -0
  22. package/dist/tests/edge-cases/error-handling.test.js.map +1 -0
  23. package/dist/tests/edge-cases/ndjson-edge-cases.test.d.ts +2 -0
  24. package/dist/tests/edge-cases/ndjson-edge-cases.test.js +539 -0
  25. package/dist/tests/edge-cases/ndjson-edge-cases.test.js.map +1 -0
  26. package/dist/tests/edge-cases/node-helpers.test.d.ts +2 -0
  27. package/dist/tests/edge-cases/node-helpers.test.js +114 -0
  28. package/dist/tests/edge-cases/node-helpers.test.js.map +1 -0
  29. package/dist/wasm/nodejs/convert_buddy_bg.wasm +0 -0
  30. package/dist/wasm/web/convert_buddy_bg.wasm +0 -0
  31. package/package.json +6 -2
package/README.md CHANGED
@@ -2,15 +2,152 @@
2
2
 
3
3
  A high-performance, streaming-first parser and converter for CSV, XML, NDJSON, and JSON. `convert-buddy-js` is a TypeScript wrapper around a Rust/WASM core, offering fast parsing and multiple usage styles for Node.js and browsers.
4
4
 
5
+ ## Status & Quality
6
+
7
+ [![Known Vulnerabilities](https://snyk.io/test/github/brunohanss/convert-buddy/badge.svg)](https://snyk.io/test/github/brunohanss/convert-buddy)
8
+ [![CI/CD Pipeline](https://github.com/brunohanss/convert-buddy/actions/workflows/test.yml/badge.svg)](https://github.com/brunohanss/convert-buddy/actions)
9
+ [![Coverage Status](https://img.shields.io/codecov/c/github/brunohanss/convert-buddy?label=coverage)](https://codecov.io/gh/brunohanss/convert-buddy)
10
+ [![npm version](https://img.shields.io/npm/v/convert-buddy-js.svg)](https://www.npmjs.com/package/convert-buddy-js)
11
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/convert-buddy-js.svg)](https://bundlephobia.com/package/convert-buddy-js)
12
+
5
13
  ## Install
6
14
 
7
15
  ```bash
8
16
  npm install convert-buddy-js
9
17
  ```
10
18
 
19
+ ## Quick Start
20
+
21
+ ### Browser - Convert File Input
22
+
23
+ ```ts
24
+ import { convertFileToString } from "convert-buddy-js/browser";
25
+
26
+ const fileInput = document.querySelector('input[type="file"]');
27
+ fileInput.addEventListener('change', async (e) => {
28
+ const file = e.target.files[0];
29
+ const result = await convertFileToString(file, {
30
+ inputFormat: "auto",
31
+ outputFormat: "json"
32
+ });
33
+ console.log(result);
34
+ });
35
+ ```
36
+
37
+ ### Node.js - Convert File
38
+
39
+ ```ts
40
+ import { convertFileToString } from "convert-buddy-js/node";
41
+
42
+ const result = await convertFileToString("input.csv", {
43
+ inputFormat: "auto",
44
+ outputFormat: "json"
45
+ });
46
+ console.log(result);
47
+ ```
48
+
11
49
  ## Usage
12
50
 
13
- ### Convert a full string or buffer
51
+ ### High-Level API (Recommended)
52
+
53
+ #### Browser Helpers
54
+
55
+ Simple file conversion without manual buffer handling:
56
+
57
+ ```ts
58
+ import {
59
+ convertFileToString,
60
+ convertFile,
61
+ convertFileToFile,
62
+ convertFileStream
63
+ } from "convert-buddy-js/browser";
64
+
65
+ // Convert to string
66
+ const json = await convertFileToString(file, {
67
+ inputFormat: "csv",
68
+ outputFormat: "json"
69
+ });
70
+
71
+ // Convert and download
72
+ await convertFileToFile(file, "output.json", {
73
+ inputFormat: "csv",
74
+ outputFormat: "json"
75
+ });
76
+
77
+ // Get streaming API
78
+ const stream = await convertFileStream(file, {
79
+ inputFormat: "csv",
80
+ outputFormat: "ndjson"
81
+ });
82
+ ```
83
+
84
+ #### Node.js Helpers
85
+
86
+ Convenient file path and stream conversions:
87
+
88
+ ```ts
89
+ import {
90
+ convertFileToString,
91
+ convertFileToFile,
92
+ convertBuffer,
93
+ convertStream
94
+ } from "convert-buddy-js/node";
95
+
96
+ // File to string
97
+ const json = await convertFileToString("input.csv", {
98
+ inputFormat: "csv",
99
+ outputFormat: "json"
100
+ });
101
+
102
+ // File to file
103
+ await convertFileToFile("input.csv", "output.json", {
104
+ inputFormat: "csv",
105
+ outputFormat: "json"
106
+ });
107
+
108
+ // From stream (HTTP, stdin, etc.)
109
+ const result = await convertStream(inputStream, {
110
+ inputFormat: "auto",
111
+ outputFormat: "json"
112
+ });
113
+ ```
114
+
115
+ #### Progress Tracking & Control
116
+
117
+ Monitor long-running conversions and allow cancellation:
118
+
119
+ ```ts
120
+ const buddy = await ConvertBuddy.create({
121
+ inputFormat: "csv",
122
+ outputFormat: "json",
123
+ onProgress: (stats) => {
124
+ console.log(`${stats.recordsProcessed} records processed`);
125
+ console.log(`${stats.throughputMbPerSec.toFixed(2)} MB/s`);
126
+ },
127
+ progressIntervalBytes: 1024 * 1024 // Every 1MB
128
+ });
129
+
130
+ // User cancels
131
+ cancelButton.addEventListener('click', () => buddy.abort());
132
+ ```
133
+
134
+ #### Auto-Detection
135
+
136
+ Let the library detect format automatically:
137
+
138
+ ```ts
139
+ const result = await convertFileToString(file, {
140
+ inputFormat: "auto", // Auto-detect CSV, JSON, NDJSON, XML
141
+ outputFormat: "json",
142
+ csvConfig: { // Optional: still apply config
143
+ delimiter: ","
144
+ }
145
+ });
146
+ ```
147
+
148
+ ### Low-Level API
149
+
150
+ #### Convert a full string or buffer
14
151
 
15
152
  ```ts
16
153
  import { convertToString } from "convert-buddy-js";
@@ -25,7 +162,7 @@ const output = await convertToString(csv, {
25
162
  console.log(output);
26
163
  ```
27
164
 
28
- ### Manual streaming (chunked)
165
+ #### Manual streaming (chunked)
29
166
 
30
167
  ```ts
31
168
  import { ConvertBuddy } from "convert-buddy-js";
@@ -42,7 +179,7 @@ const finalOutput = buddy.finish();
42
179
  console.log(buddy.stats());
43
180
  ```
44
181
 
45
- ### Node.js Transform stream
182
+ #### Node.js Transform stream
46
183
 
47
184
  Use the Node-specific entrypoint so bundlers keep `node:stream` out of the browser bundle.
48
185
 
@@ -62,7 +199,7 @@ createReadStream("input.csv")
62
199
  .pipe(createWriteStream("output.ndjson"));
63
200
  ```
64
201
 
65
- ### Web Streams
202
+ #### Web Streams
66
203
 
67
204
  ```ts
68
205
  import { ConvertBuddyTransformStream } from "convert-buddy-js";
@@ -76,7 +213,7 @@ const response = await fetch("/data.csv");
76
213
  const outputStream = response.body?.pipeThrough(transform);
77
214
  ```
78
215
 
79
- ### Detect format and CSV fields/delimiter
216
+ #### Detect format and CSV fields/delimiter
80
217
 
81
218
  Use streaming inputs to keep detection fast on large files.
82
219
 
@@ -0,0 +1,74 @@
1
+ import { ConvertBuddyOptions } from './index.js';
2
+ export { ConvertBuddy, ConvertBuddyTransformStream, CsvConfig, CsvDetection, DetectInput, DetectOptions, Format, ProgressCallback, Stats, XmlConfig, XmlDetection, autoDetectConfig, convert, convertToString, detectCsvFieldsAndDelimiter, detectFormat, detectXmlElements } from './index.js';
3
+
4
+ type BrowserConvertOptions = ConvertBuddyOptions & {};
5
+ /**
6
+ * Convert a browser File or Blob object to a string.
7
+ * Handles streaming internally using FileReader and web streams.
8
+ *
9
+ * @example
10
+ * // From file input
11
+ * const fileInput = document.querySelector('input[type="file"]');
12
+ * const file = fileInput.files[0];
13
+ * const result = await convertFileToString(file, {
14
+ * inputFormat: "csv",
15
+ * outputFormat: "json"
16
+ * });
17
+ *
18
+ * @example
19
+ * // With auto-detection
20
+ * const result = await convertFileToString(file, {
21
+ * inputFormat: "auto",
22
+ * outputFormat: "ndjson"
23
+ * });
24
+ */
25
+ declare function convertFileToString(file: File | Blob, opts?: BrowserConvertOptions): Promise<string>;
26
+ /**
27
+ * Convert a browser File or Blob object to a Uint8Array.
28
+ * Handles streaming internally using FileReader and web streams.
29
+ *
30
+ * @example
31
+ * const file = fileInput.files[0];
32
+ * const result = await convertFile(file, {
33
+ * inputFormat: "csv",
34
+ * outputFormat: "json",
35
+ * onProgress: (stats) => {
36
+ * console.log(`Progress: ${stats.bytesIn} bytes processed`);
37
+ * }
38
+ * });
39
+ */
40
+ declare function convertFile(file: File | Blob, opts?: BrowserConvertOptions): Promise<Uint8Array>;
41
+ /**
42
+ * Convert a browser File or Blob and download the result as a file.
43
+ *
44
+ * @example
45
+ * const fileInput = document.querySelector('input[type="file"]');
46
+ * const file = fileInput.files[0];
47
+ * await convertFileToFile(file, "output.json", {
48
+ * inputFormat: "csv",
49
+ * outputFormat: "json"
50
+ * });
51
+ */
52
+ declare function convertFileToFile(inputFile: File | Blob, outputFilename: string, opts?: BrowserConvertOptions): Promise<void>;
53
+ /**
54
+ * Create a ReadableStream that converts data on-the-fly from a File or Blob.
55
+ * Useful for piping through other stream processors.
56
+ *
57
+ * @example
58
+ * const file = fileInput.files[0];
59
+ * const stream = convertFileStream(file, {
60
+ * inputFormat: "csv",
61
+ * outputFormat: "ndjson"
62
+ * });
63
+ *
64
+ * const reader = stream.getReader();
65
+ * while (true) {
66
+ * const { done, value } = await reader.read();
67
+ * if (done) break;
68
+ * // Process each chunk as it's converted
69
+ * console.log(new TextDecoder().decode(value));
70
+ * }
71
+ */
72
+ declare function convertFileStream(file: File | Blob, opts?: BrowserConvertOptions): Promise<ReadableStream<Uint8Array>>;
73
+
74
+ export { type BrowserConvertOptions, ConvertBuddyOptions, convertFile, convertFileStream, convertFileToFile, convertFileToString };
@@ -0,0 +1,140 @@
1
+ import { ConvertBuddy, autoDetectConfig } from "./index.js";
2
+ export * from "./index.js";
3
+ async function convertFileToString(file, opts = {}) {
4
+ const bytes = await convertFile(file, opts);
5
+ return new TextDecoder().decode(bytes);
6
+ }
7
+ async function convertFile(file, opts = {}) {
8
+ let actualOpts = { ...opts };
9
+ if (opts.inputFormat === "auto") {
10
+ const sampleSize = 256 * 1024;
11
+ const sampleBlob = file.slice(0, sampleSize);
12
+ const sampleBuffer = await sampleBlob.arrayBuffer();
13
+ const sample = new Uint8Array(sampleBuffer);
14
+ const detected = await autoDetectConfig(sample, { debug: opts.debug });
15
+ if (detected.format !== "unknown") {
16
+ actualOpts.inputFormat = detected.format;
17
+ if (detected.csvConfig) {
18
+ actualOpts.csvConfig = opts.csvConfig ? { ...detected.csvConfig, ...opts.csvConfig } : detected.csvConfig;
19
+ }
20
+ if (detected.xmlConfig) {
21
+ actualOpts.xmlConfig = opts.xmlConfig ? { ...detected.xmlConfig, ...opts.xmlConfig } : detected.xmlConfig;
22
+ }
23
+ if (opts.debug) {
24
+ console.log("[convert-buddy] Auto-detected format:", detected.format);
25
+ }
26
+ } else {
27
+ throw new Error("Could not auto-detect input format. Please specify inputFormat explicitly.");
28
+ }
29
+ }
30
+ const buddy = await ConvertBuddy.create(actualOpts);
31
+ const stream = file.stream();
32
+ const reader = stream.getReader();
33
+ const chunks = [];
34
+ try {
35
+ while (true) {
36
+ const { done, value } = await reader.read();
37
+ if (done) break;
38
+ if (buddy.isAborted()) {
39
+ throw new Error("Conversion aborted");
40
+ }
41
+ const output = buddy.push(value);
42
+ if (output.length > 0) {
43
+ chunks.push(output);
44
+ }
45
+ }
46
+ const final = buddy.finish();
47
+ if (final.length > 0) {
48
+ chunks.push(final);
49
+ }
50
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
51
+ const result = new Uint8Array(totalLength);
52
+ let offset = 0;
53
+ for (const chunk of chunks) {
54
+ result.set(chunk, offset);
55
+ offset += chunk.length;
56
+ }
57
+ if (opts.profile) {
58
+ console.log("[convert-buddy] Performance Stats:", buddy.stats());
59
+ }
60
+ return result;
61
+ } catch (error) {
62
+ reader.releaseLock();
63
+ throw error;
64
+ }
65
+ }
66
+ async function convertFileToFile(inputFile, outputFilename, opts = {}) {
67
+ const result = await convertFile(inputFile, opts);
68
+ const blob = new Blob([new Uint8Array(result)], { type: "application/octet-stream" });
69
+ const url = URL.createObjectURL(blob);
70
+ const link = document.createElement("a");
71
+ link.href = url;
72
+ link.download = outputFilename;
73
+ link.style.display = "none";
74
+ document.body.appendChild(link);
75
+ link.click();
76
+ setTimeout(() => {
77
+ document.body.removeChild(link);
78
+ URL.revokeObjectURL(url);
79
+ }, 100);
80
+ }
81
+ async function convertFileStream(file, opts = {}) {
82
+ let actualOpts = { ...opts };
83
+ if (opts.inputFormat === "auto") {
84
+ const sampleSize = 256 * 1024;
85
+ const sampleBlob = file.slice(0, sampleSize);
86
+ const sampleBuffer = await sampleBlob.arrayBuffer();
87
+ const sample = new Uint8Array(sampleBuffer);
88
+ const detected = await autoDetectConfig(sample, { debug: opts.debug });
89
+ if (detected.format !== "unknown") {
90
+ actualOpts.inputFormat = detected.format;
91
+ if (detected.csvConfig) {
92
+ actualOpts.csvConfig = opts.csvConfig ? { ...detected.csvConfig, ...opts.csvConfig } : detected.csvConfig;
93
+ }
94
+ if (detected.xmlConfig) {
95
+ actualOpts.xmlConfig = opts.xmlConfig ? { ...detected.xmlConfig, ...opts.xmlConfig } : detected.xmlConfig;
96
+ }
97
+ } else {
98
+ throw new Error("Could not auto-detect input format.");
99
+ }
100
+ }
101
+ const buddy = await ConvertBuddy.create(actualOpts);
102
+ const inputStream = file.stream();
103
+ return new ReadableStream({
104
+ async start(controller) {
105
+ const reader = inputStream.getReader();
106
+ try {
107
+ while (true) {
108
+ const { done, value } = await reader.read();
109
+ if (done) {
110
+ const final = buddy.finish();
111
+ if (final.length > 0) {
112
+ controller.enqueue(final);
113
+ }
114
+ controller.close();
115
+ break;
116
+ }
117
+ if (buddy.isAborted()) {
118
+ controller.error(new Error("Conversion aborted"));
119
+ break;
120
+ }
121
+ const output = buddy.push(value);
122
+ if (output.length > 0) {
123
+ controller.enqueue(output);
124
+ }
125
+ }
126
+ } catch (error) {
127
+ controller.error(error);
128
+ } finally {
129
+ reader.releaseLock();
130
+ }
131
+ }
132
+ });
133
+ }
134
+ export {
135
+ convertFile,
136
+ convertFileStream,
137
+ convertFileToFile,
138
+ convertFileToString
139
+ };
140
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/browser.ts"],"sourcesContent":["import { ConvertBuddy, type ConvertBuddyOptions, type Format, autoDetectConfig } from \"./index.js\";\r\n\r\nexport * from \"./index.js\";\r\n\r\nexport type BrowserConvertOptions = ConvertBuddyOptions & {\r\n // Additional browser-specific options can go here\r\n};\r\n\r\n/**\r\n * Convert a browser File or Blob object to a string.\r\n * Handles streaming internally using FileReader and web streams.\r\n * \r\n * @example\r\n * // From file input\r\n * const fileInput = document.querySelector('input[type=\"file\"]');\r\n * const file = fileInput.files[0];\r\n * const result = await convertFileToString(file, {\r\n * inputFormat: \"csv\",\r\n * outputFormat: \"json\"\r\n * });\r\n * \r\n * @example\r\n * // With auto-detection\r\n * const result = await convertFileToString(file, {\r\n * inputFormat: \"auto\",\r\n * outputFormat: \"ndjson\"\r\n * });\r\n */\r\nexport async function convertFileToString(\r\n file: File | Blob,\r\n opts: BrowserConvertOptions = {}\r\n): Promise<string> {\r\n const bytes = await convertFile(file, opts);\r\n return new TextDecoder().decode(bytes);\r\n}\r\n\r\n/**\r\n * Convert a browser File or Blob object to a Uint8Array.\r\n * Handles streaming internally using FileReader and web streams.\r\n * \r\n * @example\r\n * const file = fileInput.files[0];\r\n * const result = await convertFile(file, {\r\n * inputFormat: \"csv\",\r\n * outputFormat: \"json\",\r\n * onProgress: (stats) => {\r\n * console.log(`Progress: ${stats.bytesIn} bytes processed`);\r\n * }\r\n * });\r\n */\r\nexport async function convertFile(\r\n file: File | Blob,\r\n opts: BrowserConvertOptions = {}\r\n): Promise<Uint8Array> {\r\n // Handle auto-detection\r\n let actualOpts = { ...opts };\r\n \r\n if (opts.inputFormat === \"auto\") {\r\n // Read a sample for auto-detection\r\n const sampleSize = 256 * 1024; // 256KB\r\n const sampleBlob = file.slice(0, sampleSize);\r\n const sampleBuffer = await sampleBlob.arrayBuffer();\r\n const sample = new Uint8Array(sampleBuffer);\r\n \r\n const detected = await autoDetectConfig(sample, { debug: opts.debug });\r\n \r\n if (detected.format !== \"unknown\") {\r\n actualOpts.inputFormat = detected.format as Format;\r\n \r\n if (detected.csvConfig) {\r\n actualOpts.csvConfig = opts.csvConfig ? { ...detected.csvConfig, ...opts.csvConfig } : detected.csvConfig;\r\n }\r\n \r\n if (detected.xmlConfig) {\r\n actualOpts.xmlConfig = opts.xmlConfig ? { ...detected.xmlConfig, ...opts.xmlConfig } : detected.xmlConfig;\r\n }\r\n \r\n if (opts.debug) {\r\n console.log(\"[convert-buddy] Auto-detected format:\", detected.format);\r\n }\r\n } else {\r\n throw new Error(\"Could not auto-detect input format. Please specify inputFormat explicitly.\");\r\n }\r\n }\r\n \r\n const buddy = await ConvertBuddy.create(actualOpts);\r\n \r\n // Use streams API for efficient processing\r\n const stream = file.stream();\r\n const reader = stream.getReader();\r\n \r\n const chunks: Uint8Array[] = [];\r\n \r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n \r\n if (done) break;\r\n \r\n if (buddy.isAborted()) {\r\n throw new Error(\"Conversion aborted\");\r\n }\r\n \r\n const output = buddy.push(value);\r\n if (output.length > 0) {\r\n chunks.push(output);\r\n }\r\n }\r\n \r\n const final = buddy.finish();\r\n if (final.length > 0) {\r\n chunks.push(final);\r\n }\r\n \r\n // Combine all chunks\r\n const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\r\n const result = new Uint8Array(totalLength);\r\n let offset = 0;\r\n for (const chunk of chunks) {\r\n result.set(chunk, offset);\r\n offset += chunk.length;\r\n }\r\n \r\n if (opts.profile) {\r\n console.log(\"[convert-buddy] Performance Stats:\", buddy.stats());\r\n }\r\n \r\n return result;\r\n } catch (error) {\r\n reader.releaseLock();\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Convert a browser File or Blob and download the result as a file.\r\n * \r\n * @example\r\n * const fileInput = document.querySelector('input[type=\"file\"]');\r\n * const file = fileInput.files[0];\r\n * await convertFileToFile(file, \"output.json\", {\r\n * inputFormat: \"csv\",\r\n * outputFormat: \"json\"\r\n * });\r\n */\r\nexport async function convertFileToFile(\r\n inputFile: File | Blob,\r\n outputFilename: string,\r\n opts: BrowserConvertOptions = {}\r\n): Promise<void> {\r\n const result = await convertFile(inputFile, opts);\r\n \r\n // Create a download link\r\n const blob = new Blob([new Uint8Array(result)], { type: \"application/octet-stream\" });\r\n const url = URL.createObjectURL(blob);\r\n \r\n const link = document.createElement(\"a\");\r\n link.href = url;\r\n link.download = outputFilename;\r\n link.style.display = \"none\";\r\n \r\n document.body.appendChild(link);\r\n link.click();\r\n \r\n // Cleanup\r\n setTimeout(() => {\r\n document.body.removeChild(link);\r\n URL.revokeObjectURL(url);\r\n }, 100);\r\n}\r\n\r\n/**\r\n * Create a ReadableStream that converts data on-the-fly from a File or Blob.\r\n * Useful for piping through other stream processors.\r\n * \r\n * @example\r\n * const file = fileInput.files[0];\r\n * const stream = convertFileStream(file, {\r\n * inputFormat: \"csv\",\r\n * outputFormat: \"ndjson\"\r\n * });\r\n * \r\n * const reader = stream.getReader();\r\n * while (true) {\r\n * const { done, value } = await reader.read();\r\n * if (done) break;\r\n * // Process each chunk as it's converted\r\n * console.log(new TextDecoder().decode(value));\r\n * }\r\n */\r\nexport async function convertFileStream(\r\n file: File | Blob,\r\n opts: BrowserConvertOptions = {}\r\n): Promise<ReadableStream<Uint8Array>> {\r\n // Handle auto-detection\r\n let actualOpts = { ...opts };\r\n \r\n if (opts.inputFormat === \"auto\") {\r\n const sampleSize = 256 * 1024;\r\n const sampleBlob = file.slice(0, sampleSize);\r\n const sampleBuffer = await sampleBlob.arrayBuffer();\r\n const sample = new Uint8Array(sampleBuffer);\r\n \r\n const detected = await autoDetectConfig(sample, { debug: opts.debug });\r\n \r\n if (detected.format !== \"unknown\") {\r\n actualOpts.inputFormat = detected.format as Format;\r\n if (detected.csvConfig) {\r\n actualOpts.csvConfig = opts.csvConfig ? { ...detected.csvConfig, ...opts.csvConfig } : detected.csvConfig;\r\n }\r\n if (detected.xmlConfig) {\r\n actualOpts.xmlConfig = opts.xmlConfig ? { ...detected.xmlConfig, ...opts.xmlConfig } : detected.xmlConfig;\r\n }\r\n } else {\r\n throw new Error(\"Could not auto-detect input format.\");\r\n }\r\n }\r\n \r\n const buddy = await ConvertBuddy.create(actualOpts);\r\n const inputStream = file.stream();\r\n \r\n return new ReadableStream<Uint8Array>({\r\n async start(controller) {\r\n const reader = inputStream.getReader();\r\n \r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n \r\n if (done) {\r\n const final = buddy.finish();\r\n if (final.length > 0) {\r\n controller.enqueue(final);\r\n }\r\n controller.close();\r\n break;\r\n }\r\n \r\n if (buddy.isAborted()) {\r\n controller.error(new Error(\"Conversion aborted\"));\r\n break;\r\n }\r\n \r\n const output = buddy.push(value);\r\n if (output.length > 0) {\r\n controller.enqueue(output);\r\n }\r\n }\r\n } catch (error) {\r\n controller.error(error);\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n },\r\n });\r\n}\r\n"],"mappings":"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","names":[]}
@@ -6,20 +6,24 @@ type CsvDetection = {
6
6
  };
7
7
  type XmlDetection = {
8
8
  elements: string[];
9
+ recordElement?: string;
9
10
  };
10
11
  type DetectOptions = {
11
12
  maxBytes?: number;
12
13
  debug?: boolean;
13
14
  };
15
+ type ProgressCallback = (stats: Stats) => void;
14
16
  type ConvertBuddyOptions = {
15
17
  debug?: boolean;
16
18
  profile?: boolean;
17
- inputFormat?: Format;
19
+ inputFormat?: Format | "auto";
18
20
  outputFormat?: Format;
19
21
  chunkTargetBytes?: number;
20
22
  parallelism?: number;
21
23
  csvConfig?: CsvConfig;
22
24
  xmlConfig?: XmlConfig;
25
+ onProgress?: ProgressCallback;
26
+ progressIntervalBytes?: number;
23
27
  };
24
28
  type CsvConfig = {
25
29
  delimiter?: string;
@@ -49,19 +53,36 @@ declare class ConvertBuddy {
49
53
  private converter;
50
54
  private debug;
51
55
  private profile;
56
+ private aborted;
57
+ private paused;
58
+ private onProgress?;
59
+ private progressIntervalBytes;
60
+ private lastProgressBytes;
52
61
  private constructor();
53
62
  static create(opts?: ConvertBuddyOptions): Promise<ConvertBuddy>;
54
63
  push(chunk: Uint8Array): Uint8Array;
55
64
  finish(): Uint8Array;
56
65
  stats(): Stats;
66
+ abort(): void;
67
+ pause(): void;
68
+ resume(): void;
69
+ isAborted(): boolean;
70
+ isPaused(): boolean;
57
71
  }
58
72
  declare function detectFormat(input: DetectInput, opts?: DetectOptions): Promise<Format | "unknown">;
59
73
  declare function detectCsvFieldsAndDelimiter(input: DetectInput, opts?: DetectOptions): Promise<CsvDetection | null>;
60
74
  declare function detectXmlElements(input: DetectInput, opts?: DetectOptions): Promise<XmlDetection | null>;
75
+ declare function autoDetectConfig(sample: Uint8Array, opts?: {
76
+ debug?: boolean;
77
+ }): Promise<{
78
+ format: Format | "unknown";
79
+ csvConfig?: CsvConfig;
80
+ xmlConfig?: XmlConfig;
81
+ }>;
61
82
  declare class ConvertBuddyTransformStream extends TransformStream<Uint8Array, Uint8Array> {
62
83
  constructor(opts?: ConvertBuddyOptions);
63
84
  }
64
85
  declare function convert(input: Uint8Array | string, opts?: ConvertBuddyOptions): Promise<Uint8Array>;
65
86
  declare function convertToString(input: Uint8Array | string, opts?: ConvertBuddyOptions): Promise<string>;
66
87
 
67
- export { ConvertBuddy, type ConvertBuddyOptions, ConvertBuddyTransformStream, type CsvConfig, type CsvDetection, type DetectInput, type DetectOptions, type Format, type Stats, type XmlConfig, type XmlDetection, convert, convertToString, detectCsvFieldsAndDelimiter, detectFormat, detectXmlElements };
88
+ export { ConvertBuddy, type ConvertBuddyOptions, ConvertBuddyTransformStream, type CsvConfig, type CsvDetection, type DetectInput, type DetectOptions, type Format, type ProgressCallback, type Stats, type XmlConfig, type XmlDetection, autoDetectConfig, convert, convertToString, detectCsvFieldsAndDelimiter, detectFormat, detectXmlElements };
package/dist/src/index.js CHANGED
@@ -14,10 +14,17 @@ class ConvertBuddy {
14
14
  converter;
15
15
  debug;
16
16
  profile;
17
- constructor(converter, debug, profile) {
17
+ aborted = false;
18
+ paused = false;
19
+ onProgress;
20
+ progressIntervalBytes;
21
+ lastProgressBytes = 0;
22
+ constructor(converter, debug, profile, opts = {}) {
18
23
  this.converter = converter;
19
24
  this.debug = debug;
20
25
  this.profile = profile;
26
+ this.onProgress = opts.onProgress;
27
+ this.progressIntervalBytes = opts.progressIntervalBytes || 1024 * 1024;
21
28
  }
22
29
  static async create(opts = {}) {
23
30
  const debug = !!opts.debug;
@@ -27,35 +34,79 @@ class ConvertBuddy {
27
34
  await wasmModule.default();
28
35
  }
29
36
  wasmModule.init(debug);
37
+ let inputFormat = opts.inputFormat;
38
+ let csvConfig = opts.csvConfig;
39
+ if (inputFormat === "auto") {
40
+ inputFormat = void 0;
41
+ }
30
42
  let converter;
31
- if (opts.inputFormat && opts.outputFormat) {
43
+ if (inputFormat && opts.outputFormat) {
32
44
  const Converter = wasmModule.Converter;
33
45
  converter = Converter.withConfig(
34
46
  debug,
35
- opts.inputFormat,
47
+ inputFormat,
36
48
  opts.outputFormat,
37
49
  opts.chunkTargetBytes || 1024 * 1024,
38
50
  profile,
39
- opts.csvConfig,
40
- opts.xmlConfig
51
+ csvConfig || null,
52
+ opts.xmlConfig || null
41
53
  );
42
54
  } else {
43
55
  converter = new wasmModule.Converter(debug);
44
56
  }
45
57
  if (debug) console.log("[convert-buddy-js] initialized", opts);
46
- return new ConvertBuddy(converter, debug, profile);
58
+ return new ConvertBuddy(converter, debug, profile, opts);
47
59
  }
48
60
  push(chunk) {
61
+ if (this.aborted) {
62
+ throw new Error("Conversion has been aborted");
63
+ }
64
+ if (this.paused) {
65
+ throw new Error("Conversion is paused. Call resume() before pushing more data.");
66
+ }
49
67
  if (this.debug) console.log("[convert-buddy-js] push", chunk.byteLength);
50
- return this.converter.push(chunk);
68
+ const output = this.converter.push(chunk);
69
+ if (this.onProgress) {
70
+ const stats = this.stats();
71
+ if (stats.bytesIn - this.lastProgressBytes >= this.progressIntervalBytes) {
72
+ this.onProgress(stats);
73
+ this.lastProgressBytes = stats.bytesIn;
74
+ }
75
+ }
76
+ return output;
51
77
  }
52
78
  finish() {
79
+ if (this.aborted) {
80
+ throw new Error("Conversion has been aborted");
81
+ }
53
82
  if (this.debug) console.log("[convert-buddy-js] finish");
54
- return this.converter.finish();
83
+ const output = this.converter.finish();
84
+ if (this.onProgress) {
85
+ this.onProgress(this.stats());
86
+ }
87
+ return output;
55
88
  }
56
89
  stats() {
57
90
  return this.converter.getStats();
58
91
  }
92
+ abort() {
93
+ this.aborted = true;
94
+ if (this.debug) console.log("[convert-buddy-js] aborted");
95
+ }
96
+ pause() {
97
+ this.paused = true;
98
+ if (this.debug) console.log("[convert-buddy-js] paused");
99
+ }
100
+ resume() {
101
+ this.paused = false;
102
+ if (this.debug) console.log("[convert-buddy-js] resumed");
103
+ }
104
+ isAborted() {
105
+ return this.aborted;
106
+ }
107
+ isPaused() {
108
+ return this.paused;
109
+ }
59
110
  }
60
111
  async function readSample(input, maxBytes = 256 * 1024) {
61
112
  if (typeof input === "string") {
@@ -136,6 +187,28 @@ async function detectXmlElements(input, opts = {}) {
136
187
  const result = wasmModule.detectXmlElements?.(sample);
137
188
  return result ?? null;
138
189
  }
190
+ async function autoDetectConfig(sample, opts = {}) {
191
+ const wasmModule = await loadDetectionWasm(!!opts.debug);
192
+ const format = wasmModule.detectFormat?.(sample) ?? "unknown";
193
+ const result = { format };
194
+ if (format === "csv") {
195
+ const csvDetection = wasmModule.detectCsvFields?.(sample);
196
+ if (csvDetection) {
197
+ result.csvConfig = {
198
+ delimiter: csvDetection.delimiter,
199
+ hasHeaders: csvDetection.fields.length > 0
200
+ };
201
+ }
202
+ } else if (format === "xml") {
203
+ const xmlDetection = wasmModule.detectXmlElements?.(sample);
204
+ if (xmlDetection?.recordElement) {
205
+ result.xmlConfig = {
206
+ recordElement: xmlDetection.recordElement
207
+ };
208
+ }
209
+ }
210
+ return result;
211
+ }
139
212
  class ConvertBuddyTransformStream extends TransformStream {
140
213
  constructor(opts = {}) {
141
214
  let buddy = null;
@@ -189,6 +262,7 @@ async function convertToString(input, opts = {}) {
189
262
  export {
190
263
  ConvertBuddy,
191
264
  ConvertBuddyTransformStream,
265
+ autoDetectConfig,
192
266
  convert,
193
267
  convertToString,
194
268
  detectCsvFieldsAndDelimiter,