Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.laportenard.com/llms.txt

Use this file to discover all available pages before exploring further.

System topology

  ┌─────────────┐  WebSocket  ┌───────────────────────────────────────────┐
  │ Terminal 1  │◄───────────►│              POS Sync Hub                 │
  │  (Browser)  │             │             (Raspberry Pi)                │
  └─────────────┘             │                                           │
                              │  ┌─────────────────┐  ┌───────────────┐  │
  ┌─────────────┐  WebSocket  │  │  Lease Manager  │  │  Order Store  │  │
  │ Terminal 2  │◄───────────►│  └─────────────────┘  └───────────────┘  │
  │  (Browser)  │             │                                           │
  └─────────────┘             │  ┌─────────────────┐  ┌───────────────┐  │
        ⋮                     │  │   Event Log     │  │  Table Store  │  │
  ┌─────────────┐  WebSocket  │  └─────────────────┘  └───────────────┘  │
  │ Terminal N  │◄───────────►│                                           │
  │  (Browser)  │             │  ┌─────────────────────────────────────┐  │
  └─────────────┘             │  │          SQLite (WAL mode)          │  │
                              │  └─────────────────────────────────────┘  │
                              └──────────────────────┬────────────────────┘
                                                     │ HTTP POST
                                                     │ (Forwarder, 5s cycle)

                              ┌───────────────────────────────────────────┐
                              │               Odoo Cloud                  │
                              │                                           │
                              │  POST /pos-api/v1/orders/{create,update}  │
                              │  POST /web/session/authenticate           │
                              │                                           │
                              │  ┌──────────────┐  ┌──────────────────┐  │
                              │  │  pos.order   │  │   order_audit    │  │
                              │  └──────────────┘  └──────────────────┘  │
                              └───────────────────────────────────────────┘

Three-tier data flow

1. Terminal (browser)

The POS frontend connects to the hub via WebSocket. On every order change, the terminal sends an ORDER_SNAPSHOT message containing the full order state. It does not talk to Odoo directly in hub mode. Key modules (in nu_pos_react/src/realtime/):
  • hubTransport.ts — Implements SyncTransport for hub mode
  • hub/hubConnection.ts — WebSocket lifecycle with auto-reconnect
  • hub/hubDiscovery.ts — Resolves hub URL from bootstrap config

2. Hub (Raspberry Pi)

The hub is a stateless-except-SQLite Node.js service that:
  1. Authenticates terminals via Odoo session validation
  2. Enforces single-writer order ownership through leases
  3. Persists order snapshots with optimistic versioning
  4. Broadcasts changes to all other connected terminals
  5. Forwards events to Odoo in the background
Key modules (in nu_pos_hub/src/):
  • handlers/ — Message handlers (auth, lease, order, table)
  • storage/ — SQLite stores (orders, leases, events, tables)
  • leasing/leaseManager.ts — Lease business logic
  • upstream/forwarder.ts — Background event drain to Odoo

3. Odoo (cloud)

Odoo is the system of record. The hub forwards order events via the existing /pos-react/api/orders/{create,update} endpoints. These are idempotent by pos_reference, so re-forwarding is safe.

Direct mode vs hub mode

AspectDirect modeHub mode
ConnectionHTTP polling (2s interval)WebSocket (real-time)
Conflict preventionLocal tab locks onlyDistributed leases via hub
Cross-terminal updatesPoll-based (2-15s delay)Instant broadcast
Offline supportlocalStorage outboxlocalStorage outbox + hub reconnect
InfrastructureNone (just Odoo)Raspberry Pi running hub
Odoo callsEach terminal calls OdooHub is sole Odoo gateway
ActivationDefaultOdoo config: nu_hub_enabled = True
Both modes implement the same SyncTransport interface (see transport layer), so the UI code is identical regardless of mode.

Component ownership

  ┌─────────────────────────────────────────────────────────────────────┐
  │  nu_pos_sync_protocol/   Shared types, validators, constants        │
  └───────────────────────────────────┬─────────────────────────────────┘

                   ┌──────────────────┴──────────────────┐
                   │                                     │
                   ▼                                     ▼
  ┌────────────────────────┐        ┌────────────────────────────────────┐
  │      nu_pos_hub/       │        │          nu_pos_react/             │
  │  Consumes @nu/sync-    │        │  Mirrors types in transport.ts     │
  │  protocol for WS       │        │  (no direct npm dependency)        │
  │  message handling      │        │                                    │
  └────────────────────────┘        └────────────────────────────────────┘
The frontend does not import @nu/sync-protocol directly. Instead, transport.ts defines OrderSnapshotData (a mirror of the protocol’s OrderSnapshot) to avoid coupling the browser bundle to the Node.js protocol package.