Skip to content

Retry fmFetch when FileMaker bridge is briefly unavailable#289

Merged
eluce2 merged 2 commits into
mainfrom
codex/add-fmfetch-retry-on-filemaker
Jun 17, 2026
Merged

Retry fmFetch when FileMaker bridge is briefly unavailable#289
eluce2 merged 2 commits into
mainfrom
codex/add-fmfetch-retry-on-filemaker

Conversation

@eluce2

@eluce2 eluce2 commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add retry handling around fmFetch when window.FileMaker is temporarily unavailable.
  • Retry with short backoff before failing, covering both callback and promise usage.
  • Add tests for successful recovery and final rejection after retries are exhausted.

Closes #288

Testing

  • pnpm run ci

Summary by CodeRabbit

Bug Fixes

  • Improved fmFetch reliability by automatically retrying FileMaker script calls when FileMaker is briefly unavailable, using progressive backoff delays.
  • Prevented stale pending callbacks by cleaning up when script invocation fails synchronously.
  • Updated error behavior so Promise mode rejects on failure and callback-mode failures remain catchable.

New Features

  • fmFetch callback usage now returns a Promise<void> to match the retry flow.

Tests

  • Added Vitest coverage for retry success (via callback dispatch) and retry exhaustion (Promise rejection).

Chores

  • Prepared a patch release for the webviewer package.

@changeset-bot

changeset-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b352a48

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@proofkit/webviewer Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
proofkit-docs Ready Ready Preview, Comment Jun 16, 2026 4:51pm

Request Review

@eluce2

eluce2 commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

fixes #288

@eluce2

eluce2 commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@pkg-pr-new

pkg-pr-new Bot commented Jun 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

@proofkit/better-auth

pnpm add https://pkg.pr.new/proofsh/proofkit/@proofkit/better-auth@289

@proofkit/cli

pnpm add https://pkg.pr.new/proofsh/proofkit/@proofkit/cli@289

create-proofkit

pnpm add https://pkg.pr.new/proofsh/proofkit/create-proofkit@289

@proofkit/fmdapi

pnpm add https://pkg.pr.new/proofsh/proofkit/@proofkit/fmdapi@289

@proofkit/fmodata

pnpm add https://pkg.pr.new/proofsh/proofkit/@proofkit/fmodata@289

@proofkit/typegen

pnpm add https://pkg.pr.new/proofsh/proofkit/@proofkit/typegen@289

@proofkit/webviewer

pnpm add https://pkg.pr.new/proofsh/proofkit/@proofkit/webviewer@289

commit: b352a48

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch PR details, files changed, and diff
gh pr view 289 --repo proofsh/proofkit --json title,body,files,additions,deletions,headRefName

echo "=== PR DIFF ==="
gh pr diff 289 --repo proofsh/proofkit

Length of output: 5498



The retry logic is well-structured. A few observations:


packages/webviewer/src/main.ts

The for (let attempt = 0; ; attempt++) pattern is clean — when FM_FETCH_FILEMAKER_RETRY_DELAYS_MS[attempt] is undefined (after 3 retries), the loop breaks with a throw. The cbs[fetchId] cleanup on error in _execScript correctly handles the case where a new fetchId is generated on each retry attempt via v4(), so no stale callbacks accumulate.

One thing to be aware of: isFileMakerUnavailableError relies on matching the exact error message string "'window.FileMaker' was not available". If callFMScript's error message ever changes, retries will silently stop working. Tying both the constant and the check to the same source (or validating it with an error subclass/code) would be more robust, but it's a minor concern since this is your own internal code.


packages/webviewer/tests/main.test.ts

The two tests cover the promise path well. However, the callback path error surface — the setTimeout(() => { throw error; }, 0) branch — has no test coverage. When retries are exhausted in the callback variant, the error is rethrown asynchronously into the global scope, which is easy to overlook. A test like:

it("surfaces error asynchronously when callback path exhausts retries", async () => {
  const { fmFetch } = await import("../src/main.ts");
  const callback = vi.fn();
  let caughtError: unknown;
  vi.spyOn(globalThis, "setTimeout").mockImplementationOnce((fn) => {
    try { fn(); } catch (e) { caughtError = e; }
    return 0 as unknown as ReturnType<typeof setTimeout>;
  });

  fmFetch("LoadData", { id: "123" }, callback);
  await vi.advanceTimersByTimeAsync(1750);
  expect(callback).not.toHaveBeenCalled();
  expect(caughtError).toBeInstanceOf(Error);
});

