Keeping restaurant orders flowing when Wi‑Fi stumbles is non‑negotiable. Here’s a simplified outline of how we keep devices in lockstep and make sure every ticket hits the printer.
The shape of an order
export type Order = {
id: string;
table: string;
items: Array<{ sku: string; qty: number }>;
status: 'pending' | 'paid' | 'printed';
updatedAt: number; // unix ms
};
Conflict resolution
We lean on a last-writer-wins register for most fields and a grow-only set for items. In pseudocode:
function mergeOrders(local: Order, incoming: Order): Order {
const newer = incoming.updatedAt > local.updatedAt ? incoming : local;
return {
...newer,
items: mergeItems(local.items, incoming.items),
};
}
function mergeItems(a: Order['items'], b: Order['items']) {
const byKey = new Map<string, number>();
for (const line of [...a, ...b]) {
const next = (byKey.get(line.sku) ?? 0) + line.qty;
byKey.set(line.sku, next);
}
return [...byKey.entries()].map(([sku, qty]) => ({ sku, qty }));
}
Printing guarantees
- Each device keeps a local queue of
pendingPrints. - When connectivity returns, we reconcile orders, then drain
pendingPrints. - Printers are idempotent on
orderId + status, so retries are safe.
Takeaways
- Offline-first buys calm service; reconciliation should be boring and deterministic.
- Simple CRDT-inspired merges beat complex coordination for this use case.
- Observability matters: we log merges and print attempts with correlation IDs to debug fast.