I was building offline-first apps for the Gnoke Suite and ran into the same wall every time.
The File System Access API works beautifully — until your phone decides otherwise.
A background OS kill. A tab reload at the wrong moment. Ten concurrent writes racing for the same stream. Any of these silently drops your data. No error. No warning. Just gone.
The browser fantasy is: pick a folder, write files, done.
The mobile reality is: your process dies, your handle goes stale, and your writable stream errors out mid-write. 😬
So I built a survival layer around it.
Meet gnoke-savenative
A two-layer write pipeline for browser apps. Native filesystem first, IndexedDB shelf as instant fallback.
npm install gnoke-savenative
import { saveNative } from 'gnoke-savenative';
import { openDB } from 'idb';
// Mount once (user gesture required)
const handle = await saveNative.mount(openDB);
let workspace = { handle, db: await saveNative._db(openDB) };
// Write — native first, shelf if it fails
await saveNative.write(workspace, 'notes.txt', 'Hello world');
// After reload — wake restores handle and auto-flushes shelf
workspace = await saveNative.wake(openDB);
The Survival Loop
Every write has a guaranteed outcome — it's either on disk or in the shelf.
write()
↓ native success → file on disk ✓
↓ native failure → shelved in IndexedDB
wake() after reload
↓ handle restored
↓ _flush() drains shelf → file on disk ✓
Writes are never dropped — only delayed. Recovery is automatic. Visibility is optional via hooks.
What makes it mobile-ready 📱
On desktop, the File System Access API mostly just works. On mobile (tested on Infinix Android, Chrome), three things will break a naive implementation:
1. OS background kills
The browser process dies. The handle survives in IndexedDB. But the write that was in-flight is gone. The shelf catches it.
2. Stale writable streams
Open a stream, come back after the OS has changed something on disk — the stream errors. Every write goes through a fresh createWritable() call.
3. Concurrent write races — this is where most implementations silently fail 🤷
The File System Access API does not serialize concurrent writes to the same file. Fire ten writes at once and they fight over the same stream — most fail with no error. gnoke-savenative maintains a per-filename queue so writes process in strict order. This is not retry logic — it's guaranteed ordering backed by a persistent fallback. v0.1.1 was specifically a concurrency patch after stress testing exposed false shelf activations on clean writes.
The API
saveNative.mount(openDB) // pick folder, stash handle
saveNative.wake(openDB) // restore handle, auto-flush shelf
saveNative.write(workspace, name, content) // queued write with shelf fallback
Optional hooks for UI feedback:
saveNative.onWriteFailure = (name, err) => { /* shelved */ };
saveNative.onFlushProgress = (done, total) => { /* draining */ };
saveNative.onFlushComplete = (count) => { /* recovered */ };
How it was built
This came out of a real stress test — a Ghost Editor testbench on a real Infinix device, with hard reloads, app switches, and 10 concurrent writes fired at once.
The pattern is essentially a write-ahead buffer with eventual durability: attempt the native write, fall back to the shelf on failure, replay on wake. The same principle behind WAL in database engines — applied to the browser filesystem. 🧠
v0.1 proved the shelf worked.
v0.1.1 eliminated false shelf activations under concurrent writes.
After the stress test showed zero shelf activations on clean concurrent writes, it was ready to ship. ✅
Try it
👉 github.com/edmundsparrow/gnoke-savenative
Zero dependencies (brings your own openDB). MIT licensed. Vanilla JS ES module.
Drop it in any project via CDN — no npm, no build step:
<!-- ES module -->
<script type="module">
import { saveNative } from 'https://cdn.jsdelivr.net/gh/edmundsparrow/gnoke-savenative/gnoke-savenative.js';
import { openDB } from 'https://unpkg.com/idb?module';
</script>
<!-- Or plain script tag — window.saveNative available globally -->
<script src="https://cdn.jsdelivr.net/gh/edmundsparrow/gnoke-savenative/gnoke-savenative.js"></script>
— Edmund Sparrow, Gnoke Suite
Top comments (1)
Why this matters
If you're using the File System Access API on mobile, you're not actually getting reliable disk writes — you're getting best-effort I/O under an unstable lifecycle.
Tabs get killed. Writes get interrupted. Concurrent operations collide. And when it fails, it often fails silently.
That means your app can lose user data without throwing a single error.
Gnoke-savenative exists to fix that gap — turning unsafe writes into a controlled, recoverable pipeline.
Desktop success is not a durability guarantee on mobile.