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 +415 -1
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +4 -3
- package/dist/browser.js.map +1 -1
- package/dist/index.d.ts +32 -4
- package/dist/index.js +64 -22
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +57 -19
- package/dist/node.js.map +1 -1
- package/dist/streaming-worker.js +8 -4
- package/dist/streaming-worker.js.map +1 -1
- package/dist/tests/edge-cases/csv-edge-cases.test.js +47 -0
- package/dist/tests/edge-cases/csv-edge-cases.test.js.map +1 -1
- package/dist/tests/edge-cases/detection.test.js +101 -1
- package/dist/tests/edge-cases/detection.test.js.map +1 -1
- package/dist/tests/edge-cases/node-helpers.test.js +34 -1
- package/dist/tests/edge-cases/node-helpers.test.js.map +1 -1
- package/dist/tests/edge-cases/roundtrip.test.js +147 -0
- package/dist/tests/edge-cases/roundtrip.test.js.map +1 -0
- package/dist/wasm/nodejs/convert_buddy.d.ts +16 -16
- package/dist/wasm/nodejs/convert_buddy.js +159 -65
- package/dist/wasm/nodejs/convert_buddy_bg.wasm +0 -0
- package/dist/wasm/nodejs/convert_buddy_bg.wasm.d.ts +3 -3
- package/dist/wasm/web/convert_buddy.d.ts +19 -19
- package/dist/wasm/web/convert_buddy.js +142 -65
- package/dist/wasm/web/convert_buddy_bg.wasm +0 -0
- package/dist/wasm/web/convert_buddy_bg.wasm.d.ts +3 -3
- package/package.json +15 -3
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
|
+
[](https://snyk.io/test/github/brunohanss/convert-buddy)
|
|
14
|
+
[](https://github.com/brunohanss/convert-buddy/actions)
|
|
15
|
+
[](https://codecov.io/gh/brunohanss/convert-buddy)
|
|
16
|
+
[](https://www.npmjs.com/package/convert-buddy-js)
|
|
17
|
+
[](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
|
|
20
|
-
export * from "./index
|
|
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
|
-
|
|
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 };
|
package/dist/browser.js.map
CHANGED
|
@@ -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":"AAQA,eAAsB,0BACpB,MACA,OAA8B,CAAC,GAC/B,YACqB;AACrB,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AAEtC,UAAM,SAAS,IAAI,OAAO,IAAI,IAAI,yBAAyB,YAAY,GAAG,GAAG,EAAE,MAAM,SAAS,CAAC;AAE/F,WAAO,YAAY,CAAC,UAAU;AAC5B,YAAM,EAAE,MAAM,GAAG,KAAK,IAAI,MAAM;AAChC,UAAI,SAAS,cAAc,YAAY;AACrC,mBAAW,IAAI;AAAA,MACjB,WAAW,SAAS,YAAY;AAC9B,eAAO,UAAU;AACjB,gBAAQ,IAAI,WAAW,KAAK,MAAM,CAAC;AAAA,MACrC,WAAW,SAAS,SAAS;AAC3B,eAAO,UAAU;AACjB,eAAO,IAAI,MAAM,KAAK,KAAK,CAAC;AAAA,MAC9B;AAAA,IACF;AAGA,WAAO,YAAY,EAAE,MAAM,SAAS,MAAM,KAAK,CAAC;AAAA,EAClD,CAAC;AACH;AACA,SAAS,cAA0E,kBAA6C,mBAAmB,cAAc,gBAAgB,sBAAsB,8BAA8B;AAErO,cAAc;AAuBd,eAAsB,QACpB,OACA,MACqB;AACrB,SAAO,eAAe,OAAO,IAAI;AACnC;AASA,eAAsB,gBACpB,OACA,MACiB;AACjB,SAAO,uBAAuB,OAAO,IAAI;AAC3C;AAsBA,eAAsB,oBACpB,MACA,OAA8B,CAAC,GACd;AACjB,QAAM,QAAQ,MAAM,YAAY,MAAM,IAAI;AAC1C,SAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACvC;AAgBA,eAAsB,YACpB,MACA,OAA8B,CAAC,GACV;AAErB,MAAI,aAAa,EAAE,GAAG,KAAK;AAE3B,MAAI,KAAK,gBAAgB,QAAQ;AAE/B,UAAM,aAAa,MAAM;AACzB,UAAM,aAAa,KAAK,MAAM,GAAG,UAAU;AAC3C,UAAM,eAAe,MAAM,WAAW,YAAY;AAClD,UAAM,SAAS,IAAI,WAAW,YAA2B;AAEzD,UAAM,WAAW,MAAM,iBAAiB,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC;AAErE,QAAI,SAAS,WAAW,WAAW;AACjC,iBAAW,cAAc,SAAS;AAElC,UAAI,SAAS,WAAW;AACtB,mBAAW,YAAY,KAAK,YAAY,EAAE,GAAG,SAAS,WAAW,GAAG,KAAK,UAAU,IAAI,SAAS;AAAA,MAClG;AAEA,UAAI,SAAS,WAAW;AACtB,mBAAW,YAAY,KAAK,YAAY,EAAE,GAAG,SAAS,WAAW,GAAG,KAAK,UAAU,IAAI,SAAS;AAAA,MAClG;AAEA,UAAI,KAAK,OAAO;AACd,gBAAQ,IAAI,yCAAyC,SAAS,MAAM;AAAA,MACtE;AAAA,IACF,OAAO;AACL,YAAM,IAAI,MAAM,4EAA4E;AAAA,IAC9F;AAAA,EACF;AAIA,MAAI,CAAC,WAAW,kBAAkB;AAChC,UAAM,WAAW,KAAK;AACtB,eAAW,mBAAmB,KAAK;AAAA,MACjC,MAAM;AAAA;AAAA,MACN,KAAK;AAAA,QACH,IAAI,OAAO;AAAA;AAAA,QACX,KAAK,MAAM,WAAW,EAAE;AAAA;AAAA,MAC1B;AAAA,IACF;AACA,QAAI,KAAK,OAAO;AACd,cAAQ,IAAI,0CAA0C,QAAQ,iBAAY,WAAW,gBAAgB,cAAc;AAAA,IACrH;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM,aAAa,OAAO,UAAU;AAGlD,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,SAAS,OAAO,UAAU;AAEhC,QAAM,SAAuB,CAAC;AAE9B,MAAI;AACF,WAAO,MAAM;AACX,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAE1C,UAAI,KAAM;AAEV,UAAI,MAAM,UAAU,GAAG;AACrB,cAAM,IAAI,MAAM,oBAAoB;AAAA,MACtC;AAEA,YAAM,SAAS,MAAM,KAAK,KAAK;AAC/B,UAAI,OAAO,SAAS,GAAG;AACrB,eAAO,KAAK,MAAM;AAAA,MACpB;AAAA,IACF;AAEA,UAAM,QAAQ,MAAM,OAAO;AAC3B,QAAI,MAAM,SAAS,GAAG;AACpB,aAAO,KAAK,KAAK;AAAA,IACnB;AAGA,UAAM,cAAc,OAAO,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,QAAQ,CAAC;AACvE,UAAM,SAAS,IAAI,WAAW,WAAW;AACzC,QAAI,SAAS;AACb,eAAW,SAAS,QAAQ;AAC1B,aAAO,IAAI,OAAO,MAAM;AACxB,gBAAU,MAAM;AAAA,IAClB;AAEA,QAAI,KAAK,SAAS;AAChB,cAAQ,IAAI,sCAAsC,MAAM,MAAM,CAAC;AAAA,IACjE;AAEA,WAAO;AAAA,EACT,SAAS,OAAO;AACd,WAAO,YAAY;AACnB,UAAM;AAAA,EACR;AACF;AAaA,eAAsB,kBACpB,WACA,gBACA,OAA8B,CAAC,GAChB;AACf,QAAM,SAAS,MAAM,YAAY,WAAW,IAAI;AAGhD,QAAM,OAAO,IAAI,KAAK,CAAC,IAAI,WAAW,MAAM,CAAC,GAAG,EAAE,MAAM,2BAA2B,CAAC;AACpF,QAAM,MAAM,IAAI,gBAAgB,IAAI;AAEpC,QAAM,OAAO,SAAS,cAAc,GAAG;AACvC,OAAK,OAAO;AACZ,OAAK,WAAW;AAChB,OAAK,MAAM,UAAU;AAErB,WAAS,KAAK,YAAY,IAAI;AAC9B,OAAK,MAAM;AAGX,aAAW,MAAM;AACf,aAAS,KAAK,YAAY,IAAI;AAC9B,QAAI,gBAAgB,GAAG;AAAA,EACzB,GAAG,GAAG;AACR;AAqBA,eAAsB,kBACpB,MACA,OAA8B,CAAC,GACM;AAErC,MAAI,aAAa,EAAE,GAAG,KAAK;AAE3B,MAAI,KAAK,gBAAgB,QAAQ;AAC/B,UAAM,aAAa,MAAM;AACzB,UAAM,aAAa,KAAK,MAAM,GAAG,UAAU;AAC3C,UAAM,eAAe,MAAM,WAAW,YAAY;AAClD,UAAM,SAAS,IAAI,WAAW,YAAY;AAE1C,UAAM,WAAW,MAAM,iBAAiB,QAAQ,EAAE,OAAO,KAAK,MAAM,CAAC;AAErE,QAAI,SAAS,WAAW,WAAW;AACjC,iBAAW,cAAc,SAAS;AAClC,UAAI,SAAS,WAAW;AACtB,mBAAW,YAAY,KAAK,YAAY,EAAE,GAAG,SAAS,WAAW,GAAG,KAAK,UAAU,IAAI,SAAS;AAAA,MAClG;AACA,UAAI,SAAS,WAAW;AACtB,mBAAW,YAAY,KAAK,YAAY,EAAE,GAAG,SAAS,WAAW,GAAG,KAAK,UAAU,IAAI,SAAS;AAAA,MAClG;AAAA,IACF,OAAO;AACL,YAAM,IAAI,MAAM,qCAAqC;AAAA,IACvD;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM,aAAa,OAAO,UAAU;AAClD,QAAM,cAAc,KAAK,OAAO;AAEhC,SAAO,IAAI,eAA2B;AAAA,IACpC,MAAM,MAAM,YAAY;AACtB,YAAM,SAAS,YAAY,UAAU;AAErC,UAAI;AACF,eAAO,MAAM;AACX,gBAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAE1C,cAAI,MAAM;AACR,kBAAM,QAAQ,MAAM,OAAO;AAC3B,gBAAI,MAAM,SAAS,GAAG;AACpB,yBAAW,QAAQ,KAAK;AAAA,YAC1B;AACA,uBAAW,MAAM;AACjB;AAAA,UACF;AAEA,cAAI,MAAM,UAAU,GAAG;AACrB,uBAAW,MAAM,IAAI,MAAM,oBAAoB,CAAC;AAChD;AAAA,UACF;AAEA,gBAAM,SAAS,MAAM,KAAK,KAAK;AAC/B,cAAI,OAAO,SAAS,GAAG;AACrB,uBAAW,QAAQ,MAAM;AAAA,UAC3B;AAAA,QACF;AAAA,MACF,SAAS,OAAO;AACd,mBAAW,MAAM,KAAK;AAAA,MACxB,UAAE;AACA,eAAO,YAAY;AAAA,MACrB;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAYA,eAAsB,kBACpB,MACA,MACqC;AACrC,SAAO,kBAAkB,MAAM;AAAA,IAC7B,GAAG;AAAA,IACH,aAAa;AAAA,EACf,CAAC;AACH;AA+BA,eAAsB,wBACpB,MACA,UACA,OAA8B,CAAC,GAChB;AACf,QAAM,eAAe,MAAM,kBAAkB,MAAM,IAAI;AACvD,QAAM,aAAa,OAAO,QAAQ;AACpC;AAeO,SAAS,8BAAuC;AACrD,SACE,OAAO,WAAW,eAClB,wBAAwB,UACxB,wBAAwB;AAE5B;AAeA,eAAsB,eACpB,MACA,OAEI,CAAC,GACU;AACf,MAAI,CAAC,4BAA4B,GAAG;AAElC,UAAM,WAAW,KAAK,iBAAiB;AACvC,UAAM,kBAAkB,MAAM,UAAU,IAAI;AAC5C;AAAA,EACF;AAEA,QAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAM,gBAAgB,KAAK,iBAAiB,aAAa,YAAY;AACrE,QAAM,QAAQ,kBAAkB,YAAY;AAE5C,MAAI;AACF,UAAM,aAAa,MAAO,OAAe,mBAAmB;AAAA,MAC1D;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,WAAW,MAAM,WAAW,eAAe;AACjD,UAAM,wBAAwB,MAAM,UAAU,IAAI;AAAA,EACpD,SAAS,OAAY;AAEnB,QAAI,OAAO,SAAS,cAAc;AAEhC;AAAA,IACF;AAEA,UAAM;AAAA,EACR;AACF;AAMA,eAAsB,oCACpB,MACA,UACA,OAA8B,CAAC,GAC/B,YACe;AACf,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,SAAS,IAAI,OAAO,IAAI,IAAI,yBAAyB,YAAY,GAAG,GAAG,EAAE,MAAM,SAAS,CAAC;AAC/F,QAAI,SAAyD;AAE7D,KAAC,YAAY;AACX,UAAI;AACF,YAAK,SAAiB,WAAW;AAC/B,mBAAU,SAAwC,UAAU;AAAA,QAC9D,OAAO;AAEL,mBAAS;AAAA,QACX;AAEA,eAAO,YAAY,OAAO,UAAU;AAClC,gBAAM,EAAE,KAAK,IAAI,MAAM;AACvB,cAAI,SAAS,QAAQ;AACnB,kBAAM,KAAkB,MAAM,KAAK;AACnC,kBAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,gBAAI,QAAQ;AACV,oBAAM,OAAO,MAAM,KAAK;AAAA,YAC1B,WAAY,SAAiB,OAAO;AAClC,oBAAO,SAAiB,MAAM,KAAK;AAAA,YACrC;AAAA,UACF,WAAW,SAAS,YAAY;AAC9B,yBAAa,MAAM,IAAI;AAAA,UACzB,WAAW,SAAS,YAAY;AAE9B,gBAAI,MAAM,KAAK,QAAQ;AACrB,oBAAM,aAAa,IAAI,WAAW,MAAM,KAAK,MAAM;AACnD,kBAAI,OAAQ,OAAM,OAAO,MAAM,UAAU;AAAA,uBAC/B,SAAiB,MAAO,OAAO,SAAiB,MAAM,UAAU;AAAA,YAC5E;AAEA,gBAAI;AAGF,kBAAI,QAAQ;AACV,sBAAM,OAAO,MAAM;AAAA,cACrB;AAAA,YACF,SAAS,GAAG;AAAA,YAEZ;AACA,mBAAO,UAAU;AACjB,oBAAQ;AAAA,UACV,WAAW,SAAS,SAAS;AAC3B,mBAAO,UAAU;AACjB,mBAAO,IAAI,MAAM,MAAM,KAAK,KAAK,CAAC;AAAA,UACpC;AAAA,QACF;AAGA,eAAO,YAAY,EAAE,MAAM,SAAS,MAAM,KAAK,CAAC;AAAA,MAClD,SAAS,KAAK;AACZ,YAAI,QAAQ;AACV,cAAI;AAAE,kBAAM,OAAO,MAAM,GAAG;AAAA,UAAG,QAAQ;AAAA,UAAC;AAAA,QAC1C;AACA,eAAO,UAAU;AACjB,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,GAAG;AAAA,EACL,CAAC;AACH;","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(
|
|
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 };
|