convert-buddy-js 0.9.9 → 0.10.2

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
@@ -1,3 +1,359 @@
1
+ > ⚠️ **Experimental / In Development**
2
+ > This project is under active development and may introduce breaking changes without notice.
3
+
4
+ # convert-buddy-js
5
+
6
+ A **high-performance, streaming-first** parser and converter for **CSV, XML, NDJSON, and JSON**.
7
+ `convert-buddy-js` is a **TypeScript wrapper around a Rust → WASM core**, designed for throughput and low memory overhead on large files, with unified APIs for **Node.js and modern browsers**.
8
+
9
+ ---
10
+
11
+ ## Status & Quality
12
+
13
+ [![Known Vulnerabilities](https://snyk.io/test/github/brunohanss/convert-buddy/badge.svg)](https://snyk.io/test/github/brunohanss/convert-buddy)
14
+ [![CI/CD Pipeline](https://github.com/brunohanss/convert-buddy/actions/workflows/test.yml/badge.svg)](https://github.com/brunohanss/convert-buddy/actions)
15
+ [![Coverage Status](https://img.shields.io/codecov/c/github/brunohanss/convert-buddy?label=coverage)](https://codecov.io/gh/brunohanss/convert-buddy)
16
+ [![npm version](https://img.shields.io/npm/v/convert-buddy-js.svg)](https://www.npmjs.com/package/convert-buddy-js)
17
+ [![Bundle Size](https://img.shields.io/bundlephobia/minzip/convert-buddy-js.svg)](https://bundlephobia.com/package/convert-buddy-js)
18
+
19
+ ---
20
+
21
+ ## Why Convert Buddy
22
+
23
+ **What it optimizes for**
24
+ - Performance: Rust/WASM fast-path parsing and conversion
25
+ - Streaming-first: convert without loading entire inputs into memory
26
+ - Unified multi-format API: one interface for CSV / XML / NDJSON / JSON
27
+ - Cross-platform: Node.js and modern browsers
28
+
29
+ **When you might not want it**
30
+ - Tiny inputs where WASM initialization overhead dominates
31
+ - Highly specialized format features
32
+ - Environments where WASM is restricted
33
+
34
+ ---
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ npm install convert-buddy-js
40
+ ```
41
+
42
+ ## Quick Start
43
+
44
+ The simplest way: auto-detect input type + format
45
+
46
+ ```ts
47
+ import { convert, convertToString } from "convert-buddy-js";
48
+
49
+ // From URL
50
+ const json = await convertToString("https://example.com/data.csv", {
51
+ outputFormat: "json",
52
+ });
53
+
54
+ // From File (browser)
55
+ const file = fileInput.files![0];
56
+ const ndjson = await convertToString(file, { outputFormat: "ndjson" });
57
+
58
+ // From string data
59
+ const csv = "name,age\nAda,36";
60
+ const out = await convertToString(csv, { outputFormat: "json" });
61
+
62
+ // Returns Uint8Array instead of string
63
+ const bytes = await convert(file, { outputFormat: "json" });
64
+ ```
65
+
66
+ ### Instance-based API (reuse global config)
67
+
68
+ ```ts
69
+ import { ConvertBuddy } from "convert-buddy-js";
70
+
71
+ const buddy = new ConvertBuddy({
72
+ debug: true,
73
+ maxMemoryMB: 512,
74
+ onProgress: (stats) =>
75
+ console.log(`${stats.recordsProcessed} records processed`),
76
+ });
77
+
78
+ const result = await buddy.convert("https://example.com/data.csv", {
79
+ outputFormat: "json",
80
+ });
81
+ ```
82
+
83
+ ### Platform entrypoints (recommended)
84
+
85
+ **Browser**
86
+
87
+ ```ts
88
+ import { convertFileToString } from "convert-buddy-js/browser";
89
+
90
+ const file =
91
+ document.querySelector<HTMLInputElement>('input[type="file"]')!
92
+ .files![0];
93
+
94
+ const json = await convertFileToString(file, {
95
+ inputFormat: "auto",
96
+ outputFormat: "json",
97
+ });
98
+ ```
99
+
100
+ **Node.js**
101
+
102
+ ```ts
103
+ import { convertToString } from "convert-buddy-js/node";
104
+
105
+ const json = await convertToString("input.csv", {
106
+ outputFormat: "json",
107
+ });
108
+ ```
109
+
110
+ ---
111
+
112
+ ## API Overview
113
+
114
+ Convert Buddy offers four layers of control, from one-liners to fully manual streaming.
115
+
116
+ 1. Ultra-simple API
117
+
118
+ ```ts
119
+ import { convert, convertToString } from "convert-buddy-js";
120
+
121
+ const json = await convertToString(input, { outputFormat: "json" });
122
+ ```
123
+
124
+ Supported input types
125
+
126
+ - URLs
127
+ - Browser `File`
128
+ - `Uint8Array` / Node `Buffer`
129
+ - Raw strings
130
+ - `ReadableStream<Uint8Array>`
131
+ - Node file paths
132
+
133
+ 2. Instance-based API
134
+
135
+ ```ts
136
+ import { ConvertBuddy } from "convert-buddy-js";
137
+
138
+ const buddy = new ConvertBuddy({
139
+ profile: true,
140
+ progressIntervalBytes: 1024 * 1024,
141
+ onProgress: (s) => console.log(s.throughputMbPerSec),
142
+ });
143
+
144
+ const out = await buddy.convert(file, { outputFormat: "ndjson" });
145
+ ```
146
+
147
+ 3. High-level helpers
148
+
149
+ **Browser helpers**
150
+
151
+ ```ts
152
+ import {
153
+ convertFileToString,
154
+ convertFileToFile,
155
+ convertAndSave,
156
+ convertFileStream,
157
+ convertStreamToWritable,
158
+ autoConvertStream,
159
+ isFileSystemAccessSupported,
160
+ getMimeType,
161
+ getExtension,
162
+ getSuggestedFilename,
163
+ } from "convert-buddy-js/browser";
164
+
165
+ const json = await convertFileToString(file, { outputFormat: "json" });
166
+ ```
167
+
168
+ **Node helpers**
169
+
170
+ ```ts
171
+ import {
172
+ convertFileToString,
173
+ convertFileToFile,
174
+ convertStream,
175
+ } from "convert-buddy-js/node";
176
+
177
+ const json = await convertFileToString("input.csv", {
178
+ inputFormat: "csv",
179
+ outputFormat: "json",
180
+ });
181
+ ```
182
+
183
+ 4. Low-level API
184
+
185
+ Manual chunked streaming
186
+
187
+ ```ts
188
+ import { ConvertBuddy } from "convert-buddy-js";
189
+
190
+ const converter = await ConvertBuddy.create({
191
+ inputFormat: "xml",
192
+ outputFormat: "ndjson",
193
+ xmlConfig: { recordElement: "row", includeAttributes: true },
194
+ });
195
+
196
+ converter.push(new Uint8Array([/* bytes */]));
197
+ converter.push(new Uint8Array([/* bytes */]));
198
+
199
+ const final = converter.finish();
200
+ console.log(converter.stats());
201
+ ```
202
+
203
+ Node.js Transform stream
204
+
205
+ ```ts
206
+ import { createNodeTransform } from "convert-buddy-js/node";
207
+ import { createReadStream, createWriteStream } from "node:fs";
208
+
209
+ const transform = await createNodeTransform({
210
+ inputFormat: "csv",
211
+ outputFormat: "ndjson",
212
+ csvConfig: { hasHeaders: true },
213
+ profile: true,
214
+ });
215
+
216
+ createReadStream("input.csv")
217
+ .pipe(transform)
218
+ .pipe(createWriteStream("output.ndjson"));
219
+ ```
220
+
221
+ Web Streams
222
+
223
+ ```ts
224
+ import { ConvertBuddyTransformStream } from "convert-buddy-js";
225
+
226
+ const transform = new ConvertBuddyTransformStream({
227
+ inputFormat: "csv",
228
+ outputFormat: "ndjson",
229
+ });
230
+
231
+ const response = await fetch("/data.csv");
232
+ const output = response.body?.pipeThrough(transform);
233
+ ```
234
+
235
+ ---
236
+
237
+ ## Formats
238
+
239
+ Supported
240
+
241
+ - `csv`
242
+ - `xml`
243
+ - `ndjson`
244
+ - `json`
245
+ - `auto`
246
+
247
+ ### CSV options
248
+
249
+ ```ts
250
+ {
251
+ csvConfig: {
252
+ delimiter: ",",
253
+ quote: '"',
254
+ hasHeaders: true,
255
+ trimWhitespace: false,
256
+ }
257
+ }
258
+ ```
259
+
260
+ ### XML options
261
+
262
+ ```ts
263
+ {
264
+ xmlConfig: {
265
+ recordElement: "row",
266
+ trimText: true,
267
+ includeAttributes: true,
268
+ }
269
+ }
270
+ ```
271
+
272
+ ### Performance options
273
+
274
+ ```ts
275
+ {
276
+ chunkTargetBytes: 1024 * 1024,
277
+ parallelism: 4,
278
+ profile: true,
279
+ debug: false,
280
+ }
281
+ ```
282
+
283
+ ---
284
+
285
+ ## Transformations & Field Mapping
286
+
287
+ Convert Buddy supports field-level transformations during conversion via the `transform` option. Example:
288
+
289
+ ```ts
290
+ const out = await buddy.convert(csvString, {
291
+ outputFormat: "json",
292
+ transform: {
293
+ mode: "augment",
294
+ fields: [
295
+ { targetFieldName: "full_name", compute: "concat(first, ' ', last)" },
296
+ { targetFieldName: "age", coerce: { type: "i64" }, defaultValue: 0 },
297
+ ],
298
+ onMissingField: "null",
299
+ onCoerceError: "null",
300
+ },
301
+ });
302
+ ```
303
+
304
+ Runtime compute helpers depend on the Rust/WASM core build. For complex transforms consider pre/post-processing in JS.
305
+
306
+ ---
307
+
308
+ ## Auto-detection & inspection
309
+
310
+ Detect format
311
+
312
+ ```ts
313
+ import { detectFormat } from "convert-buddy-js";
314
+
315
+ const format = await detectFormat(stream, { maxBytes: 256 * 1024 });
316
+ ```
317
+
318
+ Detect structure
319
+
320
+ ```ts
321
+ import { detectStructure } from "convert-buddy-js";
322
+
323
+ const structure = await detectStructure(stream);
324
+ ```
325
+
326
+ ---
327
+
328
+ ## How it works
329
+
330
+ - Rust core implements streaming parsers and conversion
331
+ - WASM bindings generated via `wasm-bindgen`
332
+ - TypeScript wrapper exposes high-level APIs and stream adapters
333
+
334
+ ## What ships in the npm package
335
+
336
+ - Prebuilt WASM binaries
337
+ - Compiled JS / TypeScript output
338
+
339
+ Rust sources, demos, and benchmarks live in the repository but are not published in the npm package.
340
+
341
+ ---
342
+
343
+ ## Benchmarks (repository)
344
+
345
+ ```bash
346
+ cd packages/convert-buddy-js
347
+ npm run bench
348
+ npm run bench:check
349
+ npm run bench:competitors
350
+ ```
351
+
352
+ ---
353
+
354
+ ## License
355
+
356
+ MIT
1
357
  > ⚠️ **In Development** - This project is currently under active development and subject to breaking changes. Experimental state, could change heavily.
2
358
 
3
359
  # convert-buddy-js
@@ -22,6 +378,7 @@ npm install convert-buddy-js
22
378
 
23
379
  ### The Simplest Way - Auto-Detect Everything
24
380
 
381
+ ```
25
382
  ```ts
26
383
  import { convert, convertToString } from "convert-buddy-js";
27
384
 
@@ -405,6 +762,63 @@ const handle = await window.showSaveFilePicker({
405
762
  });
406
763
  ```
407
764
 
765
+ ---
766
+
767
+ ### Transformations & Field Mapping
768
+
769
+ Convert Buddy supports field-level transformations during conversion via the `transform` option. This lets you rename fields, provide defaults, coerce types, compute derived values, and choose whether to replace or augment existing records.
770
+
771
+ Key concepts:
772
+ - **`TransformConfig`**: top-level transform config with `mode` (`"replace" | "augment"`) and a `fields` array.
773
+ - **`FieldMap`**: maps one output field to an input field and supports `required`, `defaultValue`, `coerce`, and `compute`.
774
+ - **`Coerce`**: supported coercions include `string`, `i64`, `f64`, `bool`, and `timestamp_ms` (with formats `iso8601`, `unix_ms`, `unix_s`).
775
+ - **Error handling**: control missing/invalid data with `onMissingField`, `onMissingRequired`, and `onCoerceError`.
776
+
777
+ Computed fields let you derive values from other fields or runtime data. The `compute` property is a short expression string evaluated by the conversion runtime (WASM core). Below are common usage patterns; actual available functions/operators depend on the runtime build.
778
+
779
+ Basic examples:
780
+
781
+ ```ts
782
+ // Derive a full name from first/last
783
+ { targetFieldName: "full_name", compute: "concat(first, ' ', last)" }
784
+
785
+ // Current epoch milliseconds
786
+ { targetFieldName: "ingest_ts", compute: "now()" }
787
+
788
+ // Multiply numeric fields
789
+ { targetFieldName: "total_price", compute: "price * quantity", coerce: { type: "f64" } }
790
+
791
+ // Safe lookup with default (example expression syntax may vary)
792
+ { targetFieldName: "country", compute: "coalesce(country, 'unknown')" }
793
+ ```
794
+
795
+ Runtime helpers & guidance:
796
+
797
+ - `compute` expression availability depends on the Rust/WASM core compiled into the package. Check the package `wasm/` runtime docs or the project `crates/convert-buddy` README for the exact helper list.
798
+ - Typical helpers you may find in supported builds: `now()`, `concat()`, `coalesce()`, basic arithmetic and string functions, and simple date parsing/formatting helpers.
799
+ - If a compute expression is not supported by the runtime, the conversion will follow the `onCoerceError` / `onMissingField` policy you configured (e.g., return `null`, drop the record, or error).
800
+ - For complex transformations that are not available in-WASM, you can:
801
+ - Preprocess input with a small JS step to add computed fields before passing to `convert`, or
802
+ - Post-process the converted output in JS (useful when runtime compute helpers are intentionally minimal for performance/size).
803
+
804
+ Example - `augment` mode with computed field and default handling:
805
+
806
+ ```ts
807
+ const out = await buddy.convert(csvString, {
808
+ outputFormat: "json",
809
+ transform: {
810
+ mode: "augment",
811
+ fields: [
812
+ { targetFieldName: "full_name", compute: "concat(first, ' ', last)" },
813
+ { targetFieldName: "age", coerce: { type: "i64" }, defaultValue: 0 }
814
+ ],
815
+ onMissingField: "null",
816
+ onCoerceError: "null"
817
+ }
818
+ });
819
+ ```
820
+
821
+
408
822
  #### Advanced Browser Streaming
409
823
 
410
824
  For maximum efficiency with large files, use streaming APIs to avoid loading entire files into memory:
@@ -521,7 +935,7 @@ console.log(xmlStructure?.recordElement); // "record"
521
935
  // These now use detectStructure internally
522
936
  const csvInfo = await detectCsvFieldsAndDelimiter(fileStream);
523
937
  const xmlInfo = await detectXmlElements(fileStream);
524
- ```
938
+
525
939
  ```
526
940
 
527
941
  ## Configuration
package/dist/browser.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ConvertBuddyOptions, ConvertOptions, Format } from './index.js';
2
- export { ConvertBuddy, ConvertBuddyTransformStream, CsvConfig, CsvDetection, DetectInput, DetectOptions, JsonDetection, NdjsonDetection, ProgressCallback, Stats, StructureDetection, XmlConfig, XmlDetection, autoDetectConfig, convertAny, convertAnyToString, detectCsvFieldsAndDelimiter, detectFormat, detectStructure, detectXmlElements, getExtension, getFileTypeConfig, getMimeType, getOptimalThreadCount, getSuggestedFilename, getThreadingInfo, isWasmThreadingSupported } from './index.js';
2
+ export { Coerce, ConvertBuddy, ConvertBuddyTransformStream, CsvConfig, CsvDetection, DetectInput, DetectOptions, FieldMap, JsonDetection, NdjsonDetection, ProgressCallback, Stats, StructureDetection, TransformConfig, TransformMode, XmlConfig, XmlDetection, autoDetectConfig, convertAny, convertAnyToString, detectCsvFieldsAndDelimiter, detectFormat, detectStructure, detectXmlElements, getExtension, getFileTypeConfig, getMimeType, getOptimalThreadCount, getSuggestedFilename, getThreadingInfo, isWasmThreadingSupported } from './index.js';
3
3
 
4
4
  /**
5
5
  * Stream a File/Blob conversion in a Web Worker with progress updates.
package/dist/browser.js CHANGED
@@ -16,8 +16,8 @@ async function streamProcessFileInWorker(file, opts = {}, onProgress) {
16
16
  worker.postMessage({ type: "start", file, opts });
17
17
  });
18
18
  }
19
- import { ConvertBuddy, autoDetectConfig, getFileTypeConfig, convertAny as convertAnyCore, convertAnyToString as convertAnyToStringCore } from "./index.js";
20
- export * from "./index.js";
19
+ import { ConvertBuddy, autoDetectConfig, getFileTypeConfig, convertAny as convertAnyCore, convertAnyToString as convertAnyToStringCore } from "./index";
20
+ export * from "./index";
21
21
  async function convert(input, opts) {
22
22
  return convertAnyCore(input, opts);
23
23
  }
@@ -26,7 +26,8 @@ async function convertToString(input, opts) {
26
26
  }
27
27
  async function convertFileToString(file, opts = {}) {
28
28
  const bytes = await convertFile(file, opts);
29
- return new TextDecoder().decode(bytes);
29
+ const decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
30
+ return decoder.decode(bytes);
30
31
  }
31
32
  async function convertFile(file, opts = {}) {
32
33
  let actualOpts = { ...opts };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/browser.ts"],"sourcesContent":["/**\r\n * Stream a File/Blob conversion in a Web Worker with progress updates.\r\n *\r\n * @param file File or Blob to convert\r\n * @param opts Convert options (inputFormat, outputFormat, etc)\r\n * @param onProgress Callback for progress updates\r\n * @returns Promise<Uint8Array> with the converted result\r\n */\r\nexport async function streamProcessFileInWorker(\r\n file: File | Blob,\r\n opts: BrowserConvertOptions = {},\r\n onProgress?: (progress: { bytesRead: number; bytesWritten: number; percentComplete: number; recordsProcessed: number }) => void\r\n): Promise<Uint8Array> {\r\n return new Promise((resolve, reject) => {\r\n // Create worker\r\n const worker = new Worker(new URL('./streaming-worker.js', import.meta.url), { type: 'module' });\r\n\r\n worker.onmessage = (event) => {\r\n const { type, ...data } = event.data;\r\n if (type === 'progress' && onProgress) {\r\n onProgress(data);\r\n } else if (type === 'complete') {\r\n worker.terminate();\r\n resolve(new Uint8Array(data.result));\r\n } else if (type === 'error') {\r\n worker.terminate();\r\n reject(new Error(data.error));\r\n }\r\n };\r\n\r\n // Send file and options to worker\r\n worker.postMessage({ type: 'start', file, opts });\r\n });\r\n}\r\nimport { ConvertBuddy, type ConvertBuddyOptions, type ConvertOptions, type Format, autoDetectConfig, detectFormat, getMimeType, getFileTypeConfig, convertAny as convertAnyCore, convertAnyToString as convertAnyToStringCore } 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 * Ultra-simple convert function for browser.\r\n * Auto-detects input type (File, URL, string, etc.) and format.\r\n * \r\n * @example\r\n * // From File\r\n * const file = fileInput.files[0];\r\n * const result = await convert(file, { outputFormat: \"json\" });\r\n * \r\n * @example\r\n * // From URL\r\n * const result = await convert(\"https://example.com/data.csv\", { outputFormat: \"json\" });\r\n * \r\n * @example\r\n * // From string\r\n * const result = await convert(csvString, { outputFormat: \"json\" });\r\n */\r\nexport async function convert(\r\n input: string | Uint8Array | File | Blob | ReadableStream<Uint8Array>,\r\n opts: ConvertOptions\r\n): Promise<Uint8Array> {\r\n return convertAnyCore(input, opts);\r\n}\r\n\r\n/**\r\n * Ultra-simple convert function that returns a string.\r\n * \r\n * @example\r\n * const json = await convertToString(file, { outputFormat: \"json\" });\r\n * console.log(JSON.parse(json));\r\n */\r\nexport async function convertToString(\r\n input: string | Uint8Array | File | Blob | ReadableStream<Uint8Array>,\r\n opts: ConvertOptions\r\n): Promise<string> {\r\n return convertAnyToStringCore(input, opts);\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 as ArrayBuffer);\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 // Adaptive chunk sizing based on file size\r\n // Tuned to balance WASM boundary crossing reduction with memory efficiency\r\n if (!actualOpts.chunkTargetBytes) {\r\n const fileSize = file.size;\r\n actualOpts.chunkTargetBytes = Math.max(\r\n 512 * 1024, // minimum: 512KB\r\n Math.min(\r\n 1 * 1024 * 1024, // maximum: 1MB (conservative for memory)\r\n Math.floor(fileSize / 16) // 1/16 of file size\r\n )\r\n );\r\n if (opts.debug) {\r\n console.log(`[convert-buddy] Adaptive chunk sizing: ${fileSize} bytes → ${actualOpts.chunkTargetBytes} byte chunks`);\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/**\r\n * Auto-detect format and create a ReadableStream for conversion.\r\n * This is a convenience helper that combines format detection with streaming conversion.\r\n * \r\n * @example\r\n * const file = fileInput.files[0];\r\n * const stream = await autoConvertStream(file, { outputFormat: \"json\" });\r\n * \r\n * // Pipe to response\r\n * return new Response(stream);\r\n */\r\nexport async function autoConvertStream(\r\n file: File | Blob,\r\n opts: Omit<BrowserConvertOptions, \"inputFormat\"> & { outputFormat: Format }\r\n): Promise<ReadableStream<Uint8Array>> {\r\n return convertFileStream(file, {\r\n ...opts,\r\n inputFormat: \"auto\",\r\n });\r\n}\r\n\r\n/**\r\n * Stream a file conversion directly to a writable destination.\r\n * This is useful for streaming large conversions to disk using File System Access API\r\n * or to other writable streams.\r\n * \r\n * @example\r\n * // Using File System Access API\r\n * const fileInput = document.querySelector('input[type=\"file\"]');\r\n * const file = fileInput.files[0];\r\n * \r\n * const fileHandle = await window.showSaveFilePicker({\r\n * suggestedName: \"output.json\",\r\n * types: [{ description: \"JSON\", accept: { \"application/json\": [\".json\"] } }]\r\n * });\r\n * const writable = await fileHandle.createWritable();\r\n * \r\n * await convertStreamToWritable(file, writable, {\r\n * inputFormat: \"csv\",\r\n * outputFormat: \"json\",\r\n * onProgress: (stats) => console.log(`${stats.bytesIn} bytes processed`)\r\n * });\r\n * \r\n * @example\r\n * // With auto-detection\r\n * await convertStreamToWritable(file, writable, {\r\n * inputFormat: \"auto\",\r\n * outputFormat: \"ndjson\"\r\n * });\r\n */\r\nexport async function convertStreamToWritable(\r\n file: File | Blob,\r\n writable: WritableStream<Uint8Array> | FileSystemWritableFileStream,\r\n opts: BrowserConvertOptions = {}\r\n): Promise<void> {\r\n const outputStream = await convertFileStream(file, opts);\r\n await outputStream.pipeTo(writable);\r\n}\r\n\r\n/**\r\n * Check if File System Access API is supported in the current browser.\r\n * Use this to determine if you can use File System Access API features.\r\n * \r\n * @example\r\n * if (isFileSystemAccessSupported()) {\r\n * // Use File System Access API\r\n * const handle = await window.showSaveFilePicker();\r\n * } else {\r\n * // Fall back to download link\r\n * await convertFileToFile(file, \"output.json\", opts);\r\n * }\r\n */\r\nexport function isFileSystemAccessSupported(): boolean {\r\n return (\r\n typeof window !== \"undefined\" &&\r\n \"showSaveFilePicker\" in window &&\r\n \"showOpenFilePicker\" in window\r\n );\r\n}\r\n\r\n/**\r\n * Convert a file and save it using File System Access API with a user-selected location.\r\n * This provides a better UX than automatic downloads by letting users choose where to save.\r\n * Falls back to regular download if File System Access API is not supported.\r\n * \r\n * @example\r\n * const file = fileInput.files[0];\r\n * await convertAndSave(file, {\r\n * inputFormat: \"csv\",\r\n * outputFormat: \"json\",\r\n * suggestedName: \"output.json\"\r\n * });\r\n */\r\nexport async function convertAndSave(\r\n file: File | Blob,\r\n opts: BrowserConvertOptions & {\r\n suggestedName?: string;\r\n } = {}\r\n): Promise<void> {\r\n if (!isFileSystemAccessSupported()) {\r\n // Fall back to regular download\r\n const filename = opts.suggestedName || \"converted\";\r\n await convertFileToFile(file, filename, opts);\r\n return;\r\n }\r\n\r\n const outputFormat = opts.outputFormat || \"json\";\r\n const suggestedName = opts.suggestedName || `converted.${outputFormat}`;\r\n const types = getFileTypeConfig(outputFormat);\r\n\r\n try {\r\n const fileHandle = await (window as any).showSaveFilePicker({\r\n suggestedName,\r\n types,\r\n });\r\n\r\n const writable = await fileHandle.createWritable();\r\n await convertStreamToWritable(file, writable, opts);\r\n } catch (error: any) {\r\n // User cancelled or error occurred\r\n if (error?.name === \"AbortError\") {\r\n // User cancelled - this is fine, just return\r\n return;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Stream a File/Blob conversion in a Web Worker and pipe output to a Writable.\r\n * This is useful for saving directly to disk using File System Access API.\r\n */\r\nexport async function streamProcessFileInWorkerToWritable(\r\n file: File | Blob,\r\n writable: WritableStream<Uint8Array> | FileSystemWritableFileStream,\r\n opts: BrowserConvertOptions = {},\r\n onProgress?: (progress: { bytesRead: number; bytesWritten: number; percentComplete: number; recordsProcessed: number }) => void\r\n): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const worker = new Worker(new URL('./streaming-worker.js', import.meta.url), { type: 'module' });\r\n let writer: WritableStreamDefaultWriter<Uint8Array> | null = null;\r\n\r\n (async () => {\r\n try {\r\n if ((writable as any).getWriter) {\r\n writer = (writable as WritableStream<Uint8Array>).getWriter();\r\n } else {\r\n // FileSystemWritableFileStream supports write(Uint8Array)\r\n writer = null;\r\n }\r\n\r\n worker.onmessage = async (event) => {\r\n const { type } = event.data;\r\n if (type === 'data') {\r\n const ab: ArrayBuffer = event.data.chunk;\r\n const chunk = new Uint8Array(ab);\r\n if (writer) {\r\n await writer.write(chunk);\r\n } else if ((writable as any).write) {\r\n await (writable as any).write(chunk);\r\n }\r\n } else if (type === 'progress') {\r\n onProgress?.(event.data);\r\n } else if (type === 'complete') {\r\n // write final chunk if present\r\n if (event.data.result) {\r\n const finalChunk = new Uint8Array(event.data.result);\r\n if (writer) await writer.write(finalChunk);\r\n else if ((writable as any).write) await (writable as any).write(finalChunk);\r\n }\r\n\r\n try {\r\n // Only close if we have a writer (WritableStream with getWriter)\r\n // FileSystemWritableFileStream should NOT be closed by us - caller owns it\r\n if (writer) {\r\n await writer.close();\r\n }\r\n } catch (e) {\r\n // ignore close errors\r\n }\r\n worker.terminate();\r\n resolve();\r\n } else if (type === 'error') {\r\n worker.terminate();\r\n reject(new Error(event.data.error));\r\n }\r\n };\r\n\r\n // Start worker\r\n worker.postMessage({ type: 'start', file, opts });\r\n } catch (err) {\r\n if (writer) {\r\n try { await writer.abort(err); } catch {}\r\n }\r\n worker.terminate();\r\n reject(err);\r\n }\r\n })();\r\n });\r\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/browser.ts"],"sourcesContent":["/**\r\n * Stream a File/Blob conversion in a Web Worker with progress updates.\r\n *\r\n * @param file File or Blob to convert\r\n * @param opts Convert options (inputFormat, outputFormat, etc)\r\n * @param onProgress Callback for progress updates\r\n * @returns Promise<Uint8Array> with the converted result\r\n */\r\nexport async function streamProcessFileInWorker(\r\n file: File | Blob,\r\n opts: BrowserConvertOptions = {},\r\n onProgress?: (progress: { bytesRead: number; bytesWritten: number; percentComplete: number; recordsProcessed: number }) => void\r\n): Promise<Uint8Array> {\r\n return new Promise((resolve, reject) => {\r\n // Create worker\r\n const worker = new Worker(new URL('./streaming-worker.js', import.meta.url), { type: 'module' });\r\n\r\n worker.onmessage = (event) => {\r\n const { type, ...data } = event.data;\r\n if (type === 'progress' && onProgress) {\r\n onProgress(data);\r\n } else if (type === 'complete') {\r\n worker.terminate();\r\n resolve(new Uint8Array(data.result));\r\n } else if (type === 'error') {\r\n worker.terminate();\r\n reject(new Error(data.error));\r\n }\r\n };\r\n\r\n // Send file and options to worker\r\n worker.postMessage({ type: 'start', file, opts });\r\n });\r\n}\r\nimport { ConvertBuddy, type ConvertBuddyOptions, type ConvertOptions, type Format, autoDetectConfig, detectFormat, getMimeType, getFileTypeConfig, convertAny as convertAnyCore, convertAnyToString as convertAnyToStringCore } from \"./index\";\r\n\r\nexport * from \"./index\";\r\n\r\nexport type BrowserConvertOptions = ConvertBuddyOptions & {\r\n // Additional browser-specific options can go here\r\n};\r\n\r\n/**\r\n * Ultra-simple convert function for browser.\r\n * Auto-detects input type (File, URL, string, etc.) and format.\r\n * \r\n * @example\r\n * // From File\r\n * const file = fileInput.files[0];\r\n * const result = await convert(file, { outputFormat: \"json\" });\r\n * \r\n * @example\r\n * // From URL\r\n * const result = await convert(\"https://example.com/data.csv\", { outputFormat: \"json\" });\r\n * \r\n * @example\r\n * // From string\r\n * const result = await convert(csvString, { outputFormat: \"json\" });\r\n */\r\nexport async function convert(\r\n input: string | Uint8Array | File | Blob | ReadableStream<Uint8Array>,\r\n opts: ConvertOptions\r\n): Promise<Uint8Array> {\r\n return convertAnyCore(input, opts);\r\n}\r\n\r\n/**\r\n * Ultra-simple convert function that returns a string.\r\n * \r\n * @example\r\n * const json = await convertToString(file, { outputFormat: \"json\" });\r\n * console.log(JSON.parse(json));\r\n */\r\nexport async function convertToString(\r\n input: string | Uint8Array | File | Blob | ReadableStream<Uint8Array>,\r\n opts: ConvertOptions\r\n): Promise<string> {\r\n return convertAnyToStringCore(input, opts);\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 const decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true });\r\n return decoder.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 as ArrayBuffer);\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 // Adaptive chunk sizing based on file size\r\n // Tuned to balance WASM boundary crossing reduction with memory efficiency\r\n if (!actualOpts.chunkTargetBytes) {\r\n const fileSize = file.size;\r\n actualOpts.chunkTargetBytes = Math.max(\r\n 512 * 1024, // minimum: 512KB\r\n Math.min(\r\n 1 * 1024 * 1024, // maximum: 1MB (conservative for memory)\r\n Math.floor(fileSize / 16) // 1/16 of file size\r\n )\r\n );\r\n if (opts.debug) {\r\n console.log(`[convert-buddy] Adaptive chunk sizing: ${fileSize} bytes → ${actualOpts.chunkTargetBytes} byte chunks`);\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/**\r\n * Auto-detect format and create a ReadableStream for conversion.\r\n * This is a convenience helper that combines format detection with streaming conversion.\r\n * \r\n * @example\r\n * const file = fileInput.files[0];\r\n * const stream = await autoConvertStream(file, { outputFormat: \"json\" });\r\n * \r\n * // Pipe to response\r\n * return new Response(stream);\r\n */\r\nexport async function autoConvertStream(\r\n file: File | Blob,\r\n opts: Omit<BrowserConvertOptions, \"inputFormat\"> & { outputFormat: Format }\r\n): Promise<ReadableStream<Uint8Array>> {\r\n return convertFileStream(file, {\r\n ...opts,\r\n inputFormat: \"auto\",\r\n });\r\n}\r\n\r\n/**\r\n * Stream a file conversion directly to a writable destination.\r\n * This is useful for streaming large conversions to disk using File System Access API\r\n * or to other writable streams.\r\n * \r\n * @example\r\n * // Using File System Access API\r\n * const fileInput = document.querySelector('input[type=\"file\"]');\r\n * const file = fileInput.files[0];\r\n * \r\n * const fileHandle = await window.showSaveFilePicker({\r\n * suggestedName: \"output.json\",\r\n * types: [{ description: \"JSON\", accept: { \"application/json\": [\".json\"] } }]\r\n * });\r\n * const writable = await fileHandle.createWritable();\r\n * \r\n * await convertStreamToWritable(file, writable, {\r\n * inputFormat: \"csv\",\r\n * outputFormat: \"json\",\r\n * onProgress: (stats) => console.log(`${stats.bytesIn} bytes processed`)\r\n * });\r\n * \r\n * @example\r\n * // With auto-detection\r\n * await convertStreamToWritable(file, writable, {\r\n * inputFormat: \"auto\",\r\n * outputFormat: \"ndjson\"\r\n * });\r\n */\r\nexport async function convertStreamToWritable(\r\n file: File | Blob,\r\n writable: WritableStream<Uint8Array> | FileSystemWritableFileStream,\r\n opts: BrowserConvertOptions = {}\r\n): Promise<void> {\r\n const outputStream = await convertFileStream(file, opts);\r\n await outputStream.pipeTo(writable);\r\n}\r\n\r\n/**\r\n * Check if File System Access API is supported in the current browser.\r\n * Use this to determine if you can use File System Access API features.\r\n * \r\n * @example\r\n * if (isFileSystemAccessSupported()) {\r\n * // Use File System Access API\r\n * const handle = await window.showSaveFilePicker();\r\n * } else {\r\n * // Fall back to download link\r\n * await convertFileToFile(file, \"output.json\", opts);\r\n * }\r\n */\r\nexport function isFileSystemAccessSupported(): boolean {\r\n return (\r\n typeof window !== \"undefined\" &&\r\n \"showSaveFilePicker\" in window &&\r\n \"showOpenFilePicker\" in window\r\n );\r\n}\r\n\r\n/**\r\n * Convert a file and save it using File System Access API with a user-selected location.\r\n * This provides a better UX than automatic downloads by letting users choose where to save.\r\n * Falls back to regular download if File System Access API is not supported.\r\n * \r\n * @example\r\n * const file = fileInput.files[0];\r\n * await convertAndSave(file, {\r\n * inputFormat: \"csv\",\r\n * outputFormat: \"json\",\r\n * suggestedName: \"output.json\"\r\n * });\r\n */\r\nexport async function convertAndSave(\r\n file: File | Blob,\r\n opts: BrowserConvertOptions & {\r\n suggestedName?: string;\r\n } = {}\r\n): Promise<void> {\r\n if (!isFileSystemAccessSupported()) {\r\n // Fall back to regular download\r\n const filename = opts.suggestedName || \"converted\";\r\n await convertFileToFile(file, filename, opts);\r\n return;\r\n }\r\n\r\n const outputFormat = opts.outputFormat || \"json\";\r\n const suggestedName = opts.suggestedName || `converted.${outputFormat}`;\r\n const types = getFileTypeConfig(outputFormat);\r\n\r\n try {\r\n const fileHandle = await (window as any).showSaveFilePicker({\r\n suggestedName,\r\n types,\r\n });\r\n\r\n const writable = await fileHandle.createWritable();\r\n await convertStreamToWritable(file, writable, opts);\r\n } catch (error: any) {\r\n // User cancelled or error occurred\r\n if (error?.name === \"AbortError\") {\r\n // User cancelled - this is fine, just return\r\n return;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Stream a File/Blob conversion in a Web Worker and pipe output to a Writable.\r\n * This is useful for saving directly to disk using File System Access API.\r\n */\r\nexport async function streamProcessFileInWorkerToWritable(\r\n file: File | Blob,\r\n writable: WritableStream<Uint8Array> | FileSystemWritableFileStream,\r\n opts: BrowserConvertOptions = {},\r\n onProgress?: (progress: { bytesRead: number; bytesWritten: number; percentComplete: number; recordsProcessed: number }) => void\r\n): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const worker = new Worker(new URL('./streaming-worker.js', import.meta.url), { type: 'module' });\r\n let writer: WritableStreamDefaultWriter<Uint8Array> | null = null;\r\n\r\n (async () => {\r\n try {\r\n if ((writable as any).getWriter) {\r\n writer = (writable as WritableStream<Uint8Array>).getWriter();\r\n } else {\r\n // FileSystemWritableFileStream supports write(Uint8Array)\r\n writer = null;\r\n }\r\n\r\n worker.onmessage = async (event) => {\r\n const { type } = event.data;\r\n if (type === 'data') {\r\n const ab: ArrayBuffer = event.data.chunk;\r\n const chunk = new Uint8Array(ab);\r\n if (writer) {\r\n await writer.write(chunk);\r\n } else if ((writable as any).write) {\r\n await (writable as any).write(chunk);\r\n }\r\n } else if (type === 'progress') {\r\n onProgress?.(event.data);\r\n } else if (type === 'complete') {\r\n // write final chunk if present\r\n if (event.data.result) {\r\n const finalChunk = new Uint8Array(event.data.result);\r\n if (writer) await writer.write(finalChunk);\r\n else if ((writable as any).write) await (writable as any).write(finalChunk);\r\n }\r\n\r\n try {\r\n // Only close if we have a writer (WritableStream with getWriter)\r\n // FileSystemWritableFileStream should NOT be closed by us - caller owns it\r\n if (writer) {\r\n await writer.close();\r\n }\r\n } catch (e) {\r\n // ignore close errors\r\n }\r\n worker.terminate();\r\n resolve();\r\n } else if (type === 'error') {\r\n worker.terminate();\r\n reject(new Error(event.data.error));\r\n }\r\n };\r\n\r\n // Start worker\r\n worker.postMessage({ type: 'start', file, opts });\r\n } catch (err) {\r\n if (writer) {\r\n try { await writer.abort(err); } catch {}\r\n }\r\n worker.terminate();\r\n reject(err);\r\n }\r\n })();\r\n });\r\n}\r\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -35,6 +35,7 @@ type ConvertBuddyOptions = {
35
35
  maxMemoryMB?: number;
36
36
  csvConfig?: CsvConfig;
37
37
  xmlConfig?: XmlConfig;
38
+ transform?: TransformConfig;
38
39
  onProgress?: ProgressCallback;
39
40
  progressIntervalBytes?: number;
40
41
  };
@@ -43,6 +44,7 @@ type ConvertOptions = {
43
44
  outputFormat: Format;
44
45
  csvConfig?: CsvConfig;
45
46
  xmlConfig?: XmlConfig;
47
+ transform?: TransformConfig;
46
48
  onProgress?: ProgressCallback;
47
49
  };
48
50
  type CsvConfig = {
@@ -57,6 +59,34 @@ type XmlConfig = {
57
59
  includeAttributes?: boolean;
58
60
  expandEntities?: boolean;
59
61
  };
62
+ type TransformMode = "replace" | "augment";
63
+ type Coerce = {
64
+ type: "string";
65
+ } | {
66
+ type: "i64";
67
+ } | {
68
+ type: "f64";
69
+ } | {
70
+ type: "bool";
71
+ } | {
72
+ type: "timestamp_ms";
73
+ format?: "iso8601" | "unix_ms" | "unix_s";
74
+ };
75
+ type FieldMap = {
76
+ targetFieldName: string;
77
+ originFieldName?: string;
78
+ required?: boolean;
79
+ defaultValue?: string | number | boolean | null;
80
+ coerce?: Coerce;
81
+ compute?: string;
82
+ };
83
+ type TransformConfig = {
84
+ mode?: TransformMode;
85
+ fields: FieldMap[];
86
+ onMissingField?: "error" | "null" | "drop";
87
+ onMissingRequired?: "error" | "abort";
88
+ onCoerceError?: "error" | "null" | "dropRecord";
89
+ };
60
90
  type Stats = {
61
91
  bytesIn: number;
62
92
  bytesOut: number;
@@ -134,9 +164,7 @@ declare function detectFormat(input: DetectInput, opts?: DetectOptions): Promise
134
164
  declare function detectStructure(input: DetectInput, formatHint?: Format, opts?: DetectOptions): Promise<StructureDetection | null>;
135
165
  declare function detectCsvFieldsAndDelimiter(input: DetectInput, opts?: DetectOptions): Promise<CsvDetection | null>;
136
166
  declare function detectXmlElements(input: DetectInput, opts?: DetectOptions): Promise<XmlDetection | null>;
137
- declare function autoDetectConfig(sample: Uint8Array, opts?: {
138
- debug?: boolean;
139
- }): Promise<{
167
+ declare function autoDetectConfig(input: DetectInput, opts?: DetectOptions): Promise<{
140
168
  format: Format | "unknown";
141
169
  csvConfig?: CsvConfig;
142
170
  xmlConfig?: XmlConfig;
@@ -235,4 +263,4 @@ declare function getThreadingInfo(): {
235
263
  approach: string;
236
264
  };
237
265
 
238
- export { ConvertBuddy, type ConvertBuddyOptions, ConvertBuddyTransformStream, type ConvertOptions, type CsvConfig, type CsvDetection, type DetectInput, type DetectOptions, type Format, type JsonDetection, type NdjsonDetection, type ProgressCallback, type Stats, type StructureDetection, type XmlConfig, type XmlDetection, autoDetectConfig, convert, convertAny, convertAnyToString, convertToString, detectCsvFieldsAndDelimiter, detectFormat, detectStructure, detectXmlElements, getExtension, getFileTypeConfig, getMimeType, getOptimalThreadCount, getSuggestedFilename, getThreadingInfo, isWasmThreadingSupported };
266
+ export { type Coerce, ConvertBuddy, type ConvertBuddyOptions, ConvertBuddyTransformStream, type ConvertOptions, type CsvConfig, type CsvDetection, type DetectInput, type DetectOptions, type FieldMap, type Format, type JsonDetection, type NdjsonDetection, type ProgressCallback, type Stats, type StructureDetection, type TransformConfig, type TransformMode, type XmlConfig, type XmlDetection, autoDetectConfig, convert, convertAny, convertAnyToString, convertToString, detectCsvFieldsAndDelimiter, detectFormat, detectStructure, detectXmlElements, getExtension, getFileTypeConfig, getMimeType, getOptimalThreadCount, getSuggestedFilename, getThreadingInfo, isWasmThreadingSupported };