From 8fdbb0852ee6e038dba2eb902255369e28d4514e Mon Sep 17 00:00:00 2001 From: Yusuke Hirao Date: Tue, 1 Sep 2026 17:10:16 +0900 Subject: [PATCH 1/3] feat(google-sheets): expose pending row count before flush Sheet#onProgress only fires after a chunk's batchUpdate resolves, so callers had no way to show anything while the final buffered chunk was in flight. Add a pendingCount getter so callers can report how many rows are about to be sent right before calling flush(). --- .../google-sheets/src/sheets/sheet.spec.ts | 28 +++++++++++++++++++ .../@d-zero/google-sheets/src/sheets/sheet.ts | 15 ++++++++++ 2 files changed, 43 insertions(+) diff --git a/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts b/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts index 62125ed3..b21e26e3 100644 --- a/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts +++ b/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts @@ -328,6 +328,34 @@ describe('appendRow / flush', () => { expect(sheet.sentCount).toBe(0); }); + test('pendingCount reflects buffered rows below the auto-flush threshold', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + expect(sheet.pendingCount).toBe(0); + + await sheet.appendRow(...Array.from({ length: 2499 }, () => eagerRow())); + expect(sheet.pendingCount).toBe(2499); + }); + + test('pendingCount drops to the chunk remainder once auto-flush fires', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + // 6000 rows -> two auto-flushed chunks of 2500, 1000 left buffered. + await sheet.appendRow(...Array.from({ length: 6000 }, () => eagerRow())); + expect(sheet.pendingCount).toBe(1000); + }); + + test('pendingCount returns to 0 after flush() drains the buffer', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + await sheet.appendRow(...Array.from({ length: 6000 }, () => eagerRow())); + await sheet.flush(); + expect(sheet.pendingCount).toBe(0); + }); + test('detects a lazy cell at any column position, not just the first cell of the row', async () => { // containsLazyCell() iterates every cell of the row. A defensive // "optimization" that only checks row[0] would slip past unit tests diff --git a/packages/@d-zero/google-sheets/src/sheets/sheet.ts b/packages/@d-zero/google-sheets/src/sheets/sheet.ts index 3bf40800..939f196e 100644 --- a/packages/@d-zero/google-sheets/src/sheets/sheet.ts +++ b/packages/@d-zero/google-sheets/src/sheets/sheet.ts @@ -100,6 +100,21 @@ export class Sheet { return props; } + /** + * バッファに溜まっているが未送信の行数。`SEND_CHUNK_SIZE` 未満で + * 自動 flush の閾値に達していない行、または遅延セルによって自動 + * flush が保留されている行を指す。 + * + * `flush()` 呼び出し前に「これから何行分のネットワーク往復が + * 発生するか」を呼び出し元が表示する用途(例: 進捗表示の + * "flushing N rows..." メッセージ)。`onProgress` は chunk の + * `batchUpdate` 完了後にしか呼ばれないため、送信開始前の見積もりは + * この getter でのみ得られる。 + */ + get pendingCount() { + return this.#pendingRows.length; + } + /** * `appendRow()` / `flush()` を通じてこれまでに送信した累計行数。 * 進捗表示用途。`setHeaders()` の送信分は含まない。 From 52a86c2953373920808766d407caf355cfdc01a7 Mon Sep 17 00:00:00 2001 From: Yusuke Hirao Date: Tue, 1 Sep 2026 17:28:44 +0900 Subject: [PATCH 2/3] docs(google-sheets): add example and lazy-row test for pendingCount Cover the JSDoc claim that pendingCount reflects rows held past SEND_CHUNK_SIZE while a lazy row suspends auto-flush, and add the @example the project's public-API JSDoc convention requires. --- .../@d-zero/google-sheets/src/sheets/sheet.spec.ts | 13 +++++++++++++ packages/@d-zero/google-sheets/src/sheets/sheet.ts | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts b/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts index b21e26e3..5f462cb7 100644 --- a/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts +++ b/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts @@ -356,6 +356,19 @@ describe('appendRow / flush', () => { expect(sheet.pendingCount).toBe(0); }); + test('pendingCount includes rows held past SEND_CHUNK_SIZE while a lazy row suspends auto-flush', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + // 2499 eager (below threshold, no flush yet) + 1 lazy (latches + // auto-flush) + 5000 more eager = 7500 held in the buffer instead of + // auto-flushing at the 2500 boundary. + await sheet.appendRow(...Array.from({ length: 2499 }, () => eagerRow())); + await sheet.appendRow(lazyRow()); + await sheet.appendRow(...Array.from({ length: 5000 }, () => eagerRow())); + expect(sheet.pendingCount).toBe(7500); + }); + test('detects a lazy cell at any column position, not just the first cell of the row', async () => { // containsLazyCell() iterates every cell of the row. A defensive // "optimization" that only checks row[0] would slip past unit tests diff --git a/packages/@d-zero/google-sheets/src/sheets/sheet.ts b/packages/@d-zero/google-sheets/src/sheets/sheet.ts index 939f196e..067fa448 100644 --- a/packages/@d-zero/google-sheets/src/sheets/sheet.ts +++ b/packages/@d-zero/google-sheets/src/sheets/sheet.ts @@ -110,6 +110,13 @@ export class Sheet { * "flushing N rows..." メッセージ)。`onProgress` は chunk の * `batchUpdate` 完了後にしか呼ばれないため、送信開始前の見積もりは * この getter でのみ得られる。 + * @example + * ```ts + * if (sheet.pendingCount > 0) { + * showProgress(`flushing ${sheet.pendingCount} rows...`); + * } + * await sheet.flush(); + * ``` */ get pendingCount() { return this.#pendingRows.length; From 6c32cf1c62248b6766e21e873dcff7a6ab7980d5 Mon Sep 17 00:00:00 2001 From: Yusuke Hirao Date: Tue, 1 Sep 2026 17:38:59 +0900 Subject: [PATCH 3/3] test(google-sheets): cover pendingCount through asyncDispose flush Complete the state-transition coverage for pendingCount alongside the existing sentCount coverage for the same [Symbol.asyncDispose] path. --- packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts b/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts index 5f462cb7..39ed6bd1 100644 --- a/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts +++ b/packages/@d-zero/google-sheets/src/sheets/sheet.spec.ts @@ -268,6 +268,15 @@ describe('appendRow / flush', () => { expect(sheet.sentCount).toBe(3); }); + test('pendingCount returns to 0 after [Symbol.asyncDispose] flushes the buffer', async () => { + const { parent } = createRecordingParent(); + const sheet = new Sheet(mockSheet as never, parent as never); + + await sheet.appendRow(...Array.from({ length: 3 }, () => eagerRow())); + await sheet[Symbol.asyncDispose](); + expect(sheet.pendingCount).toBe(0); + }); + test('suspends auto-flush as soon as a lazy row enters the buffer', async () => { const { parent, updateCellsRows } = createRecordingParent(); const sheet = new Sheet(mockSheet as never, parent as never);