From 2d38fcaeb0723343f159d9931395297d57c2b32e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 13 Jun 2026 17:52:18 +0000 Subject: [PATCH] perf: optimize updateCellBatch fallback with chunked Promise.allSettled This replaces the sequential N+1 query loop in the `updateCellBatch` fallback with a concurrent chunking approach. By grouping the updates into chunks of 50 and using `Promise.allSettled`, this drastically reduces IPC latency and overhead while safely ensuring all promises resolve before continuing to RELEASE or ROLLBACK the SAVEPOINT. Local tests across the suite continue to pass. --- package-lock.json | 4 ++-- src/hostBridge.ts | 28 +++++++++++++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb9a6c6..5586431 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sqlite-explorer", - "version": "1.5.0", + "version": "1.5.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sqlite-explorer", - "version": "1.5.0", + "version": "1.5.2", "funding": [ { "type": "github", diff --git a/src/hostBridge.ts b/src/hostBridge.ts index 12e5a75..f0b1674 100644 --- a/src/hostBridge.ts +++ b/src/hostBridge.ts @@ -403,21 +403,31 @@ export class HostBridge implements ToastService { if (typeof dbOps.updateCellBatch === 'function') { await dbOps.updateCellBatch(table, processedUpdates); } else { - // Fallback: execute updates sequentially to avoid IPC overload and N+1 concurrency, + // Fallback: execute updates in chunks to avoid IPC overload and N+1 concurrency, // but wrapped in a SAVEPOINT to avoid implicit auto-commits after every DML statement. const savepointName = `sp_batch_fallback_${Date.now()}`; await dbOps.executeQuery(`SAVEPOINT ${savepointName}`); try { - for (const update of processedUpdates) { - let patch: string | undefined; - let val = update.value; + const CHUNK_SIZE = 50; + for (let i = 0; i < processedUpdates.length; i += CHUNK_SIZE) { + const chunk = processedUpdates.slice(i, i + CHUNK_SIZE); + const promises = chunk.map(update => { + let patch: string | undefined; + let val = update.value; + + if (update.operation === 'json_patch') { + patch = update.value as string; + val = null; + } - if (update.operation === 'json_patch') { - patch = update.value as string; - val = null; - } + return dbOps.updateCell(table, update.rowId, update.column, val, patch); + }); - await dbOps.updateCell(table, update.rowId, update.column, val, patch); + const results = await Promise.allSettled(promises); + const failures = results.filter(r => r.status === 'rejected'); + if (failures.length > 0) { + throw (failures[0] as PromiseRejectedResult).reason; + } } await dbOps.executeQuery(`RELEASE SAVEPOINT ${savepointName}`); } catch (err) {