Skip to main content
Leases enforce single-writer ownership on orders. Only the terminal holding the lease for an order can submit snapshots. This prevents two terminals from concurrently editing the same order and creating divergent state.

How it works

Acquiring a lease

  1. Terminal opens an order and sends LEASE_ACQUIRE { posReference, baseVersion }
  2. Hub checks if the order is already leased:
    • Not leased — Grant immediately. Returns LEASE_GRANTED { version, expiresAt }
    • Leased by same terminal — Grant (re-entrant). Returns current version
    • Leased by another terminal — Deny. Returns LEASE_DENIED { holder, reason }
  3. Terminal must hold a lease before sending ORDER_SNAPSHOT

Maintaining a lease

  • Leases expire after 60 seconds (LEASE_TTL_MS) without renewal
  • Terminals send LEASE_HEARTBEAT every 20 seconds (HEARTBEAT_MS)
  • Each heartbeat resets the expiry to now + 60s
  • If a terminal crashes or loses connection, the lease auto-expires

Releasing a lease

  • Terminal sends LEASE_RELEASE { posReference, finalSnapshot? }
  • If finalSnapshot is provided, the hub saves it before releasing
  • This happens when closing an order, navigating away, or paying

Lease expiry (tick)

The hub runs a periodic tick every 20 seconds:
  1. Query all leases where expires_at <= now
  2. For each expired lease:
    • Load the last known order snapshot
    • Send LEASE_REVOKED { posReference, reason: "Lease expired", snapshot } to the terminal
    • Delete the lease from storage
    • The order is now available for other terminals

Disconnect grace period

When a terminal’s WebSocket disconnects (network drop, browser close):
  1. Hub does not immediately revoke leases
  2. Instead, sets lease expiry to now + 10s (grace period)
  3. If the terminal reconnects within 10 seconds, it can resume editing
  4. If grace period expires, the normal tick process revokes the lease

Manager override

Managers can forcibly take over an order held by another terminal:
1

Request

Manager terminal sends LEASE_ACQUIRE { posReference, force: true }
2

Validate

Hub checks that force is set and the requesting user has appropriate role
3

Revoke

Hub sends LEASE_REVOKED { reason: "Manager override", snapshot } to the current holder
4

Grant

Hub grants the lease to the manager. Manager receives LEASE_GRANTED with the latest version
Non-managers who attempt to access a held order see a LEASE_DENIED response. Managers see a confirmation dialog before sending the force-acquire.

Lease storage

Leases are stored in SQLite (nu_pos_hub/src/storage/leaseStore.ts):
Key operations: grant(), renew(), release(), getExpired(now), getByTerminal(id).

Frontend lease handling

In hub mode, hubTransport.ts manages lease state:
  • acquireLease() sends LEASE_ACQUIRE and returns a Promise<LeaseResult> that resolves on LEASE_GRANTED or LEASE_DENIED (or times out after 10 seconds)
  • releaseLease() sends LEASE_RELEASE and removes from internal heldLeases set
  • isLeaseHeld() checks the internal set
  • onLeaseRevoked() fires when hub sends LEASE_REVOKED
In direct mode, directTransport.ts uses local tab-level locks (orderLocks.ts) with a 45-second TTL. No network coordination exists.

Timing summary