UNPKG

@yoyo-org/progressive-json

Version:

Stream and render JSON data as it arrives - perfect for AI responses, large datasets, and real-time updates

104 lines (103 loc) 3.58 kB
/** * Batch processor for efficient React updates * Prevents excessive re-renders by intelligently batching stream chunks */ export class BatchProcessor { constructor(onBatchReady, options = {}) { var _a, _b, _c, _d; this.onBatchReady = onBatchReady; this.pendingChunks = []; this.batchTimer = null; this.lastUpdateTime = 0; this.options = { maxBatchTime: (_a = options.maxBatchTime) !== null && _a !== void 0 ? _a : 16, // ~1 frame at 60fps maxBatchSize: (_b = options.maxBatchSize) !== null && _b !== void 0 ? _b : 10, minUpdateInterval: (_c = options.minUpdateInterval) !== null && _c !== void 0 ? _c : 8, // ~120fps max smartBatching: (_d = options.smartBatching) !== null && _d !== void 0 ? _d : true }; } addChunk(chunk) { this.pendingChunks.push(chunk); const now = Date.now(); const timeSinceLastUpdate = now - this.lastUpdateTime; // Check if we should flush immediately const shouldFlushNow = this.pendingChunks.length >= this.options.maxBatchSize || (timeSinceLastUpdate >= this.options.minUpdateInterval && this.isAtBoundary()); if (shouldFlushNow) { this.flush(); } else if (!this.batchTimer) { // Schedule a flush const timeToWait = Math.max(this.options.maxBatchTime, this.options.minUpdateInterval - timeSinceLastUpdate); this.batchTimer = setTimeout(() => this.flush(), timeToWait); } } isAtBoundary() { if (!this.options.smartBatching) return true; // Check if we're at a meaningful JSON boundary const combined = this.pendingChunks.join(''); // Simple heuristics for JSON boundaries const boundaries = [ '},', // End of object '],', // End of array '",', // End of string value 'true,', // End of boolean 'false,', // End of boolean /\d+,/, // End of number '}\n', // End of line with object ']\n', // End of line with array ]; return boundaries.some(boundary => { if (typeof boundary === 'string') { return combined.endsWith(boundary); } return boundary.test(combined); }); } flush() { if (this.pendingChunks.length === 0) return; if (this.batchTimer) { clearTimeout(this.batchTimer); this.batchTimer = null; } const chunks = this.pendingChunks; this.pendingChunks = []; this.lastUpdateTime = Date.now(); this.onBatchReady(chunks); } destroy() { if (this.batchTimer) { clearTimeout(this.batchTimer); this.batchTimer = null; } this.flush(); // Flush any remaining chunks } } /** * React-friendly update scheduler using requestAnimationFrame */ export class UpdateScheduler { constructor(callback) { this.callback = callback; this.scheduled = false; this.rafId = null; } schedule() { if (this.scheduled) return; this.scheduled = true; this.rafId = requestAnimationFrame(() => { this.scheduled = false; this.callback(); }); } cancel() { if (this.rafId !== null) { cancelAnimationFrame(this.rafId); this.rafId = null; } this.scheduled = false; } }