pwa-sync
v0.3.0
Published
Offline-first data caching and mutation queue for PWAs, backed by Dexie (IndexedDB)
Downloads
226
Readme
pwa-sync
Offline-first data caching and mutation queue for PWAs, backed by Dexie (IndexedDB).
Install
pnpm add pwa-sync dexieFor React hooks, also install:
pnpm add dexie-react-hooks reactUsage
Define your database
Use a Dexie subclass for typed table properties:
// db.ts
import Dexie, { type Table } from 'dexie'
import type { SyncMeta, PendingSubmission } from 'pwa-sync'
interface Item {
id: string
name: string
}
class AppDB extends Dexie {
items!: Table<Item, string>
syncMeta!: Table<SyncMeta, string>
queue!: Table<PendingSubmission<any>, string>
constructor() {
super('app-db')
this.version(1).stores({
items: 'id',
syncMeta: 'endpoint',
queue: '++id,createdAt',
})
}
}
export const db = new AppDB()OfflineData
Caches data from a remote endpoint in IndexedDB. When online, fetches fresh data and updates the cache. When offline, serves from cache.
import { OfflineData } from 'pwa-sync'
import { db } from './db'
const offlineData = new OfflineData(db.items, db.syncMeta, '/api/items')
const result = await offlineData.get()
// result.source → 'network' | 'cache'
// result.data → Item[]
// result.lastUpdate → Date
const status = await offlineData.status()
// status.lastUpdate → Date | nullOfflineQueue
A persistent mutation queue with automatic retries, exponential backoff, and online/offline awareness. Mutations are stored in IndexedDB and processed in FIFO order.
import { OfflineQueue } from 'pwa-sync'
import { db } from './db'
const queue = new OfflineQueue(db.queue, '/api/submit')
// Add a mutation — it will be sent immediately if online, or queued for later
await queue.add({ name: 'New item' })
// Manually retry a failed item
await queue.retry(itemId)
// Remove an item from the queue
await queue.remove(itemId)Items retry up to 3 times (configurable) with exponential backoff (max 5 minutes). 5xx and network errors retry. 4xx errors (except 429) are treated as fatal and won't retry. 401/403 (auth) errors are a special case: the item is marked failed (not retried, since the same expired session would fail again) but is recoverable — call retry(id) after the user signs in. Subscribe with the onAuthError option to prompt a sign-in flow. The last HTTP status is stored on the item as lastStatus.
const queue = new OfflineQueue(db.queue, '/api/submit', {
maxAttempts: 5,
onAuthError: item => {
// session expired while `item` was queued — prompt sign-in, then retry(item.id)
}
})React Hooks
import { useQueueLength, useQueueItems } from 'pwa-sync/react'
function PendingBadge() {
const count = useQueueLength(queue)
return <span>{count ?? 0} pending</span>
}
function PendingList() {
const items = useQueueItems(queue)
// items is a live-updating array of PendingSubmission<T>
}API
OfflineData<T>
constructor(table, metaTable, endpoint)— Dexie tables for data and sync metadata, plus the fetch URLget()— Returns cached or fresh datastatus()— Returns{ lastUpdate: Date | null }clear()— Clears cached data and metadata
OfflineQueue<T>
constructor(queue, endpoint, options?)— Dexie table, POST endpoint, and eithermaxAttempts(number, default 3) or{ maxAttempts?, onAuthError? }.onAuthError(item)fires when a submission fails with 401/403.add(payload)— Enqueue a mutationremove(id)— Remove an item by IDretry(id)— Reset a failed item for retryclear()— Clear the entire queuelength()— Returns the number of queued itemsprocess()— Manually trigger queue processing
React (pwa-sync/react)
useQueueLength(queue)— Live count of queued itemsuseQueueItems(queue)— Live array ofPendingSubmission<T>
License
MIT
