convert-buddy-js 1.0.0 → 1.0.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/dist/buddy-stream.js
CHANGED
|
@@ -29,6 +29,8 @@ class BuddyControllerImpl {
|
|
|
29
29
|
state;
|
|
30
30
|
wasmStats = null;
|
|
31
31
|
utf8Decoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: false });
|
|
32
|
+
/** Dedicated decoder for onData output — uses streaming mode to buffer incomplete multi-byte sequences across chunks */
|
|
33
|
+
outputDecoder = new TextDecoder("utf-8", { ignoreBOM: true, fatal: false });
|
|
32
34
|
// Promise internals
|
|
33
35
|
donePromise;
|
|
34
36
|
doneResolve;
|
|
@@ -117,7 +119,7 @@ class BuddyControllerImpl {
|
|
|
117
119
|
this.wasmStats = converter.getStats();
|
|
118
120
|
if (output.length > 0) {
|
|
119
121
|
if (needsJsonParsing) {
|
|
120
|
-
const text = this.utf8Decoder.decode(output);
|
|
122
|
+
const text = this.utf8Decoder.decode(output, { stream: true });
|
|
121
123
|
const extracted = this.extractJsonObjects(text, jsonTextBuffer, insideArray);
|
|
122
124
|
jsonTextBuffer = extracted.remaining;
|
|
123
125
|
insideArray = extracted.insideArray;
|
|
@@ -148,7 +150,9 @@ class BuddyControllerImpl {
|
|
|
148
150
|
recordBuffer.push(...extracted.objects);
|
|
149
151
|
}
|
|
150
152
|
}
|
|
151
|
-
await this.emitData(finalOutput);
|
|
153
|
+
await this.emitData(finalOutput, true);
|
|
154
|
+
} else {
|
|
155
|
+
await this.emitData(new Uint8Array(0), true);
|
|
152
156
|
}
|
|
153
157
|
if (this.options.onRecords && finalOutput.length > 0 && !needsJsonParsing && !this.state.cancelled) {
|
|
154
158
|
const finalRecords = this.parseRecordsFromOutput(finalOutput);
|
|
@@ -167,10 +171,11 @@ class BuddyControllerImpl {
|
|
|
167
171
|
this.complete(error instanceof Error ? error : new Error(String(error)));
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
|
-
async emitData(chunk) {
|
|
174
|
+
async emitData(chunk, flush = false) {
|
|
171
175
|
if (!this.options.emitOutput || !this.options.onData) return;
|
|
172
176
|
const stats = this.createStatsSnapshot();
|
|
173
|
-
const text = this.
|
|
177
|
+
const text = this.outputDecoder.decode(chunk, { stream: !flush });
|
|
178
|
+
if (text.length === 0) return;
|
|
174
179
|
if (typeof process !== "undefined" && process.env.TEST_DEBUG === "1") {
|
|
175
180
|
try {
|
|
176
181
|
console.log("[buddy-stream] emitData preview:", text.substring(0, 200));
|
package/dist/buddy-stream.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/buddy-stream.ts"],"sourcesContent":["/**\r\n * Streaming API for ConvertBuddy\r\n * \r\n * cb.stream() returns a controller immediately; the controller exposes \r\n * done: Promise<BuddyStats> for optional awaiting.\r\n * \r\n * Design Principles:\r\n * 1. Fire-and-forget: call cb.stream(...) and ignore done\r\n * 2. Await completion: await controller.done\r\n * 3. No need for `for await (...)` in the normal path\r\n * 4. Automatic backpressure when onRecords returns a Promise\r\n * 5. Manual pause/resume also supported\r\n */\r\n\r\nimport type { \r\n Format, \r\n Stats, \r\n BuddyStats, \r\n BuddyController, \r\n BuddyStreamOptions, \r\n BuddyInput,\r\n ConverterInterface,\r\n} from \"./types.js\";\r\n\r\n// Re-export types for convenience\r\nexport type { BuddyStats, BuddyController, BuddyStreamOptions, BuddyInput } from \"./types.js\";\r\n\r\n// ============================================================================\r\n// Internal Types\r\n// ============================================================================\r\n\r\n/**\r\n * Minimal interface for Node.js Readable stream compatibility\r\n * We use duck-typing to avoid importing node:stream in browser builds\r\n */\r\ninterface NodeReadableCompat {\r\n read?: () => unknown;\r\n on?: (event: string, listener: (...args: unknown[]) => void) => unknown;\r\n [Symbol.asyncIterator]?: () => AsyncIterator<unknown>;\r\n}\r\n\r\ninterface InternalState {\r\n paused: boolean;\r\n cancelled: boolean;\r\n done: boolean;\r\n error?: Error;\r\n startedAt: number;\r\n endedAt?: number;\r\n /** JS-side batch counter (only JS knows how many batches were emitted to onRecords) */\r\n batchesOut: number;\r\n /** Cumulative count of records emitted to onRecords (sum of batch.length) */\r\n recordsEmitted: number;\r\n pauseResolvers: Array<() => void>;\r\n}\r\n\r\n// ============================================================================\r\n// BuddyControllerImpl Class\r\n// ============================================================================\r\n\r\nclass BuddyControllerImpl implements BuddyController {\r\n private options: Required<Pick<BuddyStreamOptions, 'recordBatchSize' | 'emitOutput' | 'debug'>> & BuddyStreamOptions;\r\n private state: InternalState;\r\n private wasmStats: Stats | null = null;\r\n private utf8Decoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: false });\r\n \r\n // Promise internals\r\n private donePromise: Promise<BuddyStats>;\r\n private doneResolve!: (stats: BuddyStats) => void;\r\n private doneReject!: (error: Error) => void;\r\n\r\n constructor(\r\n private input: BuddyInput,\r\n private converterFactory: () => Promise<ConverterInterface>,\r\n options: BuddyStreamOptions\r\n ) {\r\n // Merge defaults\r\n this.options = {\r\n recordBatchSize: 1000,\r\n emitOutput: options.outputFormat !== undefined,\r\n debug: false,\r\n ...options,\r\n };\r\n\r\n // Initialize state\r\n this.state = {\r\n paused: false,\r\n cancelled: false,\r\n done: false,\r\n startedAt: Date.now(),\r\n batchesOut: 0,\r\n recordsEmitted: 0,\r\n pauseResolvers: [],\r\n };\r\n\r\n // Create done promise\r\n this.donePromise = new Promise<BuddyStats>((resolve, reject) => {\r\n this.doneResolve = resolve;\r\n this.doneReject = reject;\r\n });\r\n\r\n // Fire-and-forget safety: attach catch handler to prevent unhandled rejection\r\n // This is ONLY for the case where user doesn't await done and doesn't provide onError\r\n this.donePromise.catch(() => {\r\n // Errors will be handled by onError callback or logged\r\n });\r\n\r\n // Schedule processing to start in next microtask\r\n // This ensures caller can attach handlers immediately after stream() returns\r\n queueMicrotask(() => this.startProcessing());\r\n }\r\n\r\n // ==========================================================================\r\n // Public API: Control Methods\r\n // ==========================================================================\r\n\r\n pause(): void {\r\n if (this.state.done || this.state.cancelled) return;\r\n this.state.paused = true;\r\n if (this.options.debug) {\r\n console.log('[buddy-stream] paused');\r\n }\r\n }\r\n\r\n resume(): void {\r\n if (this.state.done || this.state.cancelled) return;\r\n this.state.paused = false;\r\n if (this.options.debug) {\r\n console.log('[buddy-stream] resumed');\r\n }\r\n // Unblock any waiting pause resolvers\r\n for (const resolver of this.state.pauseResolvers) {\r\n try { resolver(); } catch { /* swallow */ }\r\n }\r\n this.state.pauseResolvers.length = 0;\r\n }\r\n\r\n cancel(reason?: Error | string): void {\r\n if (this.state.done || this.state.cancelled) return;\r\n \r\n this.state.cancelled = true;\r\n this.state.error = reason instanceof Error \r\n ? reason \r\n : new Error(reason ?? 'Stream cancelled');\r\n \r\n if (this.options.debug) {\r\n console.log('[buddy-stream] cancelled:', this.state.error.message);\r\n }\r\n\r\n // Unblock any waiting operations\r\n this.resume();\r\n \r\n // Complete with error\r\n this.complete(this.state.error);\r\n }\r\n\r\n stats(): BuddyStats {\r\n return this.createStatsSnapshot();\r\n }\r\n\r\n get done(): Promise<BuddyStats> {\r\n return this.donePromise;\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Processing\r\n // ==========================================================================\r\n\r\n private async startProcessing(): Promise<void> {\r\n let converter: ConverterInterface | null = null;\r\n\r\n try {\r\n // Create converter\r\n converter = await this.converterFactory();\r\n\r\n // Process input\r\n const iterator = this.toAsyncIterator(this.input);\r\n const batchSize = this.options.recordBatchSize;\r\n let recordBuffer: unknown[] = [];\r\n \r\n // For JSON output: parse array incrementally to extract complete objects\r\n // Buffer accumulates text until we can extract complete JSON objects\r\n const needsJsonParsing = this.options.outputFormat === 'json' && !!this.options.onRecords;\r\n let jsonTextBuffer = '';\r\n let insideArray = false;\r\n\r\n for await (const chunk of iterator) {\r\n // Check for cancellation\r\n if (this.state.cancelled) break;\r\n\r\n // Wait if paused (manual pause)\r\n await this.waitIfPaused();\r\n if (this.state.cancelled) break;\r\n\r\n // Process chunk through WASM\r\n // For JSON output: parse incrementally in JS to extract array elements\r\n // For other formats: WASM provides incremental records\r\n const needsRecords = !!this.options.onRecords && !needsJsonParsing;\r\n const result = converter.pushWithRecords(chunk, needsRecords);\r\n\r\n let output: Uint8Array;\r\n let records: unknown[] = [];\r\n\r\n if (typeof result === 'object' && 'output' in result) {\r\n output = result.output;\r\n records = result.records || [];\r\n } else {\r\n output = result;\r\n // Parse records from output if needed\r\n if (needsRecords && output.length > 0) {\r\n records = this.parseRecordsFromOutput(output);\r\n }\r\n }\r\n\r\n // Normalize records: wasm may return Map instances for records which\r\n // JSON.stringify will render as `{}`. Convert Maps to plain objects.\r\n if (records && records.length > 0) {\r\n records = records.map((r) => {\r\n try {\r\n if (r instanceof Map) {\r\n return Object.fromEntries(r as any);\r\n }\r\n } catch {}\r\n return r;\r\n });\r\n }\r\n\r\n // Update WASM stats\r\n this.wasmStats = converter.getStats();\r\n\r\n // Emit output data\r\n if (output.length > 0) {\r\n // For JSON output with onRecords: parse incrementally\r\n if (needsJsonParsing) {\r\n const text = this.utf8Decoder.decode(output);\r\n const extracted = this.extractJsonObjects(text, jsonTextBuffer, insideArray);\r\n jsonTextBuffer = extracted.remaining;\r\n insideArray = extracted.insideArray;\r\n \r\n if (extracted.objects.length > 0) {\r\n recordBuffer.push(...extracted.objects);\r\n }\r\n }\r\n \r\n await this.emitData(output);\r\n if (this.state.cancelled) break;\r\n }\r\n\r\n // Buffer records\r\n if (records.length > 0) {\r\n recordBuffer.push(...records);\r\n }\r\n\r\n // Emit batches\r\n while (recordBuffer.length >= batchSize) {\r\n const batch = recordBuffer.splice(0, batchSize);\r\n await this.emitRecordBatch(batch);\r\n if (this.state.cancelled) break;\r\n }\r\n }\r\n\r\n // Finalize converter (even if cancelled, to ensure proper cleanup like closing JSON arrays)\r\n if (converter) {\r\n const finalOutput = converter.finish();\r\n \r\n // Only emit output if not cancelled\r\n if (!this.state.cancelled && finalOutput.length > 0) {\r\n // Parse final JSON chunk\r\n if (needsJsonParsing) {\r\n const text = this.utf8Decoder.decode(finalOutput);\r\n const extracted = this.extractJsonObjects(text, jsonTextBuffer, insideArray);\r\n jsonTextBuffer = extracted.remaining;\r\n \r\n if (extracted.objects.length > 0) {\r\n recordBuffer.push(...extracted.objects);\r\n }\r\n }\r\n \r\n await this.emitData(finalOutput);\r\n }\r\n \r\n // For non-JSON formats, parse any remaining records from final output\r\n if (this.options.onRecords && finalOutput.length > 0 && !needsJsonParsing && !this.state.cancelled) {\r\n const finalRecords = this.parseRecordsFromOutput(finalOutput);\r\n recordBuffer.push(...finalRecords);\r\n }\r\n\r\n // Emit remaining records (only if not cancelled)\r\n while (recordBuffer.length > 0 && !this.state.cancelled) {\r\n const batch = recordBuffer.splice(0, this.options.recordBatchSize);\r\n await this.emitRecordBatch(batch);\r\n }\r\n\r\n // Update final WASM stats\r\n this.wasmStats = converter.getStats();\r\n }\r\n\r\n // Complete successfully\r\n if (!this.state.cancelled) {\r\n this.complete();\r\n }\r\n\r\n } catch (error) {\r\n this.complete(error instanceof Error ? error : new Error(String(error)));\r\n }\r\n }\r\n\r\n private async emitData(chunk: Uint8Array): Promise<void> {\r\n if (!this.options.emitOutput || !this.options.onData) return;\r\n \r\n const stats = this.createStatsSnapshot();\r\n // Decode Uint8Array to string for onData callback\r\n const text = this.utf8Decoder.decode(chunk);\r\n if (typeof process !== 'undefined' && (process.env as any).TEST_DEBUG === '1') {\r\n try { console.log('[buddy-stream] emitData preview:', text.substring(0, 200)); } catch {}\r\n }\r\n \r\n try {\r\n const maybePromise = this.options.onData(text as any, stats);\r\n \r\n if (maybePromise instanceof Promise) {\r\n await maybePromise;\r\n }\r\n } catch (error) {\r\n // Propagate error from onData callback\r\n throw error;\r\n }\r\n }\r\n\r\n private async emitRecordBatch(batch: unknown[]): Promise<void> {\r\n if (batch.length === 0) return;\r\n // Debugging helper: optionally log batch contents when TEST_DEBUG=1\r\n if (typeof process !== 'undefined' && (process.env as any).TEST_DEBUG === '1') {\r\n try {\r\n const sample = batch.slice(0, 2).map((r) => {\r\n try {\r\n const info: any = { type: typeof r };\r\n if (r && typeof r === 'object') {\r\n info.constructor = (r as any).constructor ? (r as any).constructor.name : undefined;\r\n try { info.keys = Object.keys(r); } catch { info.keys = '[error]'; }\r\n try { info.ownProps = Object.getOwnPropertyNames(r); } catch { info.ownProps = '[error]'; }\r\n try {\r\n if (r instanceof Uint8Array || (typeof Buffer !== 'undefined' && (r as any) instanceof Buffer)) {\r\n info.isBuffer = true;\r\n info.length = (r as any).length;\r\n info.preview = Array.from((r as any).slice(0, 8));\r\n }\r\n } catch {}\r\n }\r\n return info;\r\n } catch (e) {\r\n return { error: String(e) };\r\n }\r\n });\r\n console.log('[buddy-stream] emitRecordBatch:', batch.length, sample);\r\n try {\r\n const preview = JSON.stringify(batch[0]);\r\n console.log('[buddy-stream] emitRecordBatch preview[0]:', preview);\r\n } catch (e) {\r\n console.log('[buddy-stream] emitRecordBatch preview[0]: [unserializable]');\r\n }\r\n } catch (e) {\r\n try { console.log('[buddy-stream] emitRecordBatch: debug error', e); } catch {}\r\n }\r\n }\r\n\r\n this.state.batchesOut += 1;\r\n this.state.recordsEmitted += batch.length;\r\n\r\n const stats = this.createStatsSnapshot();\r\n\r\n // If user provided onRecords, invoke it (may apply backpressure).\r\n if (this.options.onRecords) {\r\n try {\r\n // Log invocation for tricky encoding/debugging cases\r\n if (typeof process !== 'undefined' && (process.env as any).TEST_DEBUG === '1') {\r\n try {\r\n console.log('[buddy-stream] invoking onRecords, batch.length=', batch.length);\r\n } catch {}\r\n }\r\n\r\n const maybePromise = this.options.onRecords(\r\n this,\r\n batch as any,\r\n stats,\r\n this.state.recordsEmitted\r\n );\r\n\r\n // AUTOMATIC BACKPRESSURE: if onRecords returns a Promise, wait for it\r\n if (maybePromise instanceof Promise) {\r\n await maybePromise;\r\n }\r\n\r\n // Also respect manual pause\r\n await this.waitIfPaused();\r\n } catch (error) {\r\n // Propagate error from onRecords callback\r\n throw error;\r\n }\r\n }\r\n }\r\n\r\n private async waitIfPaused(): Promise<void> {\r\n while (this.state.paused && !this.state.cancelled) {\r\n await new Promise<void>(resolve => {\r\n // support multiple concurrent waiters by pushing resolvers to an array\r\n (this.state as any).pauseResolvers.push(resolve);\r\n });\r\n }\r\n }\r\n\r\n private complete(error?: Error): void {\r\n if (this.state.done) return;\r\n\r\n this.state.done = true;\r\n this.state.endedAt = Date.now();\r\n \r\n if (error) {\r\n this.state.error = error;\r\n }\r\n\r\n const finalStats = this.createStatsSnapshot();\r\n\r\n if (typeof process !== 'undefined' && (process.env as any).TEST_DEBUG === '1') {\r\n try {\r\n console.log('[buddy-stream] complete: finalStats=', finalStats, 'wasmStats=', this.wasmStats);\r\n } catch {}\r\n }\r\n\r\n if (error) {\r\n // Call onError if provided\r\n if (this.options.onError) {\r\n try {\r\n this.options.onError(error, this, finalStats);\r\n } catch (e) {\r\n console.error('[buddy-stream] Error in onError callback:', e);\r\n }\r\n } else {\r\n // Log warning if no error handler (fire-and-forget safety)\r\n console.warn('[buddy-stream] Stream error (no onError handler):', error.message);\r\n }\r\n this.doneReject(error);\r\n } else {\r\n // Call onDone if provided. If it throws, propagate that error to\r\n // the done promise (and to onError) so callers can observe it.\r\n if (this.options.onDone) {\r\n try {\r\n this.options.onDone(finalStats);\r\n } catch (e) {\r\n const err = e instanceof Error ? e : new Error(String(e));\r\n // Notify onError if present\r\n if (this.options.onError) {\r\n try {\r\n this.options.onError(err, this, finalStats);\r\n } catch (e2) {\r\n console.error('[buddy-stream] Error in onError callback:', e2);\r\n }\r\n } else {\r\n console.warn('[buddy-stream] Stream error (no onError handler):', err.message);\r\n }\r\n this.doneReject(err);\r\n return;\r\n }\r\n }\r\n this.doneResolve(finalStats);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Stats\r\n // ==========================================================================\r\n\r\n private createStatsSnapshot(): BuddyStats {\r\n const now = Date.now();\r\n const durationMs = this.state.endedAt \r\n ? this.state.endedAt - this.state.startedAt\r\n : now - this.state.startedAt;\r\n\r\n // WASM is single source of truth for bytes/throughput.\r\n // For recordsOut: when onRecords callback is provided, use recordsEmitted (sum of batches).\r\n // Otherwise, use WASM's recordsProcessed (for conversions without record callbacks).\r\n const ws: any = this.wasmStats;\r\n const bytesIn = ws?.bytesIn ?? ws?.bytes_in ?? 0;\r\n const bytesOut = ws?.bytesOut ?? ws?.bytes_out ?? 0;\r\n const recordsOut = this.options.onRecords\r\n ? this.state.recordsEmitted\r\n : (ws?.recordsProcessed ?? ws?.records_processed ?? 0);\r\n const throughput = ws?.throughputMbPerSec ?? ws?.throughput_mb_per_sec ?? 0;\r\n\r\n const stats: BuddyStats = {\r\n bytesIn,\r\n bytesOut,\r\n recordsOut,\r\n batchesOut: this.state.batchesOut,\r\n durationMs,\r\n throughputMbPerSec: throughput,\r\n startedAt: this.state.startedAt,\r\n endedAt: this.state.endedAt,\r\n isPaused: this.state.paused,\r\n isDone: this.state.done,\r\n error: this.state.error,\r\n };\r\n\r\n return Object.freeze(stats);\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Input Normalization\r\n // ==========================================================================\r\n\r\n private async *toAsyncIterator(input: BuddyInput): AsyncGenerator<Uint8Array> {\r\n // String: URL or raw data\r\n if (typeof input === 'string') {\r\n if (input.startsWith('http://') || input.startsWith('https://')) {\r\n const response = await fetch(input);\r\n if (!response.ok) {\r\n throw new Error(`Fetch failed: ${response.status} ${response.statusText}`);\r\n }\r\n if (!response.body) {\r\n throw new Error('Response body is null');\r\n }\r\n yield* this.fromReadableStream(response.body);\r\n return;\r\n }\r\n yield new TextEncoder().encode(input);\r\n return;\r\n }\r\n\r\n if (input instanceof Uint8Array) {\r\n yield input;\r\n return;\r\n }\r\n\r\n if (input instanceof ArrayBuffer) {\r\n yield new Uint8Array(input);\r\n return;\r\n }\r\n\r\n if (this.isReadableStream(input)) {\r\n yield* this.fromReadableStream(input);\r\n return;\r\n }\r\n\r\n if (typeof Blob !== 'undefined' && input instanceof Blob) {\r\n const stream = input.stream() as ReadableStream<Uint8Array>;\r\n yield* this.fromReadableStream(stream);\r\n return;\r\n }\r\n\r\n if (this.isAsyncIterable(input)) {\r\n for await (const chunk of input) {\r\n yield this.toUint8Array(chunk);\r\n }\r\n return;\r\n }\r\n\r\n if (this.isNodeReadable(input)) {\r\n yield* this.fromNodeReadable(input);\r\n return;\r\n }\r\n\r\n throw new Error('Unsupported input type');\r\n }\r\n\r\n private isReadableStream(value: unknown): value is ReadableStream<Uint8Array> {\r\n return typeof ReadableStream !== 'undefined' && value instanceof ReadableStream;\r\n }\r\n\r\n private isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {\r\n return value != null && typeof (value as AsyncIterable<unknown>)[Symbol.asyncIterator] === 'function';\r\n }\r\n\r\n private isNodeReadable(value: unknown): value is NodeReadableCompat {\r\n const v = value as NodeReadableCompat;\r\n return v != null && typeof v.on === 'function' && typeof v.read === 'function';\r\n }\r\n\r\n private async *fromReadableStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<Uint8Array> {\r\n const reader = stream.getReader();\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n yield value;\r\n }\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n }\r\n\r\n private async *fromNodeReadable(stream: NodeReadableCompat): AsyncGenerator<Uint8Array> {\r\n if (stream[Symbol.asyncIterator]) {\r\n const iter = stream[Symbol.asyncIterator]!();\r\n for await (const chunk of { [Symbol.asyncIterator]: () => iter }) {\r\n yield this.toUint8Array(chunk);\r\n }\r\n } else {\r\n throw new Error('Node.js Readable stream must be async iterable');\r\n }\r\n }\r\n\r\n private toUint8Array(chunk: unknown): Uint8Array {\r\n if (chunk instanceof Uint8Array) return chunk;\r\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(chunk)) return new Uint8Array(chunk);\r\n if (typeof chunk === 'string') return new TextEncoder().encode(chunk);\r\n if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk);\r\n throw new Error('Unexpected chunk type');\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Incremental JSON Array Parsing\r\n // ==========================================================================\r\n\r\n /**\r\n * Extract complete JSON objects from streaming JSON array output.\r\n * Handles incomplete objects across chunk boundaries.\r\n * \r\n * @param newText - New text chunk from current output\r\n * @param buffer - Previously buffered incomplete text\r\n * @param wasInsideArray - Whether we were inside array from previous chunk\r\n * @returns Extracted objects and remaining incomplete text\r\n */\r\n private extractJsonObjects(\r\n newText: string,\r\n buffer: string,\r\n wasInsideArray: boolean\r\n ): { objects: unknown[]; remaining: string; insideArray: boolean } {\r\n const objects: unknown[] = [];\r\n const fullText = buffer + newText;\r\n let pos = 0;\r\n let insideArray = wasInsideArray;\r\n \r\n // Find array start if not already inside\r\n if (!insideArray) {\r\n const arrayStart = fullText.indexOf('[');\r\n if (arrayStart === -1) {\r\n return { objects, remaining: fullText, insideArray: false };\r\n }\r\n pos = arrayStart + 1;\r\n insideArray = true;\r\n }\r\n \r\n // Parse objects from array\r\n let depth = 0;\r\n let inString = false;\r\n let escape = false;\r\n let objectStart = -1;\r\n \r\n for (let i = pos; i < fullText.length; i++) {\r\n const char = fullText[i];\r\n \r\n if (escape) {\r\n escape = false;\r\n continue;\r\n }\r\n \r\n if (char === '\\\\') {\r\n escape = true;\r\n continue;\r\n }\r\n \r\n if (char === '\"') {\r\n inString = !inString;\r\n continue;\r\n }\r\n \r\n if (inString) continue;\r\n \r\n if (char === '{') {\r\n if (depth === 0) objectStart = i;\r\n depth++;\r\n } else if (char === '}') {\r\n depth--;\r\n if (depth === 0 && objectStart !== -1) {\r\n // Complete object found\r\n const objectText = fullText.substring(objectStart, i + 1);\r\n try {\r\n const obj = JSON.parse(objectText);\r\n objects.push(obj);\r\n } catch (e) {\r\n if (this.options.debug) {\r\n console.warn('[buddy-stream] Failed to parse JSON object:', e);\r\n }\r\n }\r\n objectStart = -1;\r\n pos = i + 1;\r\n }\r\n } else if (char === ']' && depth === 0) {\r\n // End of array\r\n insideArray = false;\r\n pos = i + 1;\r\n break;\r\n }\r\n }\r\n \r\n // Return remaining incomplete text\r\n const remaining = objectStart !== -1 ? fullText.substring(objectStart) : fullText.substring(pos);\r\n return { objects, remaining, insideArray };\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Record Parsing (Fallback)\r\n // ==========================================================================\r\n\r\n /**\r\n * Parse records from output bytes - FALLBACK ONLY\r\n * WASM should return pre-parsed records, this is for compatibility\r\n */\r\n private parseRecordsFromOutput(output: Uint8Array): unknown[] {\r\n if (output.length === 0) return [];\r\n\r\n const format = this.options.outputFormat || 'ndjson';\r\n // Use shared UTF-8 decoder (ignoring BOM) to ensure consistent behavior\r\n const text = this.utf8Decoder.decode(output);\r\n\r\n try {\r\n switch (format) {\r\n case 'ndjson':\r\n return text\r\n .split('\\n')\r\n .filter(line => line.trim().length > 0)\r\n .map(line => JSON.parse(line));\r\n \r\n case 'json':\r\n const parsed = JSON.parse(text);\r\n return Array.isArray(parsed) ? parsed : [parsed];\r\n \r\n case 'csv':\r\n case 'xml':\r\n // These should be parsed by WASM\r\n if (this.options.debug) {\r\n console.warn('[buddy-stream] WASM did not return parsed records for', format);\r\n }\r\n return [];\r\n \r\n default:\r\n return [];\r\n }\r\n } catch {\r\n return []; // Partial data - normal during streaming\r\n }\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Factory Function\r\n// ============================================================================\r\n\r\n/**\r\n * Create a BuddyController for streaming conversion.\r\n * This is the main entry point used by ConvertBuddy.stream()\r\n * \r\n * @internal\r\n */\r\nexport function createBuddyController(\r\n input: BuddyInput,\r\n converterFactory: () => Promise<ConverterInterface>,\r\n options: BuddyStreamOptions\r\n): BuddyController {\r\n return new BuddyControllerImpl(input, converterFactory, options);\r\n}\r\n\r\n// ============================================================================\r\n// Convenience: Standalone stream function\r\n// ============================================================================\r\n\r\n// This will be imported by index.ts and wired up with the actual ConvertBuddy.create\r\nlet _converterFactoryFn: ((opts: BuddyStreamOptions) => Promise<ConverterInterface>) | null = null;\r\n\r\n/**\r\n * Register the converter factory function (called by index.ts at init)\r\n * @internal\r\n */\r\nexport function _setConverterFactory(fn: (opts: BuddyStreamOptions) => Promise<ConverterInterface>): void {\r\n _converterFactoryFn = fn;\r\n}\r\n\r\n/**\r\n * Standalone stream function for simple usage\r\n * \r\n * @example\r\n * ```ts\r\n * import { stream } from 'convert-buddy-js';\r\n * \r\n * const buddy = stream(csvData, {\r\n * inputFormat: 'csv',\r\n * outputFormat: 'ndjson',\r\n * onRecords: async (buddy, records, stats, count) => {\r\n * await saveRecords(records);\r\n * }\r\n * });\r\n * \r\n * await buddy.done;\r\n * ```\r\n */\r\nexport function stream<T = unknown>(\r\n input: BuddyInput,\r\n options: BuddyStreamOptions<T>\r\n): BuddyController {\r\n if (!_converterFactoryFn) {\r\n throw new Error('Converter factory not initialized. Import from convert-buddy-js main entry point.');\r\n }\r\n \r\n return createBuddyController(\r\n input,\r\n () => _converterFactoryFn!(options as BuddyStreamOptions),\r\n options as BuddyStreamOptions\r\n );\r\n}\r\n"],"mappings":"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","names":["stream"]}
|
|
1
|
+
{"version":3,"sources":["../src/buddy-stream.ts"],"sourcesContent":["/**\r\n * Streaming API for ConvertBuddy\r\n * \r\n * cb.stream() returns a controller immediately; the controller exposes \r\n * done: Promise<BuddyStats> for optional awaiting.\r\n * \r\n * Design Principles:\r\n * 1. Fire-and-forget: call cb.stream(...) and ignore done\r\n * 2. Await completion: await controller.done\r\n * 3. No need for `for await (...)` in the normal path\r\n * 4. Automatic backpressure when onRecords returns a Promise\r\n * 5. Manual pause/resume also supported\r\n */\r\n\r\nimport type { \r\n Format, \r\n Stats, \r\n BuddyStats, \r\n BuddyController, \r\n BuddyStreamOptions, \r\n BuddyInput,\r\n ConverterInterface,\r\n} from \"./types.js\";\r\n\r\n// Re-export types for convenience\r\nexport type { BuddyStats, BuddyController, BuddyStreamOptions, BuddyInput } from \"./types.js\";\r\n\r\n// ============================================================================\r\n// Internal Types\r\n// ============================================================================\r\n\r\n/**\r\n * Minimal interface for Node.js Readable stream compatibility\r\n * We use duck-typing to avoid importing node:stream in browser builds\r\n */\r\ninterface NodeReadableCompat {\r\n read?: () => unknown;\r\n on?: (event: string, listener: (...args: unknown[]) => void) => unknown;\r\n [Symbol.asyncIterator]?: () => AsyncIterator<unknown>;\r\n}\r\n\r\ninterface InternalState {\r\n paused: boolean;\r\n cancelled: boolean;\r\n done: boolean;\r\n error?: Error;\r\n startedAt: number;\r\n endedAt?: number;\r\n /** JS-side batch counter (only JS knows how many batches were emitted to onRecords) */\r\n batchesOut: number;\r\n /** Cumulative count of records emitted to onRecords (sum of batch.length) */\r\n recordsEmitted: number;\r\n pauseResolvers: Array<() => void>;\r\n}\r\n\r\n// ============================================================================\r\n// BuddyControllerImpl Class\r\n// ============================================================================\r\n\r\nclass BuddyControllerImpl implements BuddyController {\r\n private options: Required<Pick<BuddyStreamOptions, 'recordBatchSize' | 'emitOutput' | 'debug'>> & BuddyStreamOptions;\r\n private state: InternalState;\r\n private wasmStats: Stats | null = null;\r\n private utf8Decoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: false });\r\n /** Dedicated decoder for onData output — uses streaming mode to buffer incomplete multi-byte sequences across chunks */\r\n private outputDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: false });\r\n \r\n // Promise internals\r\n private donePromise: Promise<BuddyStats>;\r\n private doneResolve!: (stats: BuddyStats) => void;\r\n private doneReject!: (error: Error) => void;\r\n\r\n constructor(\r\n private input: BuddyInput,\r\n private converterFactory: () => Promise<ConverterInterface>,\r\n options: BuddyStreamOptions\r\n ) {\r\n // Merge defaults\r\n this.options = {\r\n recordBatchSize: 1000,\r\n emitOutput: options.outputFormat !== undefined,\r\n debug: false,\r\n ...options,\r\n };\r\n\r\n // Initialize state\r\n this.state = {\r\n paused: false,\r\n cancelled: false,\r\n done: false,\r\n startedAt: Date.now(),\r\n batchesOut: 0,\r\n recordsEmitted: 0,\r\n pauseResolvers: [],\r\n };\r\n\r\n // Create done promise\r\n this.donePromise = new Promise<BuddyStats>((resolve, reject) => {\r\n this.doneResolve = resolve;\r\n this.doneReject = reject;\r\n });\r\n\r\n // Fire-and-forget safety: attach catch handler to prevent unhandled rejection\r\n // This is ONLY for the case where user doesn't await done and doesn't provide onError\r\n this.donePromise.catch(() => {\r\n // Errors will be handled by onError callback or logged\r\n });\r\n\r\n // Schedule processing to start in next microtask\r\n // This ensures caller can attach handlers immediately after stream() returns\r\n queueMicrotask(() => this.startProcessing());\r\n }\r\n\r\n // ==========================================================================\r\n // Public API: Control Methods\r\n // ==========================================================================\r\n\r\n pause(): void {\r\n if (this.state.done || this.state.cancelled) return;\r\n this.state.paused = true;\r\n if (this.options.debug) {\r\n console.log('[buddy-stream] paused');\r\n }\r\n }\r\n\r\n resume(): void {\r\n if (this.state.done || this.state.cancelled) return;\r\n this.state.paused = false;\r\n if (this.options.debug) {\r\n console.log('[buddy-stream] resumed');\r\n }\r\n // Unblock any waiting pause resolvers\r\n for (const resolver of this.state.pauseResolvers) {\r\n try { resolver(); } catch { /* swallow */ }\r\n }\r\n this.state.pauseResolvers.length = 0;\r\n }\r\n\r\n cancel(reason?: Error | string): void {\r\n if (this.state.done || this.state.cancelled) return;\r\n \r\n this.state.cancelled = true;\r\n this.state.error = reason instanceof Error \r\n ? reason \r\n : new Error(reason ?? 'Stream cancelled');\r\n \r\n if (this.options.debug) {\r\n console.log('[buddy-stream] cancelled:', this.state.error.message);\r\n }\r\n\r\n // Unblock any waiting operations\r\n this.resume();\r\n \r\n // Complete with error\r\n this.complete(this.state.error);\r\n }\r\n\r\n stats(): BuddyStats {\r\n return this.createStatsSnapshot();\r\n }\r\n\r\n get done(): Promise<BuddyStats> {\r\n return this.donePromise;\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Processing\r\n // ==========================================================================\r\n\r\n private async startProcessing(): Promise<void> {\r\n let converter: ConverterInterface | null = null;\r\n\r\n try {\r\n // Create converter\r\n converter = await this.converterFactory();\r\n\r\n // Process input\r\n const iterator = this.toAsyncIterator(this.input);\r\n const batchSize = this.options.recordBatchSize;\r\n let recordBuffer: unknown[] = [];\r\n \r\n // For JSON output: parse array incrementally to extract complete objects\r\n // Buffer accumulates text until we can extract complete JSON objects\r\n const needsJsonParsing = this.options.outputFormat === 'json' && !!this.options.onRecords;\r\n let jsonTextBuffer = '';\r\n let insideArray = false;\r\n\r\n for await (const chunk of iterator) {\r\n // Check for cancellation\r\n if (this.state.cancelled) break;\r\n\r\n // Wait if paused (manual pause)\r\n await this.waitIfPaused();\r\n if (this.state.cancelled) break;\r\n\r\n // Process chunk through WASM\r\n // For JSON output: parse incrementally in JS to extract array elements\r\n // For other formats: WASM provides incremental records\r\n const needsRecords = !!this.options.onRecords && !needsJsonParsing;\r\n const result = converter.pushWithRecords(chunk, needsRecords);\r\n\r\n let output: Uint8Array;\r\n let records: unknown[] = [];\r\n\r\n if (typeof result === 'object' && 'output' in result) {\r\n output = result.output;\r\n records = result.records || [];\r\n } else {\r\n output = result;\r\n // Parse records from output if needed\r\n if (needsRecords && output.length > 0) {\r\n records = this.parseRecordsFromOutput(output);\r\n }\r\n }\r\n\r\n // Normalize records: wasm may return Map instances for records which\r\n // JSON.stringify will render as `{}`. Convert Maps to plain objects.\r\n if (records && records.length > 0) {\r\n records = records.map((r) => {\r\n try {\r\n if (r instanceof Map) {\r\n return Object.fromEntries(r as any);\r\n }\r\n } catch {}\r\n return r;\r\n });\r\n }\r\n\r\n // Update WASM stats\r\n this.wasmStats = converter.getStats();\r\n\r\n // Emit output data\r\n if (output.length > 0) {\r\n // For JSON output with onRecords: parse incrementally\r\n if (needsJsonParsing) {\r\n const text = this.utf8Decoder.decode(output, { stream: true });\r\n const extracted = this.extractJsonObjects(text, jsonTextBuffer, insideArray);\r\n jsonTextBuffer = extracted.remaining;\r\n insideArray = extracted.insideArray;\r\n \r\n if (extracted.objects.length > 0) {\r\n recordBuffer.push(...extracted.objects);\r\n }\r\n }\r\n \r\n await this.emitData(output);\r\n if (this.state.cancelled) break;\r\n }\r\n\r\n // Buffer records\r\n if (records.length > 0) {\r\n recordBuffer.push(...records);\r\n }\r\n\r\n // Emit batches\r\n while (recordBuffer.length >= batchSize) {\r\n const batch = recordBuffer.splice(0, batchSize);\r\n await this.emitRecordBatch(batch);\r\n if (this.state.cancelled) break;\r\n }\r\n }\r\n\r\n // Finalize converter (even if cancelled, to ensure proper cleanup like closing JSON arrays)\r\n if (converter) {\r\n const finalOutput = converter.finish();\r\n \r\n // Only emit output if not cancelled\r\n if (!this.state.cancelled && finalOutput.length > 0) {\r\n // Parse final JSON chunk — flush the streaming decoder\r\n if (needsJsonParsing) {\r\n const text = this.utf8Decoder.decode(finalOutput);\r\n const extracted = this.extractJsonObjects(text, jsonTextBuffer, insideArray);\r\n jsonTextBuffer = extracted.remaining;\r\n \r\n if (extracted.objects.length > 0) {\r\n recordBuffer.push(...extracted.objects);\r\n }\r\n }\r\n \r\n await this.emitData(finalOutput, true);\r\n } else {\r\n // Even when there's no final output, flush any trailing bytes\r\n // buffered inside the streaming TextDecoder.\r\n await this.emitData(new Uint8Array(0), true);\r\n }\r\n \r\n // For non-JSON formats, parse any remaining records from final output\r\n if (this.options.onRecords && finalOutput.length > 0 && !needsJsonParsing && !this.state.cancelled) {\r\n const finalRecords = this.parseRecordsFromOutput(finalOutput);\r\n recordBuffer.push(...finalRecords);\r\n }\r\n\r\n // Emit remaining records (only if not cancelled)\r\n while (recordBuffer.length > 0 && !this.state.cancelled) {\r\n const batch = recordBuffer.splice(0, this.options.recordBatchSize);\r\n await this.emitRecordBatch(batch);\r\n }\r\n\r\n // Update final WASM stats\r\n this.wasmStats = converter.getStats();\r\n }\r\n\r\n // Complete successfully\r\n if (!this.state.cancelled) {\r\n this.complete();\r\n }\r\n\r\n } catch (error) {\r\n this.complete(error instanceof Error ? error : new Error(String(error)));\r\n }\r\n }\r\n\r\n private async emitData(chunk: Uint8Array, flush = false): Promise<void> {\r\n if (!this.options.emitOutput || !this.options.onData) return;\r\n \r\n const stats = this.createStatsSnapshot();\r\n // Decode Uint8Array to string for onData callback.\r\n // Use { stream: true } for intermediate chunks so that incomplete\r\n // multi-byte UTF-8 sequences at chunk boundaries are buffered and\r\n // handled correctly instead of being replaced with U+FFFD.\r\n const text = this.outputDecoder.decode(chunk, { stream: !flush });\r\n // Skip empty chunks (e.g. flush with no remaining buffered bytes)\r\n if (text.length === 0) return;\r\n if (typeof process !== 'undefined' && (process.env as any).TEST_DEBUG === '1') {\r\n try { console.log('[buddy-stream] emitData preview:', text.substring(0, 200)); } catch {}\r\n }\r\n \r\n try {\r\n const maybePromise = this.options.onData(text as any, stats);\r\n \r\n if (maybePromise instanceof Promise) {\r\n await maybePromise;\r\n }\r\n } catch (error) {\r\n // Propagate error from onData callback\r\n throw error;\r\n }\r\n }\r\n\r\n private async emitRecordBatch(batch: unknown[]): Promise<void> {\r\n if (batch.length === 0) return;\r\n // Debugging helper: optionally log batch contents when TEST_DEBUG=1\r\n if (typeof process !== 'undefined' && (process.env as any).TEST_DEBUG === '1') {\r\n try {\r\n const sample = batch.slice(0, 2).map((r) => {\r\n try {\r\n const info: any = { type: typeof r };\r\n if (r && typeof r === 'object') {\r\n info.constructor = (r as any).constructor ? (r as any).constructor.name : undefined;\r\n try { info.keys = Object.keys(r); } catch { info.keys = '[error]'; }\r\n try { info.ownProps = Object.getOwnPropertyNames(r); } catch { info.ownProps = '[error]'; }\r\n try {\r\n if (r instanceof Uint8Array || (typeof Buffer !== 'undefined' && (r as any) instanceof Buffer)) {\r\n info.isBuffer = true;\r\n info.length = (r as any).length;\r\n info.preview = Array.from((r as any).slice(0, 8));\r\n }\r\n } catch {}\r\n }\r\n return info;\r\n } catch (e) {\r\n return { error: String(e) };\r\n }\r\n });\r\n console.log('[buddy-stream] emitRecordBatch:', batch.length, sample);\r\n try {\r\n const preview = JSON.stringify(batch[0]);\r\n console.log('[buddy-stream] emitRecordBatch preview[0]:', preview);\r\n } catch (e) {\r\n console.log('[buddy-stream] emitRecordBatch preview[0]: [unserializable]');\r\n }\r\n } catch (e) {\r\n try { console.log('[buddy-stream] emitRecordBatch: debug error', e); } catch {}\r\n }\r\n }\r\n\r\n this.state.batchesOut += 1;\r\n this.state.recordsEmitted += batch.length;\r\n\r\n const stats = this.createStatsSnapshot();\r\n\r\n // If user provided onRecords, invoke it (may apply backpressure).\r\n if (this.options.onRecords) {\r\n try {\r\n // Log invocation for tricky encoding/debugging cases\r\n if (typeof process !== 'undefined' && (process.env as any).TEST_DEBUG === '1') {\r\n try {\r\n console.log('[buddy-stream] invoking onRecords, batch.length=', batch.length);\r\n } catch {}\r\n }\r\n\r\n const maybePromise = this.options.onRecords(\r\n this,\r\n batch as any,\r\n stats,\r\n this.state.recordsEmitted\r\n );\r\n\r\n // AUTOMATIC BACKPRESSURE: if onRecords returns a Promise, wait for it\r\n if (maybePromise instanceof Promise) {\r\n await maybePromise;\r\n }\r\n\r\n // Also respect manual pause\r\n await this.waitIfPaused();\r\n } catch (error) {\r\n // Propagate error from onRecords callback\r\n throw error;\r\n }\r\n }\r\n }\r\n\r\n private async waitIfPaused(): Promise<void> {\r\n while (this.state.paused && !this.state.cancelled) {\r\n await new Promise<void>(resolve => {\r\n // support multiple concurrent waiters by pushing resolvers to an array\r\n (this.state as any).pauseResolvers.push(resolve);\r\n });\r\n }\r\n }\r\n\r\n private complete(error?: Error): void {\r\n if (this.state.done) return;\r\n\r\n this.state.done = true;\r\n this.state.endedAt = Date.now();\r\n \r\n if (error) {\r\n this.state.error = error;\r\n }\r\n\r\n const finalStats = this.createStatsSnapshot();\r\n\r\n if (typeof process !== 'undefined' && (process.env as any).TEST_DEBUG === '1') {\r\n try {\r\n console.log('[buddy-stream] complete: finalStats=', finalStats, 'wasmStats=', this.wasmStats);\r\n } catch {}\r\n }\r\n\r\n if (error) {\r\n // Call onError if provided\r\n if (this.options.onError) {\r\n try {\r\n this.options.onError(error, this, finalStats);\r\n } catch (e) {\r\n console.error('[buddy-stream] Error in onError callback:', e);\r\n }\r\n } else {\r\n // Log warning if no error handler (fire-and-forget safety)\r\n console.warn('[buddy-stream] Stream error (no onError handler):', error.message);\r\n }\r\n this.doneReject(error);\r\n } else {\r\n // Call onDone if provided. If it throws, propagate that error to\r\n // the done promise (and to onError) so callers can observe it.\r\n if (this.options.onDone) {\r\n try {\r\n this.options.onDone(finalStats);\r\n } catch (e) {\r\n const err = e instanceof Error ? e : new Error(String(e));\r\n // Notify onError if present\r\n if (this.options.onError) {\r\n try {\r\n this.options.onError(err, this, finalStats);\r\n } catch (e2) {\r\n console.error('[buddy-stream] Error in onError callback:', e2);\r\n }\r\n } else {\r\n console.warn('[buddy-stream] Stream error (no onError handler):', err.message);\r\n }\r\n this.doneReject(err);\r\n return;\r\n }\r\n }\r\n this.doneResolve(finalStats);\r\n }\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Stats\r\n // ==========================================================================\r\n\r\n private createStatsSnapshot(): BuddyStats {\r\n const now = Date.now();\r\n const durationMs = this.state.endedAt \r\n ? this.state.endedAt - this.state.startedAt\r\n : now - this.state.startedAt;\r\n\r\n // WASM is single source of truth for bytes/throughput.\r\n // For recordsOut: when onRecords callback is provided, use recordsEmitted (sum of batches).\r\n // Otherwise, use WASM's recordsProcessed (for conversions without record callbacks).\r\n const ws: any = this.wasmStats;\r\n const bytesIn = ws?.bytesIn ?? ws?.bytes_in ?? 0;\r\n const bytesOut = ws?.bytesOut ?? ws?.bytes_out ?? 0;\r\n const recordsOut = this.options.onRecords\r\n ? this.state.recordsEmitted\r\n : (ws?.recordsProcessed ?? ws?.records_processed ?? 0);\r\n const throughput = ws?.throughputMbPerSec ?? ws?.throughput_mb_per_sec ?? 0;\r\n\r\n const stats: BuddyStats = {\r\n bytesIn,\r\n bytesOut,\r\n recordsOut,\r\n batchesOut: this.state.batchesOut,\r\n durationMs,\r\n throughputMbPerSec: throughput,\r\n startedAt: this.state.startedAt,\r\n endedAt: this.state.endedAt,\r\n isPaused: this.state.paused,\r\n isDone: this.state.done,\r\n error: this.state.error,\r\n };\r\n\r\n return Object.freeze(stats);\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Input Normalization\r\n // ==========================================================================\r\n\r\n private async *toAsyncIterator(input: BuddyInput): AsyncGenerator<Uint8Array> {\r\n // String: URL or raw data\r\n if (typeof input === 'string') {\r\n if (input.startsWith('http://') || input.startsWith('https://')) {\r\n const response = await fetch(input);\r\n if (!response.ok) {\r\n throw new Error(`Fetch failed: ${response.status} ${response.statusText}`);\r\n }\r\n if (!response.body) {\r\n throw new Error('Response body is null');\r\n }\r\n yield* this.fromReadableStream(response.body);\r\n return;\r\n }\r\n yield new TextEncoder().encode(input);\r\n return;\r\n }\r\n\r\n if (input instanceof Uint8Array) {\r\n yield input;\r\n return;\r\n }\r\n\r\n if (input instanceof ArrayBuffer) {\r\n yield new Uint8Array(input);\r\n return;\r\n }\r\n\r\n if (this.isReadableStream(input)) {\r\n yield* this.fromReadableStream(input);\r\n return;\r\n }\r\n\r\n if (typeof Blob !== 'undefined' && input instanceof Blob) {\r\n const stream = input.stream() as ReadableStream<Uint8Array>;\r\n yield* this.fromReadableStream(stream);\r\n return;\r\n }\r\n\r\n if (this.isAsyncIterable(input)) {\r\n for await (const chunk of input) {\r\n yield this.toUint8Array(chunk);\r\n }\r\n return;\r\n }\r\n\r\n if (this.isNodeReadable(input)) {\r\n yield* this.fromNodeReadable(input);\r\n return;\r\n }\r\n\r\n throw new Error('Unsupported input type');\r\n }\r\n\r\n private isReadableStream(value: unknown): value is ReadableStream<Uint8Array> {\r\n return typeof ReadableStream !== 'undefined' && value instanceof ReadableStream;\r\n }\r\n\r\n private isAsyncIterable(value: unknown): value is AsyncIterable<unknown> {\r\n return value != null && typeof (value as AsyncIterable<unknown>)[Symbol.asyncIterator] === 'function';\r\n }\r\n\r\n private isNodeReadable(value: unknown): value is NodeReadableCompat {\r\n const v = value as NodeReadableCompat;\r\n return v != null && typeof v.on === 'function' && typeof v.read === 'function';\r\n }\r\n\r\n private async *fromReadableStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<Uint8Array> {\r\n const reader = stream.getReader();\r\n try {\r\n while (true) {\r\n const { done, value } = await reader.read();\r\n if (done) break;\r\n yield value;\r\n }\r\n } finally {\r\n reader.releaseLock();\r\n }\r\n }\r\n\r\n private async *fromNodeReadable(stream: NodeReadableCompat): AsyncGenerator<Uint8Array> {\r\n if (stream[Symbol.asyncIterator]) {\r\n const iter = stream[Symbol.asyncIterator]!();\r\n for await (const chunk of { [Symbol.asyncIterator]: () => iter }) {\r\n yield this.toUint8Array(chunk);\r\n }\r\n } else {\r\n throw new Error('Node.js Readable stream must be async iterable');\r\n }\r\n }\r\n\r\n private toUint8Array(chunk: unknown): Uint8Array {\r\n if (chunk instanceof Uint8Array) return chunk;\r\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(chunk)) return new Uint8Array(chunk);\r\n if (typeof chunk === 'string') return new TextEncoder().encode(chunk);\r\n if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk);\r\n throw new Error('Unexpected chunk type');\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Incremental JSON Array Parsing\r\n // ==========================================================================\r\n\r\n /**\r\n * Extract complete JSON objects from streaming JSON array output.\r\n * Handles incomplete objects across chunk boundaries.\r\n * \r\n * @param newText - New text chunk from current output\r\n * @param buffer - Previously buffered incomplete text\r\n * @param wasInsideArray - Whether we were inside array from previous chunk\r\n * @returns Extracted objects and remaining incomplete text\r\n */\r\n private extractJsonObjects(\r\n newText: string,\r\n buffer: string,\r\n wasInsideArray: boolean\r\n ): { objects: unknown[]; remaining: string; insideArray: boolean } {\r\n const objects: unknown[] = [];\r\n const fullText = buffer + newText;\r\n let pos = 0;\r\n let insideArray = wasInsideArray;\r\n \r\n // Find array start if not already inside\r\n if (!insideArray) {\r\n const arrayStart = fullText.indexOf('[');\r\n if (arrayStart === -1) {\r\n return { objects, remaining: fullText, insideArray: false };\r\n }\r\n pos = arrayStart + 1;\r\n insideArray = true;\r\n }\r\n \r\n // Parse objects from array\r\n let depth = 0;\r\n let inString = false;\r\n let escape = false;\r\n let objectStart = -1;\r\n \r\n for (let i = pos; i < fullText.length; i++) {\r\n const char = fullText[i];\r\n \r\n if (escape) {\r\n escape = false;\r\n continue;\r\n }\r\n \r\n if (char === '\\\\') {\r\n escape = true;\r\n continue;\r\n }\r\n \r\n if (char === '\"') {\r\n inString = !inString;\r\n continue;\r\n }\r\n \r\n if (inString) continue;\r\n \r\n if (char === '{') {\r\n if (depth === 0) objectStart = i;\r\n depth++;\r\n } else if (char === '}') {\r\n depth--;\r\n if (depth === 0 && objectStart !== -1) {\r\n // Complete object found\r\n const objectText = fullText.substring(objectStart, i + 1);\r\n try {\r\n const obj = JSON.parse(objectText);\r\n objects.push(obj);\r\n } catch (e) {\r\n if (this.options.debug) {\r\n console.warn('[buddy-stream] Failed to parse JSON object:', e);\r\n }\r\n }\r\n objectStart = -1;\r\n pos = i + 1;\r\n }\r\n } else if (char === ']' && depth === 0) {\r\n // End of array\r\n insideArray = false;\r\n pos = i + 1;\r\n break;\r\n }\r\n }\r\n \r\n // Return remaining incomplete text\r\n const remaining = objectStart !== -1 ? fullText.substring(objectStart) : fullText.substring(pos);\r\n return { objects, remaining, insideArray };\r\n }\r\n\r\n // ==========================================================================\r\n // Internal: Record Parsing (Fallback)\r\n // ==========================================================================\r\n\r\n /**\r\n * Parse records from output bytes - FALLBACK ONLY\r\n * WASM should return pre-parsed records, this is for compatibility\r\n */\r\n private parseRecordsFromOutput(output: Uint8Array): unknown[] {\r\n if (output.length === 0) return [];\r\n\r\n const format = this.options.outputFormat || 'ndjson';\r\n // Use shared UTF-8 decoder (ignoring BOM) to ensure consistent behavior\r\n const text = this.utf8Decoder.decode(output);\r\n\r\n try {\r\n switch (format) {\r\n case 'ndjson':\r\n return text\r\n .split('\\n')\r\n .filter(line => line.trim().length > 0)\r\n .map(line => JSON.parse(line));\r\n \r\n case 'json':\r\n const parsed = JSON.parse(text);\r\n return Array.isArray(parsed) ? parsed : [parsed];\r\n \r\n case 'csv':\r\n case 'xml':\r\n // These should be parsed by WASM\r\n if (this.options.debug) {\r\n console.warn('[buddy-stream] WASM did not return parsed records for', format);\r\n }\r\n return [];\r\n \r\n default:\r\n return [];\r\n }\r\n } catch {\r\n return []; // Partial data - normal during streaming\r\n }\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// Factory Function\r\n// ============================================================================\r\n\r\n/**\r\n * Create a BuddyController for streaming conversion.\r\n * This is the main entry point used by ConvertBuddy.stream()\r\n * \r\n * @internal\r\n */\r\nexport function createBuddyController(\r\n input: BuddyInput,\r\n converterFactory: () => Promise<ConverterInterface>,\r\n options: BuddyStreamOptions\r\n): BuddyController {\r\n return new BuddyControllerImpl(input, converterFactory, options);\r\n}\r\n\r\n// ============================================================================\r\n// Convenience: Standalone stream function\r\n// ============================================================================\r\n\r\n// This will be imported by index.ts and wired up with the actual ConvertBuddy.create\r\nlet _converterFactoryFn: ((opts: BuddyStreamOptions) => Promise<ConverterInterface>) | null = null;\r\n\r\n/**\r\n * Register the converter factory function (called by index.ts at init)\r\n * @internal\r\n */\r\nexport function _setConverterFactory(fn: (opts: BuddyStreamOptions) => Promise<ConverterInterface>): void {\r\n _converterFactoryFn = fn;\r\n}\r\n\r\n/**\r\n * Standalone stream function for simple usage\r\n * \r\n * @example\r\n * ```ts\r\n * import { stream } from 'convert-buddy-js';\r\n * \r\n * const buddy = stream(csvData, {\r\n * inputFormat: 'csv',\r\n * outputFormat: 'ndjson',\r\n * onRecords: async (buddy, records, stats, count) => {\r\n * await saveRecords(records);\r\n * }\r\n * });\r\n * \r\n * await buddy.done;\r\n * ```\r\n */\r\nexport function stream<T = unknown>(\r\n input: BuddyInput,\r\n options: BuddyStreamOptions<T>\r\n): BuddyController {\r\n if (!_converterFactoryFn) {\r\n throw new Error('Converter factory not initialized. Import from convert-buddy-js main entry point.');\r\n }\r\n \r\n return createBuddyController(\r\n input,\r\n () => _converterFactoryFn!(options as BuddyStreamOptions),\r\n options as BuddyStreamOptions\r\n );\r\n}\r\n"],"mappings":"AA2DA,MAAM,oBAA+C;AAAA,EAanD,YACU,OACA,kBACR,SACA;AAHQ;AACA;AAIR,SAAK,UAAU;AAAA,MACb,iBAAiB;AAAA,MACjB,YAAY,QAAQ,iBAAiB;AAAA,MACrC,OAAO;AAAA,MACP,GAAG;AAAA,IACL;AAGA,SAAK,QAAQ;AAAA,MACX,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,MAAM;AAAA,MACN,WAAW,KAAK,IAAI;AAAA,MACpB,YAAY;AAAA,MACZ,gBAAgB;AAAA,MAChB,gBAAgB,CAAC;AAAA,IACnB;AAGA,SAAK,cAAc,IAAI,QAAoB,CAAC,SAAS,WAAW;AAC9D,WAAK,cAAc;AACnB,WAAK,aAAa;AAAA,IACpB,CAAC;AAID,SAAK,YAAY,MAAM,MAAM;AAAA,IAE7B,CAAC;AAID,mBAAe,MAAM,KAAK,gBAAgB,CAAC;AAAA,EAC7C;AAAA,EAnDQ;AAAA,EACA;AAAA,EACA,YAA0B;AAAA,EAC1B,cAAc,IAAI,YAAY,SAAS,EAAE,WAAW,MAAM,OAAO,MAAM,CAAC;AAAA;AAAA,EAExE,gBAAgB,IAAI,YAAY,SAAS,EAAE,WAAW,MAAM,OAAO,MAAM,CAAC;AAAA;AAAA,EAG1E;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EA+CR,QAAc;AACZ,QAAI,KAAK,MAAM,QAAQ,KAAK,MAAM,UAAW;AAC7C,SAAK,MAAM,SAAS;AACpB,QAAI,KAAK,QAAQ,OAAO;AACtB,cAAQ,IAAI,uBAAuB;AAAA,IACrC;AAAA,EACF;AAAA,EAEA,SAAe;AACb,QAAI,KAAK,MAAM,QAAQ,KAAK,MAAM,UAAW;AAC7C,SAAK,MAAM,SAAS;AACpB,QAAI,KAAK,QAAQ,OAAO;AACtB,cAAQ,IAAI,wBAAwB;AAAA,IACtC;AAEA,eAAW,YAAY,KAAK,MAAM,gBAAgB;AAChD,UAAI;AAAE,iBAAS;AAAA,MAAG,QAAQ;AAAA,MAAgB;AAAA,IAC5C;AACA,SAAK,MAAM,eAAe,SAAS;AAAA,EACrC;AAAA,EAEA,OAAO,QAA+B;AACpC,QAAI,KAAK,MAAM,QAAQ,KAAK,MAAM,UAAW;AAE7C,SAAK,MAAM,YAAY;AACvB,SAAK,MAAM,QAAQ,kBAAkB,QACjC,SACA,IAAI,MAAM,UAAU,kBAAkB;AAE1C,QAAI,KAAK,QAAQ,OAAO;AACtB,cAAQ,IAAI,6BAA6B,KAAK,MAAM,MAAM,OAAO;AAAA,IACnE;AAGA,SAAK,OAAO;AAGZ,SAAK,SAAS,KAAK,MAAM,KAAK;AAAA,EAChC;AAAA,EAEA,QAAoB;AAClB,WAAO,KAAK,oBAAoB;AAAA,EAClC;AAAA,EAEA,IAAI,OAA4B;AAC9B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,kBAAiC;AAC7C,QAAI,YAAuC;AAE3C,QAAI;AAEF,kBAAY,MAAM,KAAK,iBAAiB;AAGxC,YAAM,WAAW,KAAK,gBAAgB,KAAK,KAAK;AAChD,YAAM,YAAY,KAAK,QAAQ;AAC/B,UAAI,eAA0B,CAAC;AAI/B,YAAM,mBAAmB,KAAK,QAAQ,iBAAiB,UAAU,CAAC,CAAC,KAAK,QAAQ;AAChF,UAAI,iBAAiB;AACrB,UAAI,cAAc;AAElB,uBAAiB,SAAS,UAAU;AAElC,YAAI,KAAK,MAAM,UAAW;AAG1B,cAAM,KAAK,aAAa;AACxB,YAAI,KAAK,MAAM,UAAW;AAK1B,cAAM,eAAe,CAAC,CAAC,KAAK,QAAQ,aAAa,CAAC;AAClD,cAAM,SAAS,UAAU,gBAAgB,OAAO,YAAY;AAE5D,YAAI;AACJ,YAAI,UAAqB,CAAC;AAE1B,YAAI,OAAO,WAAW,YAAY,YAAY,QAAQ;AACpD,mBAAS,OAAO;AAChB,oBAAU,OAAO,WAAW,CAAC;AAAA,QAC/B,OAAO;AACL,mBAAS;AAET,cAAI,gBAAgB,OAAO,SAAS,GAAG;AACrC,sBAAU,KAAK,uBAAuB,MAAM;AAAA,UAC9C;AAAA,QACF;AAIA,YAAI,WAAW,QAAQ,SAAS,GAAG;AACjC,oBAAU,QAAQ,IAAI,CAAC,MAAM;AAC3B,gBAAI;AACF,kBAAI,aAAa,KAAK;AACpB,uBAAO,OAAO,YAAY,CAAQ;AAAA,cACpC;AAAA,YACF,QAAQ;AAAA,YAAC;AACT,mBAAO;AAAA,UACT,CAAC;AAAA,QACH;AAGA,aAAK,YAAY,UAAU,SAAS;AAGpC,YAAI,OAAO,SAAS,GAAG;AAErB,cAAI,kBAAkB;AACpB,kBAAM,OAAO,KAAK,YAAY,OAAO,QAAQ,EAAE,QAAQ,KAAK,CAAC;AAC7D,kBAAM,YAAY,KAAK,mBAAmB,MAAM,gBAAgB,WAAW;AAC3E,6BAAiB,UAAU;AAC3B,0BAAc,UAAU;AAExB,gBAAI,UAAU,QAAQ,SAAS,GAAG;AAChC,2BAAa,KAAK,GAAG,UAAU,OAAO;AAAA,YACxC;AAAA,UACF;AAEA,gBAAM,KAAK,SAAS,MAAM;AAC1B,cAAI,KAAK,MAAM,UAAW;AAAA,QAC5B;AAGA,YAAI,QAAQ,SAAS,GAAG;AACtB,uBAAa,KAAK,GAAG,OAAO;AAAA,QAC9B;AAGA,eAAO,aAAa,UAAU,WAAW;AACvC,gBAAM,QAAQ,aAAa,OAAO,GAAG,SAAS;AAC9C,gBAAM,KAAK,gBAAgB,KAAK;AAChC,cAAI,KAAK,MAAM,UAAW;AAAA,QAC5B;AAAA,MACF;AAGA,UAAI,WAAW;AACb,cAAM,cAAc,UAAU,OAAO;AAGrC,YAAI,CAAC,KAAK,MAAM,aAAa,YAAY,SAAS,GAAG;AAEnD,cAAI,kBAAkB;AACpB,kBAAM,OAAO,KAAK,YAAY,OAAO,WAAW;AAChD,kBAAM,YAAY,KAAK,mBAAmB,MAAM,gBAAgB,WAAW;AAC3E,6BAAiB,UAAU;AAE3B,gBAAI,UAAU,QAAQ,SAAS,GAAG;AAChC,2BAAa,KAAK,GAAG,UAAU,OAAO;AAAA,YACxC;AAAA,UACF;AAEA,gBAAM,KAAK,SAAS,aAAa,IAAI;AAAA,QACvC,OAAO;AAGL,gBAAM,KAAK,SAAS,IAAI,WAAW,CAAC,GAAG,IAAI;AAAA,QAC7C;AAGA,YAAI,KAAK,QAAQ,aAAa,YAAY,SAAS,KAAK,CAAC,oBAAoB,CAAC,KAAK,MAAM,WAAW;AAClG,gBAAM,eAAe,KAAK,uBAAuB,WAAW;AAC5D,uBAAa,KAAK,GAAG,YAAY;AAAA,QACnC;AAGA,eAAO,aAAa,SAAS,KAAK,CAAC,KAAK,MAAM,WAAW;AACvD,gBAAM,QAAQ,aAAa,OAAO,GAAG,KAAK,QAAQ,eAAe;AACjE,gBAAM,KAAK,gBAAgB,KAAK;AAAA,QAClC;AAGA,aAAK,YAAY,UAAU,SAAS;AAAA,MACtC;AAGA,UAAI,CAAC,KAAK,MAAM,WAAW;AACzB,aAAK,SAAS;AAAA,MAChB;AAAA,IAEF,SAAS,OAAO;AACd,WAAK,SAAS,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC,CAAC;AAAA,IACzE;AAAA,EACF;AAAA,EAEA,MAAc,SAAS,OAAmB,QAAQ,OAAsB;AACtE,QAAI,CAAC,KAAK,QAAQ,cAAc,CAAC,KAAK,QAAQ,OAAQ;AAEtD,UAAM,QAAQ,KAAK,oBAAoB;AAKvC,UAAM,OAAO,KAAK,cAAc,OAAO,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC;AAEhE,QAAI,KAAK,WAAW,EAAG;AACvB,QAAI,OAAO,YAAY,eAAgB,QAAQ,IAAY,eAAe,KAAK;AAC7E,UAAI;AAAE,gBAAQ,IAAI,oCAAoC,KAAK,UAAU,GAAG,GAAG,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAC;AAAA,IAC1F;AAEA,QAAI;AACF,YAAM,eAAe,KAAK,QAAQ,OAAO,MAAa,KAAK;AAE3D,UAAI,wBAAwB,SAAS;AACnC,cAAM;AAAA,MACR;AAAA,IACF,SAAS,OAAO;AAEd,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,gBAAgB,OAAiC;AAC7D,QAAI,MAAM,WAAW,EAAG;AAExB,QAAI,OAAO,YAAY,eAAgB,QAAQ,IAAY,eAAe,KAAK;AAC3E,UAAI;AACF,cAAM,SAAS,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,MAAM;AAC1C,cAAI;AACF,kBAAM,OAAY,EAAE,MAAM,OAAO,EAAE;AACnC,gBAAI,KAAK,OAAO,MAAM,UAAU;AAC9B,mBAAK,cAAe,EAAU,cAAe,EAAU,YAAY,OAAO;AAC1E,kBAAI;AAAE,qBAAK,OAAO,OAAO,KAAK,CAAC;AAAA,cAAG,QAAQ;AAAE,qBAAK,OAAO;AAAA,cAAW;AACnE,kBAAI;AAAE,qBAAK,WAAW,OAAO,oBAAoB,CAAC;AAAA,cAAG,QAAQ;AAAE,qBAAK,WAAW;AAAA,cAAW;AAC1F,kBAAI;AACF,oBAAI,aAAa,cAAe,OAAO,WAAW,eAAgB,aAAqB,QAAS;AAC9F,uBAAK,WAAW;AAChB,uBAAK,SAAU,EAAU;AACzB,uBAAK,UAAU,MAAM,KAAM,EAAU,MAAM,GAAG,CAAC,CAAC;AAAA,gBAClD;AAAA,cACF,QAAQ;AAAA,cAAC;AAAA,YACX;AACA,mBAAO;AAAA,UACT,SAAS,GAAG;AACV,mBAAO,EAAE,OAAO,OAAO,CAAC,EAAE;AAAA,UAC5B;AAAA,QACF,CAAC;AACD,gBAAQ,IAAI,mCAAmC,MAAM,QAAQ,MAAM;AACnE,YAAI;AACF,gBAAM,UAAU,KAAK,UAAU,MAAM,CAAC,CAAC;AACvC,kBAAQ,IAAI,8CAA8C,OAAO;AAAA,QACnE,SAAS,GAAG;AACV,kBAAQ,IAAI,6DAA6D;AAAA,QAC3E;AAAA,MACF,SAAS,GAAG;AACV,YAAI;AAAE,kBAAQ,IAAI,+CAA+C,CAAC;AAAA,QAAG,QAAQ;AAAA,QAAC;AAAA,MAChF;AAAA,IACJ;AAEA,SAAK,MAAM,cAAc;AACzB,SAAK,MAAM,kBAAkB,MAAM;AAEnC,UAAM,QAAQ,KAAK,oBAAoB;AAGvC,QAAI,KAAK,QAAQ,WAAW;AAC1B,UAAI;AAEF,YAAI,OAAO,YAAY,eAAgB,QAAQ,IAAY,eAAe,KAAK;AAC7E,cAAI;AACF,oBAAQ,IAAI,oDAAoD,MAAM,MAAM;AAAA,UAC9E,QAAQ;AAAA,UAAC;AAAA,QACX;AAEA,cAAM,eAAe,KAAK,QAAQ;AAAA,UAChC;AAAA,UACA;AAAA,UACA;AAAA,UACA,KAAK,MAAM;AAAA,QACb;AAGA,YAAI,wBAAwB,SAAS;AACnC,gBAAM;AAAA,QACR;AAGA,cAAM,KAAK,aAAa;AAAA,MAC1B,SAAS,OAAO;AAEd,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,eAA8B;AAC1C,WAAO,KAAK,MAAM,UAAU,CAAC,KAAK,MAAM,WAAW;AACjD,YAAM,IAAI,QAAc,aAAW;AAEjC,QAAC,KAAK,MAAc,eAAe,KAAK,OAAO;AAAA,MACjD,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,SAAS,OAAqB;AACpC,QAAI,KAAK,MAAM,KAAM;AAErB,SAAK,MAAM,OAAO;AAClB,SAAK,MAAM,UAAU,KAAK,IAAI;AAE9B,QAAI,OAAO;AACT,WAAK,MAAM,QAAQ;AAAA,IACrB;AAEA,UAAM,aAAa,KAAK,oBAAoB;AAE5C,QAAI,OAAO,YAAY,eAAgB,QAAQ,IAAY,eAAe,KAAK;AAC7E,UAAI;AACF,gBAAQ,IAAI,wCAAwC,YAAY,cAAc,KAAK,SAAS;AAAA,MAC9F,QAAQ;AAAA,MAAC;AAAA,IACX;AAEA,QAAI,OAAO;AAET,UAAI,KAAK,QAAQ,SAAS;AACxB,YAAI;AACF,eAAK,QAAQ,QAAQ,OAAO,MAAM,UAAU;AAAA,QAC9C,SAAS,GAAG;AACV,kBAAQ,MAAM,6CAA6C,CAAC;AAAA,QAC9D;AAAA,MACF,OAAO;AAEL,gBAAQ,KAAK,qDAAqD,MAAM,OAAO;AAAA,MACjF;AACA,WAAK,WAAW,KAAK;AAAA,IACvB,OAAO;AAGL,UAAI,KAAK,QAAQ,QAAQ;AACvB,YAAI;AACF,eAAK,QAAQ,OAAO,UAAU;AAAA,QAChC,SAAS,GAAG;AACV,gBAAM,MAAM,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC;AAExD,cAAI,KAAK,QAAQ,SAAS;AACxB,gBAAI;AACF,mBAAK,QAAQ,QAAQ,KAAK,MAAM,UAAU;AAAA,YAC5C,SAAS,IAAI;AACX,sBAAQ,MAAM,6CAA6C,EAAE;AAAA,YAC/D;AAAA,UACF,OAAO;AACL,oBAAQ,KAAK,qDAAqD,IAAI,OAAO;AAAA,UAC/E;AACA,eAAK,WAAW,GAAG;AACnB;AAAA,QACF;AAAA,MACF;AACA,WAAK,YAAY,UAAU;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,sBAAkC;AACxC,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,aAAa,KAAK,MAAM,UAC1B,KAAK,MAAM,UAAU,KAAK,MAAM,YAChC,MAAM,KAAK,MAAM;AAKrB,UAAM,KAAU,KAAK;AACrB,UAAM,UAAU,IAAI,WAAW,IAAI,YAAY;AAC/C,UAAM,WAAW,IAAI,YAAY,IAAI,aAAa;AAClD,UAAM,aAAa,KAAK,QAAQ,YAC5B,KAAK,MAAM,iBACV,IAAI,oBAAoB,IAAI,qBAAqB;AACtD,UAAM,aAAa,IAAI,sBAAsB,IAAI,yBAAyB;AAE1E,UAAM,QAAoB;AAAA,MACxB;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY,KAAK,MAAM;AAAA,MACvB;AAAA,MACA,oBAAoB;AAAA,MACpB,WAAW,KAAK,MAAM;AAAA,MACtB,SAAS,KAAK,MAAM;AAAA,MACpB,UAAU,KAAK,MAAM;AAAA,MACrB,QAAQ,KAAK,MAAM;AAAA,MACnB,OAAO,KAAK,MAAM;AAAA,IACpB;AAEA,WAAO,OAAO,OAAO,KAAK;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA,EAMA,OAAe,gBAAgB,OAA+C;AAE5E,QAAI,OAAO,UAAU,UAAU;AAC7B,UAAI,MAAM,WAAW,SAAS,KAAK,MAAM,WAAW,UAAU,GAAG;AAC/D,cAAM,WAAW,MAAM,MAAM,KAAK;AAClC,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,IAAI,MAAM,iBAAiB,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,QAC3E;AACA,YAAI,CAAC,SAAS,MAAM;AAClB,gBAAM,IAAI,MAAM,uBAAuB;AAAA,QACzC;AACA,eAAO,KAAK,mBAAmB,SAAS,IAAI;AAC5C;AAAA,MACF;AACA,YAAM,IAAI,YAAY,EAAE,OAAO,KAAK;AACpC;AAAA,IACF;AAEA,QAAI,iBAAiB,YAAY;AAC/B,YAAM;AACN;AAAA,IACF;AAEA,QAAI,iBAAiB,aAAa;AAChC,YAAM,IAAI,WAAW,KAAK;AAC1B;AAAA,IACF;AAEA,QAAI,KAAK,iBAAiB,KAAK,GAAG;AAChC,aAAO,KAAK,mBAAmB,KAAK;AACpC;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,eAAe,iBAAiB,MAAM;AACxD,YAAMA,UAAS,MAAM,OAAO;AAC5B,aAAO,KAAK,mBAAmBA,OAAM;AACrC;AAAA,IACF;AAEA,QAAI,KAAK,gBAAgB,KAAK,GAAG;AAC/B,uBAAiB,SAAS,OAAO;AAC/B,cAAM,KAAK,aAAa,KAAK;AAAA,MAC/B;AACA;AAAA,IACF;AAEA,QAAI,KAAK,eAAe,KAAK,GAAG;AAC9B,aAAO,KAAK,iBAAiB,KAAK;AAClC;AAAA,IACF;AAEA,UAAM,IAAI,MAAM,wBAAwB;AAAA,EAC1C;AAAA,EAEQ,iBAAiB,OAAqD;AAC5E,WAAO,OAAO,mBAAmB,eAAe,iBAAiB;AAAA,EACnE;AAAA,EAEQ,gBAAgB,OAAiD;AACvE,WAAO,SAAS,QAAQ,OAAQ,MAAiC,OAAO,aAAa,MAAM;AAAA,EAC7F;AAAA,EAEQ,eAAe,OAA6C;AAClE,UAAM,IAAI;AACV,WAAO,KAAK,QAAQ,OAAO,EAAE,OAAO,cAAc,OAAO,EAAE,SAAS;AAAA,EACtE;AAAA,EAEA,OAAe,mBAAmBA,SAAgE;AAChG,UAAM,SAASA,QAAO,UAAU;AAChC,QAAI;AACF,aAAO,MAAM;AACX,cAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,YAAI,KAAM;AACV,cAAM;AAAA,MACR;AAAA,IACF,UAAE;AACA,aAAO,YAAY;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,OAAe,iBAAiBA,SAAwD;AACtF,QAAIA,QAAO,OAAO,aAAa,GAAG;AAChC,YAAM,OAAOA,QAAO,OAAO,aAAa,EAAG;AAC3C,uBAAiB,SAAS,EAAE,CAAC,OAAO,aAAa,GAAG,MAAM,KAAK,GAAG;AAChE,cAAM,KAAK,aAAa,KAAK;AAAA,MAC/B;AAAA,IACF,OAAO;AACL,YAAM,IAAI,MAAM,gDAAgD;AAAA,IAClE;AAAA,EACF;AAAA,EAEQ,aAAa,OAA4B;AAC/C,QAAI,iBAAiB,WAAY,QAAO;AACxC,QAAI,OAAO,WAAW,eAAe,OAAO,SAAS,KAAK,EAAG,QAAO,IAAI,WAAW,KAAK;AACxF,QAAI,OAAO,UAAU,SAAU,QAAO,IAAI,YAAY,EAAE,OAAO,KAAK;AACpE,QAAI,iBAAiB,YAAa,QAAO,IAAI,WAAW,KAAK;AAC7D,UAAM,IAAI,MAAM,uBAAuB;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeQ,mBACN,SACA,QACA,gBACiE;AACjE,UAAM,UAAqB,CAAC;AAC5B,UAAM,WAAW,SAAS;AAC1B,QAAI,MAAM;AACV,QAAI,cAAc;AAGlB,QAAI,CAAC,aAAa;AAChB,YAAM,aAAa,SAAS,QAAQ,GAAG;AACvC,UAAI,eAAe,IAAI;AACrB,eAAO,EAAE,SAAS,WAAW,UAAU,aAAa,MAAM;AAAA,MAC5D;AACA,YAAM,aAAa;AACnB,oBAAc;AAAA,IAChB;AAGA,QAAI,QAAQ;AACZ,QAAI,WAAW;AACf,QAAI,SAAS;AACb,QAAI,cAAc;AAElB,aAAS,IAAI,KAAK,IAAI,SAAS,QAAQ,KAAK;AAC1C,YAAM,OAAO,SAAS,CAAC;AAEvB,UAAI,QAAQ;AACV,iBAAS;AACT;AAAA,MACF;AAEA,UAAI,SAAS,MAAM;AACjB,iBAAS;AACT;AAAA,MACF;AAEA,UAAI,SAAS,KAAK;AAChB,mBAAW,CAAC;AACZ;AAAA,MACF;AAEA,UAAI,SAAU;AAEd,UAAI,SAAS,KAAK;AAChB,YAAI,UAAU,EAAG,eAAc;AAC/B;AAAA,MACF,WAAW,SAAS,KAAK;AACvB;AACA,YAAI,UAAU,KAAK,gBAAgB,IAAI;AAErC,gBAAM,aAAa,SAAS,UAAU,aAAa,IAAI,CAAC;AACxD,cAAI;AACF,kBAAM,MAAM,KAAK,MAAM,UAAU;AACjC,oBAAQ,KAAK,GAAG;AAAA,UAClB,SAAS,GAAG;AACV,gBAAI,KAAK,QAAQ,OAAO;AACtB,sBAAQ,KAAK,+CAA+C,CAAC;AAAA,YAC/D;AAAA,UACF;AACA,wBAAc;AACd,gBAAM,IAAI;AAAA,QACZ;AAAA,MACF,WAAW,SAAS,OAAO,UAAU,GAAG;AAEtC,sBAAc;AACd,cAAM,IAAI;AACV;AAAA,MACF;AAAA,IACF;AAGA,UAAM,YAAY,gBAAgB,KAAK,SAAS,UAAU,WAAW,IAAI,SAAS,UAAU,GAAG;AAC/F,WAAO,EAAE,SAAS,WAAW,YAAY;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,uBAAuB,QAA+B;AAC5D,QAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AAEjC,UAAM,SAAS,KAAK,QAAQ,gBAAgB;AAE5C,UAAM,OAAO,KAAK,YAAY,OAAO,MAAM;AAE3C,QAAI;AACF,cAAQ,QAAQ;AAAA,QACd,KAAK;AACH,iBAAO,KACJ,MAAM,IAAI,EACV,OAAO,UAAQ,KAAK,KAAK,EAAE,SAAS,CAAC,EACrC,IAAI,UAAQ,KAAK,MAAM,IAAI,CAAC;AAAA,QAEjC,KAAK;AACH,gBAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,iBAAO,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM;AAAA,QAEjD,KAAK;AAAA,QACL,KAAK;AAEH,cAAI,KAAK,QAAQ,OAAO;AACtB,oBAAQ,KAAK,yDAAyD,MAAM;AAAA,UAC9E;AACA,iBAAO,CAAC;AAAA,QAEV;AACE,iBAAO,CAAC;AAAA,MACZ;AAAA,IACF,QAAQ;AACN,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;AAYO,SAAS,sBACd,OACA,kBACA,SACiB;AACjB,SAAO,IAAI,oBAAoB,OAAO,kBAAkB,OAAO;AACjE;AAOA,IAAI,sBAA0F;AAMvF,SAAS,qBAAqB,IAAqE;AACxG,wBAAsB;AACxB;AAoBO,SAAS,OACd,OACA,SACiB;AACjB,MAAI,CAAC,qBAAqB;AACxB,UAAM,IAAI,MAAM,mFAAmF;AAAA,EACrG;AAEA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,oBAAqB,OAA6B;AAAA,IACxD;AAAA,EACF;AACF;","names":["stream"]}
|
|
Binary file
|
|
Binary file
|