LocalMode
LocalForage

Overview

Cross-browser storage adapter with automatic driver fallback using localForage.

@localmode/localforage

Cross-browser storage adapter using localForage. Automatically falls back from IndexedDB to WebSQL to localStorage, ensuring storage works in every browser environment.

Features

  • 🌐 Auto-Fallback — IndexedDB -> WebSQL -> localStorage, automatically
  • 🔄 Cross-Browser — Works everywhere, including older browsers
  • 📦 ~10KB — Mature, battle-tested library
  • 🔒 Safari Private Browsing — Falls back gracefully when IndexedDB is blocked

Installation

bash pnpm install @localmode/localforage @localmode/core
bash npm install @localmode/localforage @localmode/core
bash yarn add @localmode/localforage @localmode/core
bash bun add @localmode/localforage @localmode/core

Quick Start

import { LocalForageStorage } from '@localmode/localforage';
import { createVectorDB, embed, ingest } from '@localmode/core';
import { transformers } from '@localmode/transformers';

// Create storage — automatically picks best available driver
const storage = new LocalForageStorage({ name: 'my-app' });

// Use with VectorDB
const db = await createVectorDB({
  name: 'documents',
  dimensions: 384,
  storage,
});

// Ingest documents
const model = transformers.embedding('Xenova/bge-small-en-v1.5');

await ingest({
  db,
  model,
  documents: [
    { text: 'Local-first AI is the future', metadata: { source: 'blog' } },
    { text: 'Privacy-preserving machine learning', metadata: { source: 'paper' } },
  ],
});

The ingest() function is part of the RAG pipeline. It chunks, embeds, and stores documents in a single call.

Configuration

Prop

Type

Driver Selection

By default, localForage selects the best available driver automatically. You can override the priority:

import localforage from 'localforage';
import { LocalForageStorage } from '@localmode/localforage';

// Force localStorage only (e.g., for testing)
const storage = new LocalForageStorage({
  name: 'my-app',
  driver: [localforage.LOCALSTORAGE],
});

// Prefer IndexedDB, fall back to localStorage (skip WebSQL)
const storage2 = new LocalForageStorage({
  name: 'my-app',
  driver: [localforage.INDEXEDDB, localforage.LOCALSTORAGE],
});

Auto-Fallback Behavior

localForage handles driver selection transparently:

DriverEnvironmentUsed When
IndexedDBModern browsersDefault, best performance
WebSQLOlder Safari, some mobileIndexedDB unavailable
localStorageAll browsersLast resort fallback

Safari Private Browsing

Safari's private browsing mode blocks IndexedDB. localForage automatically falls back to localStorage, so your app keeps working. Note that localStorage has a ~5MB limit, which may affect large vector databases.

Vector Serialization

LocalForageStorage stores vectors as number[] arrays (not typed arrays) internally. This is required because typed arrays do not survive JSON round-tripping used by the localStorage and WebSQL drivers. Vectors are automatically converted back to their original typed array on read (Float32Array for float vectors, Uint8Array for SQ8/PQ-compressed payloads) and to number[] on write. This means storage size is ~2-3x larger than binary storage, and the localStorage driver's ~5MB quota limit may be reached with large vector databases.

Persistence Guarantees

LocalForageStorage round-trips the full Collection object, so everything core stores on it survives a page reload or database reopen: SQ8/PQ vector quantization calibration (calibration, pqCodebook), storage-compression calibration (compressionCalibration, deltaCalibration, compression), and the embedding-drift modelFingerprint. Quantized and compressed databases decode identically across sessions, and drift detection works the same as with core's built-in IndexedDBStorage.

Used quantization or compression before this fix?

Earlier versions of this adapter persisted only four Collection fields and silently dropped the calibration data — quantized/compressed vectors round-tripped fine in-session but decoded as raw bytes after a reopen. The calibration was never stored, so affected databases cannot be repaired: clear and re-ingest (databases that never used quantization or compression are unaffected).

// Recover a database that used quantization/compression before the fix
await db.clear(); // or clear the underlying stores by deleting the localForage database

// Then re-ingest from your source documents
await ingest({ db, model, documents });

localStorage fallback driver and calibration data

localForage's localStorage fallback driver JSON-serializes stored values, and the typed arrays nested inside calibration data (e.g. PQ codebook centroids) do not survive that round-trip — so quantization/compression calibration degrades under the localStorage driver only. The default IndexedDB driver preserves it fully. If your users may land on the localStorage fallback (e.g. Safari Private Browsing), avoid vector quantization/compression, or pin driver to [localforage.INDEXEDDB]. This is a pre-existing characteristic of the driver, not part of the persistence fix above.

Writing your own adapter? Verify the same guarantees with the StorageAdapter conformance suite — the official adapters run it in their own test suites.

Storage Fallback

Combine with a try/catch pattern for maximum resilience:

import { MemoryStorage } from '@localmode/core';
import { LocalForageStorage } from '@localmode/localforage';

let storage: LocalForageStorage | MemoryStorage;

try {
  storage = new LocalForageStorage({ name: 'my-app' });
  await storage.open();
} catch (error) {
  console.warn('LocalForageStorage unavailable, falling back to memory:', error);
  storage = new MemoryStorage();
}

Comparison

AdapterPackageBundle SizeTransactionsAuto-FallbackBest For
IndexedDBStorage@localmode/core0KB (built-in)NoNoSimple apps, zero extra deps
DexieStorage@localmode/dexie~15KBYesNoProduction apps needing schema versioning
IDBStorage@localmode/idb~3KBNoNoMinimal bundle size
LocalForageStorage@localmode/localforage~10KBNoYesMax browser compatibility

Next Steps

On this page