…would prevent regressions in that code path.


Overall: The implementation is solid and the changeset is properly scoped. The missing callback-path test is the main gap worth addressing.

✅ Action performed

Full review finished.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2a75405a-881c-4f0e-9450-46ac21d50581

📥 Commits

Reviewing files that changed from the base of the PR and between 1d65dca and b352a48.

📒 Files selected for processing (3)
  • .changeset/fuzzy-trees-fetch.md
  • packages/webviewer/src/main.ts
  • packages/webviewer/tests/main.test.ts

📝 Walkthrough

Walkthrough

Adds bounded retry logic inside fmFetch for when window.FileMaker is not yet injected. New constants define retry delays and the error-message substring. _execScript now cleans up its pending callback on synchronous throws. A new _execScriptWithFileMakerRetry helper loops through attempts with increasing delays, retrying only on the classified unavailability error. Both callback and Promise code paths in fmFetch are rewired through this executor. Vitest tests with fake timers verify retry-then-resolve and retry-exhaustion behavior, and a changeset entry documents the patch release.

Changes

fmFetch FileMaker Retry

Layer / File(s) Summary
Retry constants, helpers, and fmFetch wiring
packages/webviewer/src/main.ts
Adds FM_RETRY_DELAYS and FM_UNAVAILABLE_ERROR_SUBSTR constants; introduces isFileMakerUnavailableError, wait, and _execScriptWithFileMakerRetry; patches _execScript to delete the pending cbs[fetchId] entry on synchronous callFMScript throws; rewires both callback and Promise branches in fmFetch to call _execScriptWithFileMakerRetry with error propagation.
Tests and changeset
packages/webviewer/tests/main.test.ts, .changeset/fuzzy-trees-fetch.md
Adds a Vitest suite using fake timers and mocked globalThis.window with three tests: retry-then-resolve via PerformScript/handleFmWVFetchCallback, retry-exhaustion rejection after 1750 ms in Promise mode, and retry-exhaustion rejection in callback mode. Includes the patch changeset entry for @proofkit/webviewer documenting FileMaker retry and callback-mode error propagation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding retry handling to fmFetch when FileMaker bridge is temporarily unavailable, which directly addresses issue #288.
Linked Issues check ✅ Passed The implementation fully addresses issue #288 by adding internal retry logic with bounded timeout and backoff intervals, eliminating the need for users to manually poll window.FileMaker.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the retry mechanism for fmFetch: the changeset entry, retry logic in main.ts, and comprehensive test coverage—no unrelated modifications present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/add-fmfetch-retry-on-filemaker

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/webviewer/tests/main.test.ts (1)

21-51: ⚡ Quick win

Add callback-mode retry coverage.

Current suite validates only Promise-based fmFetch. Since callback-mode retry/error behavior was also changed, add tests for callback success-after-retry and callback failure after retries to lock in both contracts.

As per coding guidelines, test files should keep assertions inside it()/test() and use async/await patterns; extending coverage here should follow that same rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/webviewer/tests/main.test.ts` around lines 21 - 51, The test suite
currently only covers Promise-based fmFetch behavior but does not validate
callback-mode retry and error handling which was also modified. Add two new test
cases after the existing tests: one using it() that tests callback-mode success
after retry (similar structure to "retries when FileMaker is not yet available"
but invoke the callback-based fmFetch method and verify the callback receives
the response after retries), and another using it() that tests callback-mode
failure after three retries (verify the callback receives an error after
vi.advanceTimersByTimeAsync moves through the retry timeline). Both new tests
should follow the established pattern of using async/await, advancing timers
with vi.advanceTimersByTimeAsync(), and keeping all assertions inside the test
body.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/webviewer/src/main.ts`:
- Around line 45-50: The error handling in the catch block for the
`_execScriptWithFileMakerRetry` call converts a catchable Promise rejection into
an uncaught global exception by rethrowing via setTimeout, which prevents
callers from handling the failure. Remove the setTimeout rethrow pattern and
instead surface the failure through a controllable channel appropriate for the
callback mode being used - either by allowing the Promise rejection to propagate
so callers can catch it with their own error handling, or by invoking an
explicit error callback to communicate the failure to the caller.

