Browse guides

Retain operations in an offline queue

Keep an order visible while delivery waits for connectivity.

Create failed order enqueues one order and runs two 503 sync attempts with maxRetries: 1 and a 10 ms linear backoff. Retry first failed order calls retryOne(id), Retry all failed orders calls retryAll(), and Discard failed orders calls discardFailed(). The mounted view refreshes pending, failed, syncing, failed IDs and the last action.

Table of contents

Main APIs

OfflineQueue
Stores operations, processes them when the network is available and exposes pending and failed counts plus syncing state.
ts
import { mount, onRemove, update, v } from "valyrian.js";
import { OfflineQueue } from "valyrian.js/offline";
import { NetworkManager } from "valyrian.js/network";

const exampleState = {
  nextOrder: 1,
  rejectDelivery: true,
  lastAction: "Ready",
};
const network = new NetworkManager();
const queue = new OfflineQueue({
  id: "orders",
  network,
  maxRetries: 1,
  backoff: { strategy: "linear", baseMs: 10, maxMs: 10 },
  handler: async (operation) => {
    const response = exampleState.rejectDelivery
      ? new Response(JSON.stringify({ message: "Delivery unavailable" }), {
          status: 503,
          statusText: "Service Unavailable",
          headers: { "Content-Type": "application/json" },
        })
      : new Response(null, { status: 204 });

    if (response.ok === false) {
      throw new Error(`Order delivery failed (${response.status})`);
    }
    exampleState.lastAction = `Delivered ${operation.id}`;
  },
});

const stop = queue.on("change", () => update());

async function createFailedOrder() {
  exampleState.rejectDelivery = true;
  queue.enqueue({
    type: "create-order",
    payload: { sku: `A-${exampleState.nextOrder}` },
  });
  exampleState.nextOrder += 1;
  await queue.sync();
  await queue.sync();
  exampleState.lastAction = "Created one failed order";
  update();
}

async function retryOneFailed() {
  const operation = queue.failed()[0];
  if (!operation) {
    exampleState.lastAction = "No failed order to retry";
    update();
    return;
  }
  exampleState.rejectDelivery = false;
  queue.retryOne(operation.id);
  await queue.sync();
  exampleState.lastAction = `Retried ${operation.id}`;
  update();
}

async function retryAllFailed() {
  exampleState.rejectDelivery = false;
  queue.retryAll();
  await queue.sync();
  exampleState.lastAction = "Retried all failed orders";
  update();
}

function discardAllFailed() {
  queue.discardFailed();
  exampleState.lastAction = "Discarded all failed orders";
  update();
}

function QueueStatus() {
  onRemove(() => {
    stop();
    queue.destroy();
    network.destroy();
  });
  const state = queue.state();
  const summary = `Pending: ${state.pending}; failed: ${state.failed}; syncing: ${state.syncing}`;
  if (typeof v !== "function") {
    return summary;
  }
  const failedIds =
    queue
      .failed()
      .map((operation) => operation.id)
      .join(", ") || "none";
  return v(
    "main",
    {},
    v("p", {}, summary),
    v("p", {}, `Failed IDs: ${failedIds}`),
    v("p", {}, exampleState.lastAction),
    v("button", { onclick: createFailedOrder }, "Create failed order"),
    v("button", { onclick: retryOneFailed }, "Retry first failed order"),
    v("button", { onclick: retryAllFailed }, "Retry all failed orders"),
    v("button", { onclick: discardAllFailed }, "Discard failed orders"),
  );
}

mount("body", QueueStatus);

Error

Failed operations increase the failed count and remain available to `retryOne`, `retryAll` or `discardFailed`.

Continue the cumulative application

Exact references