Skip to content

Replication Plugin

@routier/replication-plugin provides the HTTP and local-first replication building blocks for Routier. Use it when your app should read from local storage first, keep working offline, and synchronize with a remote API in the background.

When To Use It

Use the replication plugin when you want one of these patterns:

  • HttpDbPlugin for direct HTTP-backed collections and views.
  • HttpSwrDbPlugin for stale-while-revalidate reads with background refresh.
  • OptimisticUpdatesDbPlugin for memory-first reads layered on top of a persistent store.
  • HttpSwrDbPlugin + OptimisticUpdatesDbPlugin for local-first SWR with optimistic updates.

For the full local-first background, see Local-First Apps. For the highest-performance composition, see HttpSwrDbPlugin with Optimistic Replication.

Installation

bash
npm install @routier/replication-plugin

Install a local store plugin as well when you want persistent offline storage:

bash
npm install @routier/dexie-plugin

Minimal Setup

ts
import { DataStore } from "@routier/datastore";
import { DexiePlugin } from "@routier/dexie-plugin";
import { HttpSwrDbPlugin, OptimisticUpdatesDbPlugin } from "@routier/replication-plugin";

const cacheDb = new DexiePlugin("myapp_cache");
const unsyncedQueueDb = new DexiePlugin("myapp_unsynced");
const optimisticDb = new OptimisticUpdatesDbPlugin(cacheDb);

const plugin = new HttpSwrDbPlugin(optimisticDb, {
  getUrl: (collectionName) => `https://api.example.com/${collectionName}`,
  getHeaders: async () => ({ Authorization: `Bearer ${await getAccessToken()}` }),
  maxAgeMs: 30_000,
  unsyncedQueueStore: unsyncedQueueDb,
  translateRemoteResponse(_schema, data) {
    return (data as { data?: unknown[] }).data ?? [];
  },
});

declare function getAccessToken(): Promise<string>;

export class AppDataStore extends DataStore {
  constructor() {
    super(plugin);
  }
}

Exported Plugins

ExportBest forNotes
HttpDbPluginServer-backed reads and writes over HTTPSends query filters, sorts, skip, and take to your API.
HttpSwrDbPluginCache-first reads with background revalidationKeeps local data fresh while the UI continues reading from cache.
OptimisticUpdatesDbPluginMemory-first reads over a persistent source pluginHydrates memory from the wrapped source plugin and keeps reads extremely fast.

For most production web apps, use this layering:

  1. DexiePlugin stores the durable local cache in IndexedDB.
  2. OptimisticUpdatesDbPlugin mirrors that cache into memory for instant reads.
  3. HttpSwrDbPlugin serves stale-while-revalidate reads and syncs writes to your API.

That gives you SWR semantics, optimistic local UX, and offline resilience without making every query wait on the network.

Key Options

HttpDbPlugin

HttpDbPlugin is the simplest remote plugin. Its main options are:

  • getUrl(collectionName): maps each Routier collection to an API endpoint.
  • getHeaders(): injects auth or tenant headers per request.
  • ignoreQueryForCollections: skips query serialization for collections the server always scopes itself.
  • queryRetryBaseDelayMs, queryRetryMaxDelayMs, queryRetryMaxAttempts: control retry backoff for reads.
  • translateRemoteResponse(schema, data): adapts your API payload to the array shape Routier expects.
  • writeBatchDelayMs: debounce window for combining rapid writes to one URL (default 25; 0 disables batching).

HttpSwrDbPlugin

HttpSwrDbPlugin extends the HTTP options with local-first SWR behavior:

  • maxAgeMs: how long cached data is considered fresh before revalidation starts.
  • bulkPersistRetryBaseDelayMs, bulkPersistRetryMaxDelayMs, bulkPersistRetryMaxAttempts: retry controls for writes.
  • onAuthError(event): lets you trigger token refresh or sign-out on 401 and 403.
  • onRevalidateError(error, context): log or surface background refresh failures without breaking the current UI.
  • unsyncedQueueStore: where pending writes are persisted until the server confirms them.

Structured Permanent Rejections

A legacy permanent 4xx response does not say whether the whole request is invalid or one entity poisoned the batch. To preserve valid writes, HttpSwrDbPlugin falls back to sending each entity alone. That is safe but costs N requests.

Servers can avoid that fan-out by returning one of these JSON bodies with the permanent status:

json
{ "error": "account is read-only", "rejectionScope": "batch" }

The entire batch is dead-lettered without probes. Use this only when no entity in the request could succeed.

json
{ "error": "validation failed", "rejectedOpIds": ["operation-id-2"] }

The listed operations are dead-lettered and all unlisted operations are retried together in one request. Operation IDs come from meta.opIds, parallel to the adds, updates, and removes arrays. An unstructured response retains the per-item isolation fallback for compatibility and to avoid discarding valid writes.

Operational Notes

  • translateRemoteResponse is usually required when your API returns { data: [...] } or another wrapped payload.
  • ignoreQueryForCollections is useful for server-owned scopes such as "users" or tenant-bound collections.
  • unsyncedQueueStore persists a reserved collection named _routier_unsynced; keep that store isolated from your main cache when your backend has store-specific constraints.
  • Override formatRequestBody(...) on HttpSwrDbPlugin when you need to strip client-only fields or match an existing API contract.

Released under the MIT License.