---

Nitpick comments:
In `@packages/webviewer/tests/main.test.ts`:
- Around line 21-51: The test suite currently only covers Promise-based fmFetch
behavior but does not validate callback-mode retry and error handling which was
also modified. Add two new test cases after the existing tests: one using it()
that tests callback-mode success after retry (similar structure to "retries when
FileMaker is not yet available" but invoke the callback-based fmFetch method and
verify the callback receives the response after retries), and another using it()
that tests callback-mode failure after three retries (verify the callback
receives an error after vi.advanceTimersByTimeAsync moves through the retry
timeline). Both new tests should follow the established pattern of using
async/await, advancing timers with vi.advanceTimersByTimeAsync(), and keeping
all assertions inside the test body.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2b090d8e-fd5f-41bd-8c08-ca9d8c2dd83b

📥 Commits

Reviewing files that changed from the base of the PR and between 4e9dad0 and 1d65dca.

📒 Files selected for processing (3)
  • .changeset/fuzzy-trees-fetch.md
  • packages/webviewer/src/main.ts
  • packages/webviewer/tests/main.test.ts

Comment thread packages/webviewer/src/main.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/webviewer/src/main.ts (1)

45-50: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid converting callback-path retry exhaustion into an uncaught global exception.

Lines 47-49 rethrow via setTimeout, which makes failure uncatchable by callers and can crash the host runtime. Please surface this through a controllable channel (e.g., explicit error callback or Promise-returning API path) instead of async global throw.

As per coding guidelines, "Handle errors appropriately in async code with try-catch blocks".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/webviewer/src/main.ts` around lines 45 - 50, The error handling for
_execScriptWithFileMakerRetry is using setTimeout to rethrow errors
asynchronously, which creates an uncatchable global exception that can crash the
runtime. Replace this setTimeout pattern with a controllable error handling
approach by either passing the error to an explicit error callback parameter or
by returning the promise rejection in a way that allows callers to handle the
error through standard Promise error handling (e.g., by not catching it and
letting it propagate, or by explicitly handling it with a proper callback
mechanism). This ensures errors from the retry exhaustion are surfaced through a
channel that callers can control rather than becoming an uncaught global
exception.

Source: Coding guidelines

🧹 Nitpick comments (3)
packages/webviewer/tests/main.test.ts (1)

43-51: ⚡ Quick win

Add callback-mode retry-exhaustion coverage.

Current tests cover Promise-mode exhaustion, but not the callback branch’s failure surface. Add one test for callback-mode exhaustion so future changes don’t regress that path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/webviewer/tests/main.test.ts` around lines 43 - 51, Add a new test
case alongside the existing Promise-mode retry exhaustion test to cover
callback-mode retry exhaustion. Create a test that calls the fmFetch function
from "../src/main.ts" with a callback parameter instead of using Promise-based
handling, then advance timers by 1750ms to exhaust the three FileMaker
availability retries, and verify that the callback receives the expected error
indicating that 'window.FileMaker' was not available. This ensures the callback
branch of the retry logic is covered and protected against future regressions.
packages/webviewer/src/main.ts (2)

114-125: ⚡ Quick win

Refactor the retry loop to a bounded for...of flow.

Line 114 uses an indexed infinite loop. It works, but this violates the TS loop guideline and makes termination implicit.

