convert-buddy-js 0.10.18 → 0.11.1
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 +49 -4
- package/dist/browser-stream-controller.d.ts +107 -0
- package/dist/browser-stream-controller.js +226 -0
- package/dist/browser-stream-controller.js.map +1 -0
- package/dist/browser.d.ts +16 -195
- package/dist/browser.js +78 -339
- package/dist/browser.js.map +1 -1
- package/dist/index-BpUN6Tdz.d.ts +440 -0
- package/dist/index.d.ts +1 -271
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -1
- package/dist/node-stream-controller.d.ts +133 -0
- package/dist/node-stream-controller.js +274 -0
- package/dist/node-stream-controller.js.map +1 -0
- package/dist/node.d.ts +3 -2
- package/dist/node.js +2 -0
- package/dist/node.js.map +1 -1
- package/dist/stream-controller.d.ts +1 -0
- package/dist/stream-controller.js +277 -0
- package/dist/stream-controller.js.map +1 -0
- package/dist/streaming-worker.js +1 -1
- package/dist/streaming-worker.js.map +1 -1
- package/dist/tests/stream-controller.test.js +237 -0
- package/dist/tests/stream-controller.test.js.map +1 -0
- package/dist/wasm/nodejs/convert_buddy.d.ts +6 -0
- package/dist/wasm/nodejs/convert_buddy.js +58 -28
- package/dist/wasm/nodejs/convert_buddy_bg.wasm +0 -0
- package/dist/wasm/nodejs/convert_buddy_bg.wasm.d.ts +4 -3
- package/dist/wasm/web/convert_buddy.d.ts +10 -3
- package/dist/wasm/web/convert_buddy.js +55 -27
- package/dist/wasm/web/convert_buddy_bg.wasm +0 -0
- package/dist/wasm/web/convert_buddy_bg.wasm.d.ts +4 -3
- package/package.json +3 -5
- package/dist/browser-core.d.ts +0 -196
- package/dist/browser-core.js +0 -787
- package/dist/browser-core.js.map +0 -1
- package/dist/wasm/nodejs/convert_buddy.cjs +0 -712
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
class NDJSONSplitter {
|
|
2
|
+
buffer;
|
|
3
|
+
bufferUsed = 0;
|
|
4
|
+
bufferCapacity;
|
|
5
|
+
decoder = new TextDecoder("utf-8", { fatal: false });
|
|
6
|
+
constructor(initialCapacity = 64 * 1024) {
|
|
7
|
+
this.bufferCapacity = initialCapacity;
|
|
8
|
+
this.buffer = new Uint8Array(initialCapacity);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Push a chunk of bytes and get complete records
|
|
12
|
+
* @param chunk - Chunk of bytes to process
|
|
13
|
+
* @returns Array of parsed records
|
|
14
|
+
*/
|
|
15
|
+
push(chunk) {
|
|
16
|
+
if (this.bufferUsed + chunk.length > this.bufferCapacity) {
|
|
17
|
+
const newCapacity = Math.max(
|
|
18
|
+
this.bufferCapacity * 2,
|
|
19
|
+
this.bufferUsed + chunk.length
|
|
20
|
+
);
|
|
21
|
+
const newBuffer = new Uint8Array(newCapacity);
|
|
22
|
+
newBuffer.set(this.buffer.subarray(0, this.bufferUsed));
|
|
23
|
+
this.buffer = newBuffer;
|
|
24
|
+
this.bufferCapacity = newCapacity;
|
|
25
|
+
}
|
|
26
|
+
this.buffer.set(chunk, this.bufferUsed);
|
|
27
|
+
this.bufferUsed += chunk.length;
|
|
28
|
+
const records = [];
|
|
29
|
+
let startIdx = 0;
|
|
30
|
+
for (let i = 0; i < this.bufferUsed; i++) {
|
|
31
|
+
if (this.buffer[i] === 10) {
|
|
32
|
+
const line = this.buffer.subarray(startIdx, i);
|
|
33
|
+
startIdx = i + 1;
|
|
34
|
+
const text = this.decoder.decode(line).trim();
|
|
35
|
+
if (text.length > 0) {
|
|
36
|
+
try {
|
|
37
|
+
const record = JSON.parse(text);
|
|
38
|
+
records.push(record);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.warn("[NDJSONSplitter] Failed to parse line:", text.substring(0, 100), err);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (startIdx < this.bufferUsed) {
|
|
46
|
+
const remaining = this.bufferUsed - startIdx;
|
|
47
|
+
this.buffer.copyWithin(0, startIdx, this.bufferUsed);
|
|
48
|
+
this.bufferUsed = remaining;
|
|
49
|
+
} else {
|
|
50
|
+
this.bufferUsed = 0;
|
|
51
|
+
}
|
|
52
|
+
return records;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Flush remaining buffer and get final records
|
|
56
|
+
* @returns Array of parsed records from remaining buffer
|
|
57
|
+
*/
|
|
58
|
+
flush() {
|
|
59
|
+
if (this.bufferUsed === 0) {
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
const text = this.decoder.decode(this.buffer.subarray(0, this.bufferUsed)).trim();
|
|
63
|
+
this.bufferUsed = 0;
|
|
64
|
+
if (text.length === 0) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const record = JSON.parse(text);
|
|
69
|
+
return [record];
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.warn("[NDJSONSplitter] Failed to parse final buffer:", text.substring(0, 100), err);
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Reset the splitter state
|
|
77
|
+
*/
|
|
78
|
+
reset() {
|
|
79
|
+
this.buffer = new Uint8Array(0);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
class CSVSplitter {
|
|
83
|
+
buffer = new Uint8Array(0);
|
|
84
|
+
decoder = new TextDecoder("utf-8", { fatal: false });
|
|
85
|
+
/**
|
|
86
|
+
* Push a chunk of bytes and get complete CSV lines
|
|
87
|
+
*/
|
|
88
|
+
push(chunk) {
|
|
89
|
+
const newBuffer = new Uint8Array(this.buffer.length + chunk.length);
|
|
90
|
+
newBuffer.set(this.buffer);
|
|
91
|
+
newBuffer.set(chunk, this.buffer.length);
|
|
92
|
+
this.buffer = newBuffer;
|
|
93
|
+
const lines = [];
|
|
94
|
+
let startIdx = 0;
|
|
95
|
+
for (let i = 0; i < this.buffer.length; i++) {
|
|
96
|
+
if (this.buffer[i] === 10) {
|
|
97
|
+
const lineBytes = this.buffer.slice(startIdx, i);
|
|
98
|
+
startIdx = i + 1;
|
|
99
|
+
const text = this.decoder.decode(lineBytes).trim();
|
|
100
|
+
if (text.length > 0) {
|
|
101
|
+
lines.push(text);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (startIdx < this.buffer.length) {
|
|
106
|
+
this.buffer = this.buffer.slice(startIdx);
|
|
107
|
+
} else {
|
|
108
|
+
this.buffer = new Uint8Array(0);
|
|
109
|
+
}
|
|
110
|
+
return lines;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Flush remaining buffer and get final lines
|
|
114
|
+
*/
|
|
115
|
+
flush() {
|
|
116
|
+
if (this.buffer.length === 0) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const text = this.decoder.decode(this.buffer).trim();
|
|
120
|
+
this.buffer = new Uint8Array(0);
|
|
121
|
+
return text.length > 0 ? [text] : [];
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Reset the splitter state
|
|
125
|
+
*/
|
|
126
|
+
reset() {
|
|
127
|
+
this.buffer = new Uint8Array(0);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
class JSONSplitter {
|
|
131
|
+
buffer = new Uint8Array(0);
|
|
132
|
+
decoder = new TextDecoder("utf-8", { fatal: false });
|
|
133
|
+
yielded = false;
|
|
134
|
+
/**
|
|
135
|
+
* Push a chunk of bytes and accumulate
|
|
136
|
+
*/
|
|
137
|
+
push(chunk) {
|
|
138
|
+
const newBuffer = new Uint8Array(this.buffer.length + chunk.length);
|
|
139
|
+
newBuffer.set(this.buffer);
|
|
140
|
+
newBuffer.set(chunk, this.buffer.length);
|
|
141
|
+
this.buffer = newBuffer;
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Flush and parse complete JSON
|
|
146
|
+
*/
|
|
147
|
+
flush() {
|
|
148
|
+
if (this.yielded || this.buffer.length === 0) {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
const text = this.decoder.decode(this.buffer).trim();
|
|
152
|
+
this.buffer = new Uint8Array(0);
|
|
153
|
+
this.yielded = true;
|
|
154
|
+
if (text.length === 0) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const parsed = JSON.parse(text);
|
|
159
|
+
return Array.isArray(parsed) ? parsed : [parsed];
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.warn("[JSONSplitter] Failed to parse JSON:", err);
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Reset the splitter state
|
|
167
|
+
*/
|
|
168
|
+
reset() {
|
|
169
|
+
this.buffer = new Uint8Array(0);
|
|
170
|
+
this.yielded = false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
class XMLSplitter {
|
|
174
|
+
buffer = new Uint8Array(0);
|
|
175
|
+
decoder = new TextDecoder("utf-8", { fatal: false });
|
|
176
|
+
recordElement;
|
|
177
|
+
constructor(recordElement = "item") {
|
|
178
|
+
this.recordElement = recordElement;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Push a chunk of bytes and extract complete XML elements
|
|
182
|
+
*/
|
|
183
|
+
push(chunk) {
|
|
184
|
+
const newBuffer = new Uint8Array(this.buffer.length + chunk.length);
|
|
185
|
+
newBuffer.set(this.buffer);
|
|
186
|
+
newBuffer.set(chunk, this.buffer.length);
|
|
187
|
+
this.buffer = newBuffer;
|
|
188
|
+
const text = this.decoder.decode(this.buffer, { stream: true });
|
|
189
|
+
const records = [];
|
|
190
|
+
const openTag = `<${this.recordElement}>`;
|
|
191
|
+
const closeTag = `</${this.recordElement}>`;
|
|
192
|
+
let startIdx = 0;
|
|
193
|
+
let searchPos = 0;
|
|
194
|
+
while (true) {
|
|
195
|
+
const openIdx = text.indexOf(openTag, searchPos);
|
|
196
|
+
if (openIdx === -1) break;
|
|
197
|
+
const closeIdx = text.indexOf(closeTag, openIdx + openTag.length);
|
|
198
|
+
if (closeIdx === -1) break;
|
|
199
|
+
const elementText = text.substring(openIdx, closeIdx + closeTag.length);
|
|
200
|
+
try {
|
|
201
|
+
const record = this.xmlToJson(elementText);
|
|
202
|
+
records.push(record);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.warn("[XMLSplitter] Failed to parse XML element:", err);
|
|
205
|
+
}
|
|
206
|
+
searchPos = closeIdx + closeTag.length;
|
|
207
|
+
startIdx = searchPos;
|
|
208
|
+
}
|
|
209
|
+
const encoder = new TextEncoder();
|
|
210
|
+
this.buffer = encoder.encode(text.substring(startIdx));
|
|
211
|
+
return records;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Very basic XML to JSON converter (for demonstration)
|
|
215
|
+
* In production, use a proper XML parser library
|
|
216
|
+
*/
|
|
217
|
+
xmlToJson(xml) {
|
|
218
|
+
return { _xml: xml.trim() };
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Flush remaining buffer
|
|
222
|
+
*/
|
|
223
|
+
flush() {
|
|
224
|
+
if (this.buffer.length === 0) {
|
|
225
|
+
return [];
|
|
226
|
+
}
|
|
227
|
+
const text = this.decoder.decode(this.buffer);
|
|
228
|
+
this.buffer = new Uint8Array(0);
|
|
229
|
+
const records = [];
|
|
230
|
+
const openTag = `<${this.recordElement}>`;
|
|
231
|
+
const closeTag = `</${this.recordElement}>`;
|
|
232
|
+
let searchPos = 0;
|
|
233
|
+
while (true) {
|
|
234
|
+
const openIdx = text.indexOf(openTag, searchPos);
|
|
235
|
+
if (openIdx === -1) break;
|
|
236
|
+
const closeIdx = text.indexOf(closeTag, openIdx + openTag.length);
|
|
237
|
+
if (closeIdx === -1) break;
|
|
238
|
+
const elementText = text.substring(openIdx, closeIdx + closeTag.length);
|
|
239
|
+
try {
|
|
240
|
+
const record = this.xmlToJson(elementText);
|
|
241
|
+
records.push(record);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
console.warn("[XMLSplitter] Failed to parse XML element:", err);
|
|
244
|
+
}
|
|
245
|
+
searchPos = closeIdx + closeTag.length;
|
|
246
|
+
}
|
|
247
|
+
return records;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Reset the splitter state
|
|
251
|
+
*/
|
|
252
|
+
reset() {
|
|
253
|
+
this.buffer = new Uint8Array(0);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function createSplitter(outputFormat, config) {
|
|
257
|
+
switch (outputFormat) {
|
|
258
|
+
case "ndjson":
|
|
259
|
+
return new NDJSONSplitter();
|
|
260
|
+
case "csv":
|
|
261
|
+
return new CSVSplitter();
|
|
262
|
+
case "json":
|
|
263
|
+
return new JSONSplitter();
|
|
264
|
+
case "xml":
|
|
265
|
+
return new XMLSplitter(config?.xmlConfig?.recordElement || "item");
|
|
266
|
+
default:
|
|
267
|
+
return new NDJSONSplitter();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
export {
|
|
271
|
+
CSVSplitter,
|
|
272
|
+
JSONSplitter,
|
|
273
|
+
NDJSONSplitter,
|
|
274
|
+
XMLSplitter,
|
|
275
|
+
createSplitter
|
|
276
|
+
};
|
|
277
|
+
//# sourceMappingURL=stream-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/stream-controller.ts"],"sourcesContent":["/**\r\n * StreamController: High-level streaming API for ConvertBuddy\r\n * \r\n * Provides per-record callbacks, backpressure management, and pause/resume/abort\r\n * controls for both browser and Node.js environments.\r\n */\r\n\r\nimport type { ConvertBuddyOptions, Stats, Format } from \"./index.js\";\r\n\r\n/**\r\n * Options for StreamController\r\n */\r\nexport interface StreamControllerOptions extends Omit<ConvertBuddyOptions, 'onProgress'> {\r\n /**\r\n * Called with raw converted bytes before parsing into records.\r\n * Useful for capturing the actual output format (CSV, XML, etc.)\r\n */\r\n onData?: (bytes: Uint8Array) => void;\r\n\r\n /**\r\n * Called for each parsed record (object) from the output stream.\r\n * If the callback returns a Promise, the controller will await it before processing the next record.\r\n * Return false to pause the stream (can resume later with resume()).\r\n */\r\n onRecord?: (record: any, index: number) => void | Promise<void> | boolean | Promise<boolean>;\r\n\r\n /**\r\n * Called periodically with statistics and record count.\r\n * Use progressBatchSize to control how often this is called (every N records).\r\n * This is different from ConvertBuddyOptions.onProgress which only provides stats.\r\n */\r\n onStreamProgress?: (stats: Stats, recordCount: number) => void;\r\n\r\n /**\r\n * Number of records to process before triggering onStreamProgress callback.\r\n * Default: 1000\r\n */\r\n progressBatchSize?: number;\r\n\r\n /**\r\n * Auto-pause the stream while onRecord callback is executing.\r\n * Default: true\r\n */\r\n autoPause?: boolean;\r\n\r\n /**\r\n * Output format for conversion. Recommended: \"ndjson\" for per-record streaming.\r\n * Default: \"ndjson\"\r\n */\r\n outputFormat?: Format;\r\n}\r\n\r\n/**\r\n * Represents a parsed record with metadata\r\n */\r\nexport interface ParsedRecord {\r\n record: any;\r\n index: number;\r\n}\r\n\r\n/**\r\n * NDJSONSplitter - Splits a stream of bytes into complete NDJSON lines\r\n * \r\n * Handles partial lines across chunk boundaries and yields complete JSON objects.\r\n * Optimized with pre-allocated growing buffer to avoid O(n²) allocations.\r\n */\r\nexport class NDJSONSplitter {\r\n private buffer: Uint8Array;\r\n private bufferUsed = 0;\r\n private bufferCapacity: number;\r\n private decoder = new TextDecoder(\"utf-8\", { fatal: false });\r\n\r\n constructor(initialCapacity = 64 * 1024) {\r\n this.bufferCapacity = initialCapacity;\r\n this.buffer = new Uint8Array(initialCapacity);\r\n }\r\n\r\n /**\r\n * Push a chunk of bytes and get complete records\r\n * @param chunk - Chunk of bytes to process\r\n * @returns Array of parsed records\r\n */\r\n push(chunk: Uint8Array): any[] {\r\n // Grow buffer only when needed, doubling capacity\r\n if (this.bufferUsed + chunk.length > this.bufferCapacity) {\r\n const newCapacity = Math.max(\r\n this.bufferCapacity * 2,\r\n this.bufferUsed + chunk.length\r\n );\r\n const newBuffer = new Uint8Array(newCapacity);\r\n newBuffer.set(this.buffer.subarray(0, this.bufferUsed));\r\n this.buffer = newBuffer;\r\n this.bufferCapacity = newCapacity;\r\n }\r\n\r\n // Append chunk to buffer\r\n this.buffer.set(chunk, this.bufferUsed);\r\n this.bufferUsed += chunk.length;\r\n\r\n const records: any[] = [];\r\n let startIdx = 0;\r\n\r\n // Find complete lines (ending with \\n) in the used portion\r\n for (let i = 0; i < this.bufferUsed; i++) {\r\n if (this.buffer[i] === 0x0a) { // \\n\r\n // Extract line from buffer slice\r\n const line = this.buffer.subarray(startIdx, i);\r\n startIdx = i + 1;\r\n\r\n // Decode and parse\r\n const text = this.decoder.decode(line).trim();\r\n if (text.length > 0) {\r\n try {\r\n const record = JSON.parse(text);\r\n records.push(record);\r\n } catch (err) {\r\n // Skip invalid JSON lines\r\n console.warn(\"[NDJSONSplitter] Failed to parse line:\", text.substring(0, 100), err);\r\n }\r\n }\r\n }\r\n }\r\n\r\n // Keep remaining incomplete data in buffer\r\n if (startIdx < this.bufferUsed) {\r\n const remaining = this.bufferUsed - startIdx;\r\n // Move remaining data to start of buffer\r\n this.buffer.copyWithin(0, startIdx, this.bufferUsed);\r\n this.bufferUsed = remaining;\r\n } else {\r\n this.bufferUsed = 0;\r\n }\r\n\r\n return records;\r\n }\r\n\r\n /**\r\n * Flush remaining buffer and get final records\r\n * @returns Array of parsed records from remaining buffer\r\n */\r\n flush(): any[] {\r\n if (this.bufferUsed === 0) {\r\n return [];\r\n }\r\n\r\n const text = this.decoder.decode(this.buffer.subarray(0, this.bufferUsed)).trim();\r\n this.bufferUsed = 0;\r\n\r\n if (text.length === 0) {\r\n return [];\r\n }\r\n\r\n try {\r\n const record = JSON.parse(text);\r\n return [record];\r\n } catch (err) {\r\n console.warn(\"[NDJSONSplitter] Failed to parse final buffer:\", text.substring(0, 100), err);\r\n return [];\r\n }\r\n }\r\n\r\n /**\r\n * Reset the splitter state\r\n */\r\n reset(): void {\r\n this.buffer = new Uint8Array(0);\r\n }\r\n}\r\n\r\n/**\r\n * CSVSplitter - Splits CSV output into lines\r\n * \r\n * For CSV output, we yield each line as a string (header + data rows).\r\n */\r\nexport class CSVSplitter {\r\n private buffer: Uint8Array = new Uint8Array(0);\r\n private decoder = new TextDecoder(\"utf-8\", { fatal: false });\r\n\r\n /**\r\n * Push a chunk of bytes and get complete CSV lines\r\n */\r\n push(chunk: Uint8Array): string[] {\r\n // Append new chunk to buffer\r\n const newBuffer = new Uint8Array(this.buffer.length + chunk.length);\r\n newBuffer.set(this.buffer);\r\n newBuffer.set(chunk, this.buffer.length);\r\n this.buffer = newBuffer;\r\n\r\n const lines: string[] = [];\r\n let startIdx = 0;\r\n\r\n // Find complete lines (ending with \\n)\r\n for (let i = 0; i < this.buffer.length; i++) {\r\n if (this.buffer[i] === 0x0a) { // \\n\r\n // Extract line\r\n const lineBytes = this.buffer.slice(startIdx, i);\r\n startIdx = i + 1;\r\n\r\n // Decode\r\n const text = this.decoder.decode(lineBytes).trim();\r\n if (text.length > 0) {\r\n lines.push(text);\r\n }\r\n }\r\n }\r\n\r\n // Keep remaining incomplete data in buffer\r\n if (startIdx < this.buffer.length) {\r\n this.buffer = this.buffer.slice(startIdx);\r\n } else {\r\n this.buffer = new Uint8Array(0);\r\n }\r\n\r\n return lines;\r\n }\r\n\r\n /**\r\n * Flush remaining buffer and get final lines\r\n */\r\n flush(): string[] {\r\n if (this.buffer.length === 0) {\r\n return [];\r\n }\r\n\r\n const text = this.decoder.decode(this.buffer).trim();\r\n this.buffer = new Uint8Array(0);\r\n\r\n return text.length > 0 ? [text] : [];\r\n }\r\n\r\n /**\r\n * Reset the splitter state\r\n */\r\n reset(): void {\r\n this.buffer = new Uint8Array(0);\r\n }\r\n}\r\n\r\n/**\r\n * JSONSplitter - Handles JSON array output\r\n * \r\n * For JSON output, we expect an array of objects. We'll parse the complete\r\n * JSON once and yield all records.\r\n */\r\nexport class JSONSplitter {\r\n private buffer: Uint8Array = new Uint8Array(0);\r\n private decoder = new TextDecoder(\"utf-8\", { fatal: false });\r\n private yielded = false;\r\n\r\n /**\r\n * Push a chunk of bytes and accumulate\r\n */\r\n push(chunk: Uint8Array): any[] {\r\n // Append to buffer\r\n const newBuffer = new Uint8Array(this.buffer.length + chunk.length);\r\n newBuffer.set(this.buffer);\r\n newBuffer.set(chunk, this.buffer.length);\r\n this.buffer = newBuffer;\r\n\r\n // JSON needs to be complete, so we don't yield until flush\r\n return [];\r\n }\r\n\r\n /**\r\n * Flush and parse complete JSON\r\n */\r\n flush(): any[] {\r\n if (this.yielded || this.buffer.length === 0) {\r\n return [];\r\n }\r\n\r\n const text = this.decoder.decode(this.buffer).trim();\r\n this.buffer = new Uint8Array(0);\r\n this.yielded = true;\r\n\r\n if (text.length === 0) {\r\n return [];\r\n }\r\n\r\n try {\r\n const parsed = JSON.parse(text);\r\n // If it's an array, return all items; otherwise return single item\r\n return Array.isArray(parsed) ? parsed : [parsed];\r\n } catch (err) {\r\n console.warn(\"[JSONSplitter] Failed to parse JSON:\", err);\r\n return [];\r\n }\r\n }\r\n\r\n /**\r\n * Reset the splitter state\r\n */\r\n reset(): void {\r\n this.buffer = new Uint8Array(0);\r\n this.yielded = false;\r\n }\r\n}\r\n\r\n/**\r\n * XMLSplitter - Handles XML output by splitting on record elements\r\n * \r\n * This is a simplified splitter that looks for complete XML elements.\r\n * For more robust XML parsing, consider using a proper XML parser.\r\n */\r\nexport class XMLSplitter {\r\n private buffer: Uint8Array = new Uint8Array(0);\r\n private decoder = new TextDecoder(\"utf-8\", { fatal: false });\r\n private recordElement: string;\r\n\r\n constructor(recordElement: string = \"item\") {\r\n this.recordElement = recordElement;\r\n }\r\n\r\n /**\r\n * Push a chunk of bytes and extract complete XML elements\r\n */\r\n push(chunk: Uint8Array): any[] {\r\n // Append to buffer\r\n const newBuffer = new Uint8Array(this.buffer.length + chunk.length);\r\n newBuffer.set(this.buffer);\r\n newBuffer.set(chunk, this.buffer.length);\r\n this.buffer = newBuffer;\r\n\r\n const text = this.decoder.decode(this.buffer, { stream: true });\r\n const records: any[] = [];\r\n\r\n const openTag = `<${this.recordElement}>`;\r\n const closeTag = `</${this.recordElement}>`;\r\n\r\n let startIdx = 0;\r\n let searchPos = 0;\r\n\r\n while (true) {\r\n const openIdx = text.indexOf(openTag, searchPos);\r\n if (openIdx === -1) break;\r\n\r\n const closeIdx = text.indexOf(closeTag, openIdx + openTag.length);\r\n if (closeIdx === -1) break;\r\n\r\n // Extract element\r\n const elementText = text.substring(openIdx, closeIdx + closeTag.length);\r\n \r\n // Simple XML to JSON conversion (very basic)\r\n // In production, use a proper XML parser\r\n try {\r\n const record = this.xmlToJson(elementText);\r\n records.push(record);\r\n } catch (err) {\r\n console.warn(\"[XMLSplitter] Failed to parse XML element:\", err);\r\n }\r\n\r\n searchPos = closeIdx + closeTag.length;\r\n startIdx = searchPos;\r\n }\r\n\r\n // Keep remaining data in buffer\r\n const encoder = new TextEncoder();\r\n this.buffer = encoder.encode(text.substring(startIdx));\r\n\r\n return records;\r\n }\r\n\r\n /**\r\n * Very basic XML to JSON converter (for demonstration)\r\n * In production, use a proper XML parser library\r\n */\r\n private xmlToJson(xml: string): any {\r\n // This is a placeholder - in reality, you'd use a proper XML parser\r\n // For now, return a simple object with the raw XML\r\n return { _xml: xml.trim() };\r\n }\r\n\r\n /**\r\n * Flush remaining buffer\r\n */\r\n flush(): any[] {\r\n // Try to parse any remaining complete elements\r\n if (this.buffer.length === 0) {\r\n return [];\r\n }\r\n\r\n const text = this.decoder.decode(this.buffer);\r\n this.buffer = new Uint8Array(0);\r\n\r\n // Try to extract any remaining complete elements\r\n const records: any[] = [];\r\n const openTag = `<${this.recordElement}>`;\r\n const closeTag = `</${this.recordElement}>`;\r\n\r\n let searchPos = 0;\r\n while (true) {\r\n const openIdx = text.indexOf(openTag, searchPos);\r\n if (openIdx === -1) break;\r\n\r\n const closeIdx = text.indexOf(closeTag, openIdx + openTag.length);\r\n if (closeIdx === -1) break;\r\n\r\n const elementText = text.substring(openIdx, closeIdx + closeTag.length);\r\n \r\n try {\r\n const record = this.xmlToJson(elementText);\r\n records.push(record);\r\n } catch (err) {\r\n console.warn(\"[XMLSplitter] Failed to parse XML element:\", err);\r\n }\r\n\r\n searchPos = closeIdx + closeTag.length;\r\n }\r\n\r\n return records;\r\n }\r\n\r\n /**\r\n * Reset the splitter state\r\n */\r\n reset(): void {\r\n this.buffer = new Uint8Array(0);\r\n }\r\n}\r\n\r\n/**\r\n * Factory function to create appropriate splitter based on output format\r\n */\r\nexport function createSplitter(outputFormat: Format, config?: any): NDJSONSplitter | CSVSplitter | JSONSplitter | XMLSplitter {\r\n switch (outputFormat) {\r\n case \"ndjson\":\r\n return new NDJSONSplitter();\r\n case \"csv\":\r\n return new CSVSplitter();\r\n case \"json\":\r\n return new JSONSplitter();\r\n case \"xml\":\r\n return new XMLSplitter(config?.xmlConfig?.recordElement || \"item\");\r\n default:\r\n return new NDJSONSplitter();\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/streaming-worker.js
CHANGED
|
@@ -11,7 +11,7 @@ self.onmessage = async (event) => {
|
|
|
11
11
|
const { type, file, opts } = event.data;
|
|
12
12
|
if (type === "start") {
|
|
13
13
|
try {
|
|
14
|
-
const mod = await import("./
|
|
14
|
+
const mod = await import("./index");
|
|
15
15
|
const { ConvertBuddy } = mod;
|
|
16
16
|
buddy = await ConvertBuddy.create(opts);
|
|
17
17
|
fileSize = file.size;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/streaming-worker.js"],"sourcesContent":["// Web Worker for streaming file conversion using convert-buddy-js\r\n// This worker processes a File/Blob in chunks and posts progress/results\r\n\r\nlet buddy = null;\r\nlet totalBytesRead = 0;\r\nlet totalBytesWritten = 0;\r\nlet recordsProcessed = 0;\r\nlet fileSize = 0;\r\nlet chunkSize = 1024 * 1024; // 1MB default\r\nlet lastProgressPost = 0;\r\nconst PROGRESS_THROTTLE_MS = 150; // post progress at most ~6-7 times/sec\r\nconst utf8Decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true });\r\n\r\nself.onmessage = async (event) => {\r\n const { type, file, opts } = event.data;\r\n\r\n if (type === 'start') {\r\n try {\r\n // Dynamically import
|
|
1
|
+
{"version":3,"sources":["../src/streaming-worker.js"],"sourcesContent":["// Web Worker for streaming file conversion using convert-buddy-js\r\n// This worker processes a File/Blob in chunks and posts progress/results\r\n\r\nlet buddy = null;\r\nlet totalBytesRead = 0;\r\nlet totalBytesWritten = 0;\r\nlet recordsProcessed = 0;\r\nlet fileSize = 0;\r\nlet chunkSize = 1024 * 1024; // 1MB default\r\nlet lastProgressPost = 0;\r\nconst PROGRESS_THROTTLE_MS = 150; // post progress at most ~6-7 times/sec\r\nconst utf8Decoder = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true });\r\n\r\nself.onmessage = async (event) => {\r\n const { type, file, opts } = event.data;\r\n\r\n if (type === 'start') {\r\n try {\r\n // Dynamically import main module \r\n const mod = await import('./index');\r\n const { ConvertBuddy } = mod;\r\n buddy = await ConvertBuddy.create(opts);\r\n fileSize = file.size;\r\n totalBytesRead = 0;\r\n totalBytesWritten = 0;\r\n recordsProcessed = 0;\r\n chunkSize = opts.chunkTargetBytes || chunkSize;\r\n\r\n let offset = 0;\r\n while (offset < fileSize) {\r\n const chunkEnd = Math.min(offset + chunkSize, fileSize);\r\n const chunk = file.slice(offset, chunkEnd);\r\n const chunkData = await chunk.arrayBuffer();\r\n const uint8Array = new Uint8Array(chunkData);\r\n totalBytesRead += uint8Array.length;\r\n\r\n const output = buddy.push(uint8Array);\r\n if (output && output.length > 0) {\r\n totalBytesWritten += output.length;\r\n\r\n // Send processed data back as transferable ArrayBuffer to avoid copy\r\n const ab = output.buffer.slice(output.byteOffset, output.byteOffset + output.byteLength);\r\n self.postMessage({ type: 'data', chunk: ab }, [ab]);\r\n\r\n // Count records (approximate) on worker side (keep for telemetry)\r\n try {\r\n const chunkText = utf8Decoder.decode(output);\r\n recordsProcessed += chunkText.split('\\n').length - 1;\r\n } catch (e) {\r\n // ignore decoding failures for counting\r\n }\r\n }\r\n\r\n const now = Date.now();\r\n if (now - lastProgressPost > PROGRESS_THROTTLE_MS) {\r\n lastProgressPost = now;\r\n self.postMessage({\r\n type: 'progress',\r\n bytesRead: totalBytesRead,\r\n bytesWritten: totalBytesWritten,\r\n percentComplete: (totalBytesRead / fileSize) * 100,\r\n recordsProcessed,\r\n });\r\n }\r\n\r\n offset = chunkEnd;\r\n }\r\n // Finalize\r\n const final = buddy.finish();\r\n totalBytesWritten += final.length;\r\n try {\r\n const finalText = utf8Decoder.decode(final);\r\n recordsProcessed += finalText.split('\\n').length - 1;\r\n } catch (e) {\r\n // ignore decoding failures for counting\r\n }\r\n self.postMessage({\r\n type: 'complete',\r\n bytesRead: totalBytesRead,\r\n bytesWritten: totalBytesWritten,\r\n recordsProcessed,\r\n result: final,\r\n });\r\n } catch (error) {\r\n self.postMessage({ type: 'error', error: error.message });\r\n }\r\n }\r\n};\r\n"],"mappings":"AAGA,IAAI,QAAQ;AACZ,IAAI,iBAAiB;AACrB,IAAI,oBAAoB;AACxB,IAAI,mBAAmB;AACvB,IAAI,WAAW;AACf,IAAI,YAAY,OAAO;AACvB,IAAI,mBAAmB;AACvB,MAAM,uBAAuB;AAC7B,MAAM,cAAc,IAAI,YAAY,SAAS,EAAE,OAAO,MAAM,WAAW,KAAK,CAAC;AAE7E,KAAK,YAAY,OAAO,UAAU;AAChC,QAAM,EAAE,MAAM,MAAM,KAAK,IAAI,MAAM;AAEnC,MAAI,SAAS,SAAS;AACpB,QAAI;AAEF,YAAM,MAAM,MAAM,OAAO,SAAS;AAClC,YAAM,EAAE,aAAa,IAAI;AACzB,cAAQ,MAAM,aAAa,OAAO,IAAI;AACtC,iBAAW,KAAK;AAChB,uBAAiB;AACjB,0BAAoB;AACpB,yBAAmB;AACnB,kBAAY,KAAK,oBAAoB;AAErC,UAAI,SAAS;AACb,aAAO,SAAS,UAAU;AACxB,cAAM,WAAW,KAAK,IAAI,SAAS,WAAW,QAAQ;AACtD,cAAM,QAAQ,KAAK,MAAM,QAAQ,QAAQ;AACzC,cAAM,YAAY,MAAM,MAAM,YAAY;AAC1C,cAAM,aAAa,IAAI,WAAW,SAAS;AAC3C,0BAAkB,WAAW;AAE7B,cAAM,SAAS,MAAM,KAAK,UAAU;AACpC,YAAI,UAAU,OAAO,SAAS,GAAG;AAC/B,+BAAqB,OAAO;AAG5B,gBAAM,KAAK,OAAO,OAAO,MAAM,OAAO,YAAY,OAAO,aAAa,OAAO,UAAU;AACvF,eAAK,YAAY,EAAE,MAAM,QAAQ,OAAO,GAAG,GAAG,CAAC,EAAE,CAAC;AAGlD,cAAI;AACF,kBAAM,YAAY,YAAY,OAAO,MAAM;AAC3C,gCAAoB,UAAU,MAAM,IAAI,EAAE,SAAS;AAAA,UACrD,SAAS,GAAG;AAAA,UAEZ;AAAA,QACF;AAEA,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,MAAM,mBAAmB,sBAAsB;AACjD,6BAAmB;AACnB,eAAK,YAAY;AAAA,YACf,MAAM;AAAA,YACN,WAAW;AAAA,YACX,cAAc;AAAA,YACd,iBAAkB,iBAAiB,WAAY;AAAA,YAC/C;AAAA,UACF,CAAC;AAAA,QACH;AAEA,iBAAS;AAAA,MACX;AAEA,YAAM,QAAQ,MAAM,OAAO;AAC3B,2BAAqB,MAAM;AAC3B,UAAI;AACF,cAAM,YAAY,YAAY,OAAO,KAAK;AAC1C,4BAAoB,UAAU,MAAM,IAAI,EAAE,SAAS;AAAA,MACrD,SAAS,GAAG;AAAA,MAEZ;AACA,WAAK,YAAY;AAAA,QACf,MAAM;AAAA,QACN,WAAW;AAAA,QACX,cAAc;AAAA,QACd;AAAA,QACA,QAAQ;AAAA,MACV,CAAC;AAAA,IACH,SAAS,OAAO;AACd,WAAK,YAAY,EAAE,MAAM,SAAS,OAAO,MAAM,QAAQ,CAAC;AAAA,IAC1D;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import { strict as assert } from "node:assert";
|
|
3
|
+
import { NodeStreamController } from "../node-stream-controller.js";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
describe("NodeStreamController", () => {
|
|
6
|
+
it("should process CSV records with onRecord callback", async () => {
|
|
7
|
+
const csvData = `name,age,city
|
|
8
|
+
Alice,30,NYC
|
|
9
|
+
Bob,25,LA
|
|
10
|
+
Charlie,35,Chicago`;
|
|
11
|
+
const records = [];
|
|
12
|
+
const controller = new NodeStreamController({
|
|
13
|
+
inputFormat: "csv",
|
|
14
|
+
outputFormat: "ndjson",
|
|
15
|
+
onRecord: (record, index) => {
|
|
16
|
+
records.push(record);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
await controller.process(csvData);
|
|
20
|
+
assert.equal(records.length, 3);
|
|
21
|
+
assert.equal(records[0].name, "Alice");
|
|
22
|
+
assert.equal(records[0].age, "30");
|
|
23
|
+
assert.equal(records[1].name, "Bob");
|
|
24
|
+
assert.equal(records[2].name, "Charlie");
|
|
25
|
+
});
|
|
26
|
+
it("should handle async onRecord callback with backpressure", async () => {
|
|
27
|
+
const csvData = `id,value
|
|
28
|
+
1,100
|
|
29
|
+
2,200
|
|
30
|
+
3,300`;
|
|
31
|
+
const records = [];
|
|
32
|
+
const delays = [];
|
|
33
|
+
const controller = new NodeStreamController({
|
|
34
|
+
inputFormat: "csv",
|
|
35
|
+
outputFormat: "ndjson",
|
|
36
|
+
autoPause: true,
|
|
37
|
+
onRecord: async (record, index) => {
|
|
38
|
+
const start = Date.now();
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
40
|
+
const delay = Date.now() - start;
|
|
41
|
+
delays.push(delay);
|
|
42
|
+
records.push(record);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
await controller.process(csvData);
|
|
46
|
+
assert.equal(records.length, 3);
|
|
47
|
+
assert.equal(records[0].id, "1");
|
|
48
|
+
assert.ok(delays.every((d) => d >= 9));
|
|
49
|
+
});
|
|
50
|
+
it("should track progress with progressBatchSize", async () => {
|
|
51
|
+
const ndjsonLines = [];
|
|
52
|
+
for (let i = 0; i < 50; i++) {
|
|
53
|
+
ndjsonLines.push(JSON.stringify({ id: i, value: i * 10 }));
|
|
54
|
+
}
|
|
55
|
+
const ndjsonData = ndjsonLines.join("\n");
|
|
56
|
+
let progressCallCount = 0;
|
|
57
|
+
let lastRecordCount = 0;
|
|
58
|
+
const controller = new NodeStreamController({
|
|
59
|
+
inputFormat: "ndjson",
|
|
60
|
+
outputFormat: "json",
|
|
61
|
+
progressBatchSize: 10,
|
|
62
|
+
onStreamProgress: (stats, recordCount) => {
|
|
63
|
+
progressCallCount++;
|
|
64
|
+
lastRecordCount = recordCount;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
await controller.process(ndjsonData);
|
|
68
|
+
assert.ok(progressCallCount >= 4);
|
|
69
|
+
assert.equal(lastRecordCount, 50);
|
|
70
|
+
});
|
|
71
|
+
it("should support pause and resume", async () => {
|
|
72
|
+
const csvData = `num
|
|
73
|
+
1
|
|
74
|
+
2
|
|
75
|
+
3
|
|
76
|
+
4
|
|
77
|
+
5`;
|
|
78
|
+
const records = [];
|
|
79
|
+
let pauseCount = 0;
|
|
80
|
+
const controller = new NodeStreamController({
|
|
81
|
+
inputFormat: "csv",
|
|
82
|
+
outputFormat: "ndjson",
|
|
83
|
+
autoPause: false,
|
|
84
|
+
onRecord: (record, index) => {
|
|
85
|
+
records.push(record);
|
|
86
|
+
if (index === 2 && pauseCount === 0) {
|
|
87
|
+
pauseCount++;
|
|
88
|
+
controller.pause();
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
controller.resume();
|
|
91
|
+
}, 100);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
const start = Date.now();
|
|
97
|
+
await controller.process(csvData);
|
|
98
|
+
const duration = Date.now() - start;
|
|
99
|
+
assert.equal(records.length, 5);
|
|
100
|
+
assert.ok(duration >= 90);
|
|
101
|
+
assert.equal(pauseCount, 1);
|
|
102
|
+
});
|
|
103
|
+
it("should support abort", async () => {
|
|
104
|
+
const csvData = `num
|
|
105
|
+
${Array.from({ length: 1e3 }, (_, i) => i).join("\n")}`;
|
|
106
|
+
const records = [];
|
|
107
|
+
const controller = new NodeStreamController({
|
|
108
|
+
inputFormat: "csv",
|
|
109
|
+
outputFormat: "ndjson",
|
|
110
|
+
onRecord: (record, index) => {
|
|
111
|
+
records.push(record);
|
|
112
|
+
if (index === 10) {
|
|
113
|
+
controller.abort();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
await controller.process(csvData);
|
|
118
|
+
assert.ok(records.length <= 20);
|
|
119
|
+
});
|
|
120
|
+
it("should work with Node.js Readable stream", async () => {
|
|
121
|
+
const csvData = `x,y
|
|
122
|
+
1,2
|
|
123
|
+
3,4
|
|
124
|
+
5,6`;
|
|
125
|
+
const stream = Readable.from([csvData]);
|
|
126
|
+
const records = [];
|
|
127
|
+
const controller = new NodeStreamController({
|
|
128
|
+
inputFormat: "csv",
|
|
129
|
+
outputFormat: "ndjson",
|
|
130
|
+
onRecord: (record) => {
|
|
131
|
+
records.push(record);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
await controller.process(stream);
|
|
135
|
+
assert.equal(records.length, 3);
|
|
136
|
+
assert.equal(records[0].x, "1");
|
|
137
|
+
assert.equal(records[0].y, "2");
|
|
138
|
+
});
|
|
139
|
+
it("should handle CSV output format with field names", async () => {
|
|
140
|
+
const jsonData = JSON.stringify([
|
|
141
|
+
{ name: "Alice", score: 95 },
|
|
142
|
+
{ name: "Bob", score: 87 }
|
|
143
|
+
]);
|
|
144
|
+
const records = [];
|
|
145
|
+
const controller = new NodeStreamController({
|
|
146
|
+
inputFormat: "json",
|
|
147
|
+
outputFormat: "csv",
|
|
148
|
+
onRecord: (record) => {
|
|
149
|
+
records.push(record);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
await controller.process(jsonData);
|
|
153
|
+
assert.equal(records.length, 2);
|
|
154
|
+
assert.ok("name" in records[0]);
|
|
155
|
+
assert.ok("score" in records[0]);
|
|
156
|
+
assert.equal(records[0].name, "Alice");
|
|
157
|
+
assert.equal(records[1].name, "Bob");
|
|
158
|
+
});
|
|
159
|
+
it("should handle NDJSON input and output", async () => {
|
|
160
|
+
const ndjsonData = `{"id":1,"name":"Alice"}
|
|
161
|
+
{"id":2,"name":"Bob"}
|
|
162
|
+
{"id":3,"name":"Charlie"}`;
|
|
163
|
+
const records = [];
|
|
164
|
+
const controller = new NodeStreamController({
|
|
165
|
+
inputFormat: "ndjson",
|
|
166
|
+
outputFormat: "ndjson",
|
|
167
|
+
onRecord: (record) => {
|
|
168
|
+
records.push(record);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
await controller.process(ndjsonData);
|
|
172
|
+
assert.equal(records.length, 3);
|
|
173
|
+
assert.equal(records[0].name, "Alice");
|
|
174
|
+
assert.equal(records[1].id, 2);
|
|
175
|
+
});
|
|
176
|
+
it("should handle JSON array output", async () => {
|
|
177
|
+
const csvData = `key,val
|
|
178
|
+
a,1
|
|
179
|
+
b,2`;
|
|
180
|
+
const records = [];
|
|
181
|
+
const controller = new NodeStreamController({
|
|
182
|
+
inputFormat: "csv",
|
|
183
|
+
outputFormat: "json",
|
|
184
|
+
onRecord: (record) => {
|
|
185
|
+
records.push(record);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
await controller.process(csvData);
|
|
189
|
+
assert.equal(records.length, 2);
|
|
190
|
+
assert.equal(records[0].key, "a");
|
|
191
|
+
assert.equal(records[1].val, "2");
|
|
192
|
+
});
|
|
193
|
+
it("should provide stats", async () => {
|
|
194
|
+
const csvData = `col1,col2
|
|
195
|
+
value1,value2
|
|
196
|
+
value3,value4`;
|
|
197
|
+
const controller = new NodeStreamController({
|
|
198
|
+
inputFormat: "csv",
|
|
199
|
+
outputFormat: "ndjson",
|
|
200
|
+
onRecord: () => {
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
await controller.process(csvData);
|
|
204
|
+
const stats = controller.stats();
|
|
205
|
+
assert.ok(stats);
|
|
206
|
+
assert.ok(stats.bytesIn > 0);
|
|
207
|
+
assert.ok(stats.recordsProcessed >= 2);
|
|
208
|
+
});
|
|
209
|
+
it("should handle empty input", async () => {
|
|
210
|
+
const controller = new NodeStreamController({
|
|
211
|
+
inputFormat: "csv",
|
|
212
|
+
outputFormat: "ndjson",
|
|
213
|
+
onRecord: () => {
|
|
214
|
+
assert.fail("Should not process any records");
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
await controller.process("");
|
|
218
|
+
assert.equal(controller.getRecordCount(), 0);
|
|
219
|
+
});
|
|
220
|
+
it("should handle Buffer input", async () => {
|
|
221
|
+
const csvData = Buffer.from(`name,age
|
|
222
|
+
Alice,30
|
|
223
|
+
Bob,25`);
|
|
224
|
+
const records = [];
|
|
225
|
+
const controller = new NodeStreamController({
|
|
226
|
+
inputFormat: "csv",
|
|
227
|
+
outputFormat: "ndjson",
|
|
228
|
+
onRecord: (record) => {
|
|
229
|
+
records.push(record);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
await controller.process(csvData);
|
|
233
|
+
assert.equal(records.length, 2);
|
|
234
|
+
assert.equal(records[0].name, "Alice");
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
//# sourceMappingURL=stream-controller.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../tests/stream-controller.test.ts"],"sourcesContent":["/**\r\n * Test: Stream Controller - Per-record processing with backpressure\r\n * \r\n * This test demonstrates the high-level streaming API with per-record callbacks.\r\n */\r\n\r\nimport { describe, it } from \"node:test\";\r\nimport { strict as assert } from \"node:assert\";\r\nimport { NodeStreamController } from \"../node-stream-controller.js\";\r\nimport { Readable } from \"node:stream\";\r\n\r\ndescribe(\"NodeStreamController\", () => {\r\n it(\"should process CSV records with onRecord callback\", async () => {\r\n const csvData = `name,age,city\r\nAlice,30,NYC\r\nBob,25,LA\r\nCharlie,35,Chicago`;\r\n\r\n const records: any[] = [];\r\n const controller = new NodeStreamController({\r\n inputFormat: \"csv\",\r\n outputFormat: \"ndjson\",\r\n onRecord: (record, index) => {\r\n records.push(record);\r\n },\r\n });\r\n\r\n await controller.process(csvData);\r\n\r\n assert.equal(records.length, 3);\r\n assert.equal(records[0].name, \"Alice\");\r\n assert.equal(records[0].age, \"30\");\r\n assert.equal(records[1].name, \"Bob\");\r\n assert.equal(records[2].name, \"Charlie\");\r\n });\r\n\r\n it(\"should handle async onRecord callback with backpressure\", async () => {\r\n const csvData = `id,value\r\n1,100\r\n2,200\r\n3,300`;\r\n\r\n const records: any[] = [];\r\n const delays: number[] = [];\r\n \r\n const controller = new NodeStreamController({\r\n inputFormat: \"csv\",\r\n outputFormat: \"ndjson\",\r\n autoPause: true,\r\n onRecord: async (record, index) => {\r\n const start = Date.now();\r\n // Simulate async processing\r\n await new Promise(resolve => setTimeout(resolve, 10));\r\n const delay = Date.now() - start;\r\n delays.push(delay);\r\n records.push(record);\r\n },\r\n });\r\n\r\n await controller.process(csvData);\r\n\r\n assert.equal(records.length, 3);\r\n assert.equal(records[0].id, \"1\");\r\n // Verify that delays happened (backpressure was applied)\r\n assert.ok(delays.every(d => d >= 9));\r\n });\r\n\r\n it(\"should track progress with progressBatchSize\", async () => {\r\n const ndjsonLines: string[] = [];\r\n for (let i = 0; i < 50; i++) {\r\n ndjsonLines.push(JSON.stringify({ id: i, value: i * 10 }));\r\n }\r\n const ndjsonData = ndjsonLines.join(\"\\n\");\r\n\r\n let progressCallCount = 0;\r\n let lastRecordCount = 0;\r\n\r\n const controller = new NodeStreamController({\r\n inputFormat: \"ndjson\",\r\n outputFormat: \"json\",\r\n progressBatchSize: 10,\r\n onStreamProgress: (stats, recordCount) => {\r\n progressCallCount++;\r\n lastRecordCount = recordCount;\r\n },\r\n });\r\n\r\n await controller.process(ndjsonData);\r\n\r\n // Progress should be called every 10 records\r\n assert.ok(progressCallCount >= 4); // 50 / 10 = 5, but may vary\r\n assert.equal(lastRecordCount, 50);\r\n });\r\n\r\n it(\"should support pause and resume\", async () => {\r\n const csvData = `num\r\n1\r\n2\r\n3\r\n4\r\n5`;\r\n\r\n const records: any[] = [];\r\n let pauseCount = 0;\r\n\r\n const controller = new NodeStreamController({\r\n inputFormat: \"csv\",\r\n outputFormat: \"ndjson\",\r\n autoPause: false,\r\n onRecord: (record, index) => {\r\n records.push(record);\r\n \r\n // Pause after 2nd record\r\n if (index === 2 && pauseCount === 0) {\r\n pauseCount++;\r\n controller.pause();\r\n \r\n // Resume after 100ms\r\n setTimeout(() => {\r\n controller.resume();\r\n }, 100);\r\n \r\n return false; // Signal pause\r\n }\r\n },\r\n });\r\n\r\n const start = Date.now();\r\n await controller.process(csvData);\r\n const duration = Date.now() - start;\r\n\r\n assert.equal(records.length, 5);\r\n assert.ok(duration >= 90); // Should have paused for ~100ms\r\n assert.equal(pauseCount, 1);\r\n });\r\n\r\n it(\"should support abort\", async () => {\r\n const csvData = `num\r\n${Array.from({ length: 1000 }, (_, i) => i).join(\"\\n\")}`;\r\n\r\n const records: any[] = [];\r\n\r\n const controller = new NodeStreamController({\r\n inputFormat: \"csv\",\r\n outputFormat: \"ndjson\",\r\n onRecord: (record, index) => {\r\n records.push(record);\r\n \r\n // Abort after 10 records\r\n if (index === 10) {\r\n controller.abort();\r\n }\r\n },\r\n });\r\n\r\n await controller.process(csvData);\r\n\r\n // Should have stopped around 10 records\r\n assert.ok(records.length <= 20); // Some buffer, but should be much less than 1000\r\n });\r\n\r\n it(\"should work with Node.js Readable stream\", async () => {\r\n const csvData = `x,y\r\n1,2\r\n3,4\r\n5,6`;\r\n\r\n const stream = Readable.from([csvData]);\r\n\r\n const records: any[] = [];\r\n const controller = new NodeStreamController({\r\n inputFormat: \"csv\",\r\n outputFormat: \"ndjson\",\r\n onRecord: (record) => {\r\n records.push(record);\r\n },\r\n });\r\n\r\n await controller.process(stream);\r\n\r\n assert.equal(records.length, 3);\r\n assert.equal(records[0].x, \"1\");\r\n assert.equal(records[0].y, \"2\");\r\n });\r\n\r\n it(\"should handle CSV output format with field names\", async () => {\r\n const jsonData = JSON.stringify([\r\n { name: \"Alice\", score: 95 },\r\n { name: \"Bob\", score: 87 },\r\n ]);\r\n\r\n const records: any[] = [];\r\n const controller = new NodeStreamController({\r\n inputFormat: \"json\",\r\n outputFormat: \"csv\",\r\n onRecord: (record) => {\r\n records.push(record);\r\n },\r\n });\r\n\r\n await controller.process(jsonData);\r\n\r\n assert.equal(records.length, 2);\r\n // CSV splitter should provide objects with field names\r\n assert.ok(\"name\" in records[0]);\r\n assert.ok(\"score\" in records[0]);\r\n assert.equal(records[0].name, \"Alice\");\r\n assert.equal(records[1].name, \"Bob\");\r\n });\r\n\r\n it(\"should handle NDJSON input and output\", async () => {\r\n const ndjsonData = `{\"id\":1,\"name\":\"Alice\"}\r\n{\"id\":2,\"name\":\"Bob\"}\r\n{\"id\":3,\"name\":\"Charlie\"}`;\r\n\r\n const records: any[] = [];\r\n const controller = new NodeStreamController({\r\n inputFormat: \"ndjson\",\r\n outputFormat: \"ndjson\",\r\n onRecord: (record) => {\r\n records.push(record);\r\n },\r\n });\r\n\r\n await controller.process(ndjsonData);\r\n\r\n assert.equal(records.length, 3);\r\n assert.equal(records[0].name, \"Alice\");\r\n assert.equal(records[1].id, 2);\r\n });\r\n\r\n it(\"should handle JSON array output\", async () => {\r\n const csvData = `key,val\r\na,1\r\nb,2`;\r\n\r\n const records: any[] = [];\r\n const controller = new NodeStreamController({\r\n inputFormat: \"csv\",\r\n outputFormat: \"json\",\r\n onRecord: (record) => {\r\n records.push(record);\r\n },\r\n });\r\n\r\n await controller.process(csvData);\r\n\r\n assert.equal(records.length, 2);\r\n assert.equal(records[0].key, \"a\");\r\n assert.equal(records[1].val, \"2\");\r\n });\r\n\r\n it(\"should provide stats\", async () => {\r\n const csvData = `col1,col2\r\nvalue1,value2\r\nvalue3,value4`;\r\n\r\n const controller = new NodeStreamController({\r\n inputFormat: \"csv\",\r\n outputFormat: \"ndjson\",\r\n onRecord: () => {},\r\n });\r\n\r\n await controller.process(csvData);\r\n\r\n const stats = controller.stats();\r\n assert.ok(stats);\r\n assert.ok(stats.bytesIn > 0);\r\n assert.ok(stats.recordsProcessed >= 2);\r\n });\r\n\r\n it(\"should handle empty input\", async () => {\r\n const controller = new NodeStreamController({\r\n inputFormat: \"csv\",\r\n outputFormat: \"ndjson\",\r\n onRecord: () => {\r\n assert.fail(\"Should not process any records\");\r\n },\r\n });\r\n\r\n await controller.process(\"\");\r\n\r\n assert.equal(controller.getRecordCount(), 0);\r\n });\r\n\r\n it(\"should handle Buffer input\", async () => {\r\n const csvData = Buffer.from(`name,age\r\nAlice,30\r\nBob,25`);\r\n\r\n const records: any[] = [];\r\n const controller = new NodeStreamController({\r\n inputFormat: \"csv\",\r\n outputFormat: \"ndjson\",\r\n onRecord: (record) => {\r\n records.push(record);\r\n },\r\n });\r\n\r\n await controller.process(csvData);\r\n\r\n assert.equal(records.length, 2);\r\n assert.equal(records[0].name, \"Alice\");\r\n });\r\n});\r\n"],"mappings":"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","names":[]}
|
|
@@ -44,6 +44,12 @@ export class Converter {
|
|
|
44
44
|
* Create a new converter with specific configuration
|
|
45
45
|
*/
|
|
46
46
|
static withConfig(debug: boolean, input_format: string, output_format: string, chunk_target_bytes: number, enable_stats: boolean, csv_config: any, xml_config: any, transform_config: any): Converter;
|
|
47
|
+
/**
|
|
48
|
+
* Push a chunk of bytes and optionally get pre-parsed records.
|
|
49
|
+
* When include_records is true, returns { output: Uint8Array, records: Array }
|
|
50
|
+
* When false, returns just the Uint8Array (for backward compatibility and performance).
|
|
51
|
+
*/
|
|
52
|
+
pushWithRecords(chunk: Uint8Array, include_records: boolean): any;
|
|
47
53
|
constructor(debug: boolean);
|
|
48
54
|
/**
|
|
49
55
|
* Push a chunk of bytes. Returns converted output bytes for that chunk.
|