Search selfstore
v1.8.21

Disk file home

The disk file is the purest form of the promise: the durable home is a file on the user’s own machine, and nothing else is involved. No account, no cloud, no consent screen, no backend of any size.

One call

const outcome = await store.connectFile(); // opens the browser's save dialog

switch (outcome) {
  case 'started':   break; // new file: this device's data now lives in it
  case 'merged':    break; // existing backup: folded with this device
  case 'manual':    break; // no File System Access here (see below)
  case 'cancelled': break; // the user closed the picker
}

connectFile() needs a user gesture (a click), because the File System Access API does. From then on, every debounced save also writes that file, encrypted if protect() is on, and the store re-acquires the handle on the next visit without asking again. Pass a password for an already encrypted file: store.connectFile({ password }).

Browser support, honestly

File System Access ships in Chromium browsers (Chrome, Edge, Brave, Arc…). Firefox and Safari do not expose it, so there connectFile() returns 'manual': fall back to a downloaded file the user saves and re-imports.

if ((await store.connectFile()) === 'manual') {
  await store.downloadBackup();          // they save it themselves
  // ...later: await store.importBackup(pickedFile);
}

Same format, same encryption; the difference is one gesture per backup instead of zero.

Inside a desktop shell

A native webview usually has no File System Access at all - macOS and Linux shells embed WebKit - so packaging your app would lose this home in the one place writing a real file is easiest. Hand the shell its own filesystem and dialog calls once and connectFile() comes back, on a path rather than a handle:

import { useDesktopFiles } from 'selfstore';

useDesktopFiles({ readFile, writeFile, stat, exists, save, open });

The path outlives the session, so the app reopens on its file with nothing asked of the user. The whole integration is on desktop shell.

Why this home matters

Every other storage story ultimately asks the user to trust someone’s server. A disk file asks them to trust their own disk. For a local-first app it is the reference implementation of “your data is yours”: auditable with an archive tool, movable on a USB stick, restorable anywhere the format is implemented.

On the advanced store

If you own the data model and drive createLocalStore directly, the same home is fileTarget from the advanced subpath:

import { fileTarget } from 'selfstore/advanced';

const target = await fileTarget.connect({ kv: cache.kv, fileName: 'my-app.zip' });
await store.attachTarget(target, { password });
// restoreTarget: (kind) => kind === 'file' ? fileTarget.fromSession({ kv: cache.kv }) : null