♻️ Proposed refactor
 async function _execScriptWithFileMakerRetry(scriptName: string, data: unknown, cb: (arg0?: unknown) => void) {
-  for (let attempt = 0; ; attempt++) {
+  for (const delay of FM_FETCH_FILEMAKER_RETRY_DELAYS_MS) {
     try {
       _execScript(scriptName, data, cb);
       return;
     } catch (error) {
-      const delay = FM_FETCH_FILEMAKER_RETRY_DELAYS_MS[attempt];
-      if (!(delay && isFileMakerUnavailableError(error))) {
+      if (!isFileMakerUnavailableError(error)) {
         throw error;
       }
       await wait(delay);
     }
   }
+  _execScript(scriptName, data, cb);
 }

As per coding guidelines, "Prefer for...of loops over .forEach() and indexed for loops".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/webviewer/src/main.ts` around lines 114 - 125, The retry loop uses
an indexed infinite loop (for (let attempt = 0; ; attempt++)) which violates
TypeScript coding guidelines. Refactor this loop to use a for...of iteration
over the FM_FETCH_FILEMAKER_RETRY_DELAYS_MS array instead, making the loop
bounds explicit and termination conditions clear. The loop should iterate
through the delay values in the array, catching FileMaker unavailable errors and
retrying with the corresponding delay, then throwing any other errors. This will
replace the implicit termination of the current indexed infinite loop with an
explicit bounded iteration.

Source: Coding guidelines


5-5: ⚡ Quick win

Tie retryable-error classification to a single source of truth.

isFileMakerUnavailableError depends on a message fragment; if the thrown wording changes, retries silently stop. Consider deriving the thrown message and matcher from one shared constant (or an error code/type) to prevent drift.

Also applies to: 103-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/webviewer/src/main.ts` at line 5, The FILEMAKER_UNAVAILABLE_MESSAGE
constant and the isFileMakerUnavailableError function (which checks for a
message fragment) are decoupled, creating a maintenance risk where if one is
updated the other silently becomes inconsistent. Refactor to use a single source
of truth by ensuring the error message thrown when FileMaker is unavailable and
the matcher in isFileMakerUnavailableError both reference the same
FILEMAKER_UNAVAILABLE_MESSAGE constant, or alternatively consider using an error
code or type-based check instead of message string matching to decouple the
error classification from wording changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/webviewer/src/main.ts`:
- Around line 45-50: The error handling for _execScriptWithFileMakerRetry is
using setTimeout to rethrow errors asynchronously, which creates an uncatchable
global exception that can crash the runtime. Replace this setTimeout pattern
with a controllable error handling approach by either passing the error to an
explicit error callback parameter or by returning the promise rejection in a way
that allows callers to handle the error through standard Promise error handling
(e.g., by not catching it and letting it propagate, or by explicitly handling it
with a proper callback mechanism). This ensures errors from the retry exhaustion
are surfaced through a channel that callers can control rather than becoming an
uncaught global exception.

---

Nitpick comments:
In `@packages/webviewer/src/main.ts`:
- Around line 114-125: The retry loop uses an indexed infinite loop (for (let
attempt = 0; ; attempt++)) which violates TypeScript coding guidelines. Refactor
this loop to use a for...of iteration over the
FM_FETCH_FILEMAKER_RETRY_DELAYS_MS array instead, making the loop bounds
explicit and termination conditions clear. The loop should iterate through the
delay values in the array, catching FileMaker unavailable errors and retrying
with the corresponding delay, then throwing any other errors. This will replace
the implicit termination of the current indexed infinite loop with an explicit
bounded iteration.
- Line 5: The FILEMAKER_UNAVAILABLE_MESSAGE constant and the
isFileMakerUnavailableError function (which checks for a message fragment) are
decoupled, creating a maintenance risk where if one is updated the other
silently becomes inconsistent. Refactor to use a single source of truth by
ensuring the error message thrown when FileMaker is unavailable and the matcher
in isFileMakerUnavailableError both reference the same
FILEMAKER_UNAVAILABLE_MESSAGE constant, or alternatively consider using an error
code or type-based check instead of message string matching to decouple the
error classification from wording changes.

In `@packages/webviewer/tests/main.test.ts`:
- Around line 43-51: Add a new test case alongside the existing Promise-mode
retry exhaustion test to cover callback-mode retry exhaustion. Create a test
that calls the fmFetch function from "../src/main.ts" with a callback parameter
instead of using Promise-based handling, then advance timers by 1750ms to
exhaust the three FileMaker availability retries, and verify that the callback
receives the expected error indicating that 'window.FileMaker' was not
available. This ensures the callback branch of the retry logic is covered and
protected against future regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 59b5954d-2e6b-4d2d-a70d-db32407c4897

📥 Commits

Reviewing files that changed from the base of the PR and between 4e9dad0 and 1d65dca.

📒 Files selected for processing (3)
  • .changeset/fuzzy-trees-fetch.md
  • packages/webviewer/src/main.ts
  • packages/webviewer/tests/main.test.ts

@eluce2 eluce2 merged commit 7f7a288 into main Jun 17, 2026
12 checks passed
@eluce2 eluce2 deleted the codex/add-fmfetch-retry-on-filemaker branch June 17, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fmFetch throws synchronously when window.FileMaker isn't injected yet at startup

1 participant