Retry queued work after connectivity returns
Keep an order visible while delivery waits for connectivity.
Table of contents
APIs used
- mount
- Starts a Valyrian.js application on a DOM target.
- onRemove
- Runs after the component subtree detaches from the rendered tree.
- update
- Requests a render pass for the mounted view.
- v
- Creates the vnode description that Valyrian.js renders as part of a view.
- OfflineQueue
- Stores operations, processes them when the network is available and exposes pending and failed counts plus syncing state.
- NetworkManager
- Observes browser connectivity and signal quality.
Run this recipe
- Starting project
- Use the local TSX and ESM project from Stage 1 with Valyrian.js installed and its ESM browser build configured.
- File
- Replace src/client-entry.tsx with this recipe snippet.
- Run
- Run npm run build, serve public with npx --yes serve@14.2.5 public --listen 8000, open http://localhost:8000 and use the example's visible controls.
- Observable result
- Choose Retry first failed order to call retryOne(id), Retry all failed orders to call retryAll(), or Discard failed orders to call discardFailed(). Retries switch the delivery response to 204.
Starting point
Create an order queue with a network manager and an asynchronous delivery handler.
Steps
Choose Create failed order. It enqueues an order and runs two sync attempts. Reproducible 503 responses, maxRetries: 1 and a 10 ms linear backoff move its ID to failed.
tsimport { 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);Result
Choose Retry first failed order to call retryOne(id), Retry all failed orders to call retryAll(), or Discard failed orders to call discardFailed(). Retries switch the delivery response to 204.
Limits
After every action, the mounted view shows pending, failed, syncing, failed IDs and the last action. Cleanup removes the subscription and destroys the queue and network manager.