DEV Community

Cover image for I Extracted My Production Offline-Sync Engine and Tried to Break It
Daniel
Daniel

Posted on

I Extracted My Production Offline-Sync Engine and Tried to Break It

A field engineer closed their laptop mid-upload. 20MB of installation photos, half-sent. When they reopened it later, the sync engine had to decide: re-upload everything, or pick up exactly where it left off?

That single edge case — and about ten more like it — is what this post is actually about.

I work on Instollar, a solar installation platform used by 1,400+ installers, many working mesh grid connections in places with little to no signal. The frontend has to assume the network will fail during a submission, not just before one. So we built an offline-first sync engine: an IndexedDB outbox queue, two-phase media upload checkpointing, and strict sequential processing — extracted this month into a standalone, framework-agnostic package so I could actually pressure-test it outside the app.

Repo + live demo: github.com/Dhanny-aay/offline-sync-engine

The bugs that actually taught me something

Building the happy path was the easy part. Here are three bugs from production that changed how I think about offline systems:

1. Parallel execution corrupted step order.

Outbox entries were originally processed as they came in. Turns out "as they came in" isn't the same as "in the order the user did them" once you're retrying failed entries alongside new ones. Step 2's submission reached the backend before Step 1's — silently corrupting foreign keys. Fix: group by correlation ID, sort by sequence number, and hard-stop a report's chain the moment one step fails, so nothing downstream can jump the queue.

2. A missing blob and a failed upload looked identical — until I actually checked.

Both used to produce the same error message: "media blobs are missing." Except one meant the photo was safely lost and unrecoverable — the other meant the upload just failed and should be retried. Same message, two completely different correct responses. I only caught this while stress-testing the extracted demo — the fix was to throw at the actual point of failure instead of inferring the cause after the fact.

3. A CORS-blocked heartbeat check failed silently, and I trusted it anyway.

The sync engine pings a lightweight endpoint before attempting to sync, to confirm real connectivity (not just navigator.onLine, which lies more than you'd expect). In the demo environment, that ping hit a CORS wall, the fetch threw, the catch block swallowed it, and the engine just... never synced. No error. No log. I stared at a stuck queue for longer than I'd like to admit before adding a single console.warn and immediately seeing the real cause.

What's real, what's mocked

The core engine (syncEngine.core.ts) is the actual extracted logic — same queuing, same sequencing, same checkpointing behavior as production. The demo's database and upload function are mocked, on purpose: the point of a public demo isn't to prove Instollar's backend works, it's to prove this specific engine's behavior under failure — which a controlled mock can demonstrate more reliably than a real, well-behaved API ever would.

Try it

Clone it, go offline, submit a report with a photo attached, force an upload failure, then go back online and watch it either recover or fail with an honest error message — not a misleading one.

Full bug writeups are in WAR_STORIES.md. If you build offline-first systems and have your own war stories, I'd genuinely like to hear them.

Top comments (0)