POS V2 — Research Hub
Architecture decisions backed by industry research
Research carried over from V1 + new V2-specific researchCore Framework Architecture
What framework and structure should be the foundation of POS V2? Build custom, use an existing framework, or hybrid?
◆ Frameworks Evaluated
9 frameworks and approaches were researched. Each evaluated for module system, performance, offline support, and POS fit.
Fastify
Plugin system, lifecycle hooks, decorators. The fastest mainstream Node.js framework.
NestJS
DI modules, guards/interceptors. Enterprise-grade but heavy abstraction layer.
Feathers.js
Services + hooks pattern, native real-time, offline-first package available.
Medusa.js
E-commerce modules, subscriber pattern. No multi-DB support, no offline capability.
Hono
Edge-first ultralight framework. Blazing fast but no offline, no state management.
Elysia
Bun-based, extremely fast. Too immature, Bun dependency is risky for production.
AdonisJS
Laravel-style, batteries included. Full MVC with ORM, auth, mail built in.
tRPC
End-to-end type safety for APIs. Not a framework — a complementary API layer tool.
Custom from Scratch
Build everything ourselves. Months of engineering for HTTP, routing, plugins, lifecycle.
◆ Comparison Matrix
Scores out of 10. Green (8-10) = excellent, Yellow (5-7) = adequate, Red (1-4) = poor fit.
| Criteria | Fastify | NestJS | Feathers | Medusa | Hono | Elysia |
|---|---|---|---|---|---|---|
| Module System | 8 | 9 | 7 | 8 | 6 | 6 |
| Hooks / Events | 8 | 9 | 9 | 7 | 6 | 6 |
| Multi-Database | 8 | 8 | 8 | 4 | 7 | 7 |
| Real-Time | 6 | 7 | 9 | 6 | 4 | 5 |
| Offline-First | 5 | 4 | 9 | 3 | 2 | 2 |
| Performance | 10 | 7 | 7 | 7 | 10 | 10 |
| Maturity | 9 | 9 | 6 | 8 | 7 | 5 |
| POS Fit | 9 | 6 | 8 | 3 | 3 | 3 |
◆ Why Fastify
Fastify is the core framework for POS V2
Hybrid approach: Fastify provides the HTTP layer, plugin system, and lifecycle hooks. We build the module structure, rule engine, sync engine, and adapter layer on top.
◆ What Fastify Doesn’t Have (We Build)
Fastify gives us the engine. These are the systems we engineer on top:
Structured Module System
Each module = routes.ts + rules.ts + sync.ts + service.ts. Self-contained, testable, consistent across HQ and store servers.
Rule Engine
Before/after hooks for every action. Cross-module communication without direct imports. Our @pos/rule-engine package.
Sync Engine
Global version counter, per-entity handlers, transactional outbox with status tracking and idempotency keys.
Event Bus
Cross-module communication for side effects. Decouples modules so inventory doesn’t import sales directly.
Backend Adapter
Web clients use HTTP fetch, Tauri apps use invoke. Same API surface, different transport. @pos/backend-adapter.
◆ POS V2 Architecture Structure
The complete monorepo layout showing where everything lives and how it connects. This is the blueprint.
pos2/ ├── docs/ │ ├── planner/ → Planning site │ └── research/ → Research site (you are here) ├── packages/ │ ├── core/ → Module system, hook pipeline, base interfaces │ ├── rule-engine/ → Before/after hooks, rule registry │ ├── sync-engine/ → Global version counter, entity handlers, outbox │ ├── shared-types/ → TypeScript interfaces for all entities │ ├── backend-adapter/ → Web (fetch) vs Tauri (invoke) │ ├── pos-logic/ → Shared Pinia stores, composables │ ├── tax-engine/ → Tax calculation (basis points) │ ├── i18n/ → EN + ES translations │ ├── ui-kit/ → PrimeVue theme + components │ └── event-bus/ → Client-side event notifications ├── apps/ │ ├── hq-server/ → Fastify, central HQ │ │ └── src/ │ │ ├── modules/ │ │ │ ├── products/ │ │ │ │ ├── routes.ts → HTTP endpoints │ │ │ │ ├── rules.ts → Business rules (before/after) │ │ │ │ ├── sync.ts → Sync handler for products │ │ │ │ └── service.ts → Business logic │ │ │ ├── sales/ │ │ │ ├── inventory/ │ │ │ ├── users/ │ │ │ └── ... │ │ ├── rules/ │ │ │ └── global.ts → Rules that apply everywhere │ │ └── db/ │ │ └── schema/ → Drizzle schema │ ├── store-server/ → Fastify, local per store │ │ └── src/ │ │ ├── modules/ → Same pattern as HQ │ │ ├── rules/ │ │ ├── sync/ → Sync service (pull/push/outbox) │ │ └── db/ │ ├── web-client/ → Vue 3 PWA │ ├── pos-terminal/ → Tauri desktop (POS) │ ├── store-manager/ → Tauri desktop (Store) │ └── hq-manager/ → Tauri desktop (HQ) └── tools/ ├── scripts/ → deploy, setup, validate └── docker/ → Docker configs
modules/sales/ ├── routes.ts → HTTP endpoints (GET/POST/PUT/DELETE) │ Calls service.ts for logic │ Uses ruleEngine.emit() for hooks │ ├── rules.ts → Registered at startup │ before('sale:create', validatePrices) │ before('sale:discount', checkPermission) │ after('sale:complete', deductInventory) │ ├── sync.ts → SyncHandler for sales entity │ push: send completed sales to HQ │ (no pull — sales don’t come from HQ) │ └── service.ts → Pure business logic createSale(), voidSale(), applySaleDiscount() No HTTP knowledge, no framework dependency Testable in isolation
- HTTP Request arrives at Fastify Router
- Auth Hook — JWT verification
- Permission Hook — route-level permission check
- Route Handler (
routes.ts) receives the request - ruleEngine.emit(
'sale:create', ctx, action) - runBefore hooks: validatePrices (
rules.ts) - runBefore hooks: checkPermission (
rules.ts) - runBefore hooks: checkInventory (
inventory/rules.ts) - All pass → Execute action (
service.ts) - runAfter hooks: deductInventory (
rules.ts) - runAfter hooks: addToSyncOutbox (
sync.ts) - runAfter hooks: emitEvent(
'sale:completed') via event-bus - HTTP Response returned to client
◆ Sync Flow
sync_outboxHTTP POST /sync/pushstore_product_dynamicsyncVersion◆ How Packages Connect
◆ Key Differences from V1
| Aspect | V1 | V2 |
|---|---|---|
| Module structure | routes.ts only |
routes + rules + sync + service |
| Rule engine | Added later, patched in | Built into core from day 1 |
| Sync | Monolithic pull-service.ts |
Per-entity handlers registered by modules |
| syncVersion | Per-row (broken) | Global counter per entity type |
| Outbox | No status, no idempotency | status + idempotencyKey + sentAt |
| Validation | Frontend + safety net | Server-side per-action + safety net |
| Module communication | Direct imports | Event-based (rule engine + event bus) |
| Schema | Evolved organically | Designed complete before first migration |
◆ Existing POS Systems Studied
Three established systems were analyzed to understand how production POS software handles modules, inventory, and multi-store operations.
ERPNext
Full ERP with POS module. DocType-based architecture with hooks and events. Taught us the value of a unified document model and server-side business rules.
Odoo
Modular ERP with offline POS client. Uses XML-RPC sync and ORM-level computed fields. Offline JS client syncs when reconnected.
TEKLA
Caribbean-focused multi-store POS. Desktop app with central server sync. Real-world multi-store with tax authority integration.
- Fastify Documentation — Official docs, plugin architecture, lifecycle hooks
- NestJS Documentation — Modules, DI, guards, interceptors
- Feathers.js Guide — Services, hooks, real-time, offline
- Medusa.js Documentation — E-commerce modules, subscribers
- Hono Documentation — Edge-first, ultralight framework
- Elysia Documentation — Bun-based framework
- AdonisJS Documentation — Laravel-style Node.js framework
- tRPC Documentation — End-to-end typesafe APIs
- Frappe/ERPNext Documentation — DocType architecture, hooks
- Odoo Documentation — POS module, offline client, sync
- Fastify Plugin Guide — Encapsulation, decorators, plugin system
- Fastify Benchmarks — Performance comparison with other frameworks
Rule Engine Architecture
Implemented in V1 — will be built into V2 core from day 1
The before/after hook pattern proved successful in V1. V2 will adopt this as a first-class architectural pattern from the start, with the rule engine integrated at the framework level rather than bolted on.
How should our POS system handle business rules (price validation, discount limits, permissions) across modules?
// Industry Patterns Compared
We evaluated three industry patterns for implementing business rules in a multi-module TypeScript/Fastify system. Each was assessed for type safety, performance, testability, and cross-module coordination.
condition() + action()before('event')after('event')Condition/Action Rules
Rules defined in TypeScript with a condition function and an action function. Priority-based execution with short-circuit on failure. Used by the ts-rule-engine library pattern.
Before/After Hooks
Lifecycle events where "before" hooks can validate and cancel, "after" hooks handle side effects. Each module registers its own hooks. Inspired by ERPNext, Mongoose, and Fastify's own hook system.
JSON Rules Engine
Rules stored in database or JSON files. Declarative conditions with operators like greaterThan, equal. No recompilation needed to change rules. Used by json-rules-engine npm package.
// How Rules Are Registered
Side-by-side comparison of the ERPNext/Python pattern and our TypeScript equivalent.
# ERPNext: hooks in each module doc_events = { "Sales Invoice": { "before_submit": validate_credit_limit, "after_submit": update_stock_ledger, }, "Stock Entry": { "before_submit": check_warehouse_capacity, } } def validate_credit_limit(doc, method): if doc.outstanding > doc.credit_limit: raise ValidationError("Over limit")
// packages/rule-engine/src/index.ts export class RuleEngine { before(event: string, handler: HookFn) {} after(event: string, handler: HookFn) {} async emit(event: string, ctx: Context) {} } // apps/hq-server/rules/sales.ts engine.before('sale:complete', async (ctx) => { if (ctx.sale.total > ctx.store.creditLimit) throw new ValidationError('Over limit'); }); engine.after('sale:complete', async (ctx) => { await updateInventory(ctx.sale.items); });
// Decision Matrix
| Feature | Condition/Action | Before/After Hooks | JSON Engine |
|---|---|---|---|
| Type Safety | Full TypeScript | Full TypeScript | JSON only |
| DB Access | Via DI | Via DI | Custom facts provider |
| No Redeploy to Change Rules | Requires deploy | Requires deploy | Runtime changes |
| Performance | Fast (native code) | Fast (native code) | Slower (parsing) |
| Cross-Module Rules | Supported | Supported | Complex setup |
| Testing | Easy to unit test | Easy to unit test | Harder to isolate |
| Industry Adoption (Retail) | Moderate | High (ERPNext, Odoo) | Niche |
| Learning Curve | Low | Low | Medium |
Implemented: Before/After Hooks with Module-Level Registration
Closest to ERPNext/Odoo architecture, the most widely adopted pattern in retail ERP systems. Provides full TypeScript type safety, clean module separation, and aligns with Fastify's own lifecycle hook model. Each module registers its own hooks. Testing is straightforward since each hook is a standalone async function.
// V1 Implementation
The rule engine was built and integrated across both servers in V1:
packages/rule-engine/ — Generic before/after hook framework (26 tests)store-server/modules/sales/rules.ts — 7 sales rulesstore-server/modules/inventory/rules.ts — Inventory adjustment rulesstore-server/modules/transfers/rules.ts — Transfer validation ruleshq-server/modules/sales/rules.ts — HQ-side sales rulesapp.tsSync Architecture
Can our sync system be modular like the rule engine? How do enterprise POS systems handle multi-store sync?
// Industry Findings
Enterprise POS systems universally use event-driven architectures with per-entity handlers — not monolithic sync services.
- Microsoft Dynamics 365 — Row version tracking per entity type with global version number. Pull-based with incremental deltas.
- Oracle Retail — Event-driven integration bus with per-entity handlers. Each retail domain has its own sync module.
- SAP Retail — CIF uses modular IDocs per entity type. Push-based notifications trigger targeted data downloads.
- Couchbase Sync Gateway — Global sequences per channel. Monotonic counters guarantee zero missed mutations.
// Three Architecture Patterns
syncVersion leads to missed updates. No hooks, no modularity.sync/pull-service.ts → ALL entities
sync/push-service.ts → ALL entities
SyncHandler. The sync service is just an orchestrator. Extensible and testable.modules/products/sync.ts → product pull/push
modules/sales/sync.ts → sale push
modules/inventory/sync.ts → inventory push
events/product-changed.ts
events/sale-created.ts
projections/product-state.ts
Registry-Based Per-Entity Handlers (Pattern 2)
Matches our existing rule engine pattern, is simple to implement, and follows what Microsoft Dynamics 365, Oracle Retail, and SAP all use internally.
// Global Version Counter
The core fix: replace per-row syncVersion with a global monotonic sequence per entity type. Pattern used by SQL Server Change Tracking, Couchbase Sync Gateway, and Dynamics 365.
Each row increments its own version independently. Gaps are inevitable.
All changes get the next number from a single counter. No gaps possible.
// Conflict Resolution Strategy
Per-entity ownership determines who wins in a conflict.
| Entity | Source of Truth | Strategy | Direction |
|---|---|---|---|
| Products (catalog) | HQ | HQ Wins | HQ → Store |
| Prices / Tax Groups | HQ | HQ Wins | HQ → Store |
| Users / Roles | HQ | HQ Wins | HQ → Store |
| Sales | Store | Store Wins | Store → HQ |
| Inventory Count | Store | Store Reports | Store → HQ |
| Customers | Store creates, HQ merges | Last-Write-Wins | Bidirectional |
// Before/After Hooks for Sync
The rule engine integrates directly with the sync system. Each sync operation can trigger before/after hooks.
before('sync:pull:products', validateVersion) // Ensure version is valid before applying before('sync:push:sale', enrichSaleData) // Add store metadata before pushing
after('sync:pull:products', reportStockToHQ) // Push local stock state after pull after('sync:push:sale', updateSyncStatus) // Update UI sync indicator
// Transactional Outbox Pattern
Changes written to sync_outbox in the same transaction as data change. Recommended additions:
- idempotencyKey — UUID per outbox entry prevents duplicate processing on retry.
- syncVersion column on outbox — links outbox entry to the global version for traceability.
// V1 Bugs Identified
Per-row syncVersion misses updates
syncVersion is per-row, auto-incremented. Store watermark advances past unsynced rows permanently.
store_product_dynamic never reflects reality
Created on first sync pull with onConflictDoNothing — never updated after. HQ quantity is permanently wrong.
Sync pull overwrites store quantity
Generic upsert overwrote ALL fields including quantity. Fixed to exclude quantity.
Fixed in V1Store never pushes product state
Store pushes events but never pushes actual quantity/price state. HQ has no way to know real inventory.
// Implementation Roadmap
Five-phase rollout. Each phase is independently deployable.
Global Version Counter
Create per-entity-type sequences. Add trigger to stamp sync_version from global sequence. Reset all store watermarks for full re-sync.
Per-Entity Sync Handlers
Create SyncHandler interface with pull(), push(), resolve(). Each module registers its handler. Sync service becomes thin orchestrator.
Before/After Hooks
Wire rule engine hooks into sync lifecycle for validation, enrichment, and automatic side effects.
Product Stock Push
Store pushes full product state to HQ after every sale, adjustment, transfer, or PO receive. HQ never calculates — only records.
Migration + Testing
Deploy updated pull handler. Force full re-sync. Monitor for missed updates. Add sync health dashboard.
Feature Roadmap
What features are we missing compared to industry standard POS systems, and how should we prioritize them?
// V1 System Capabilities
- Multi-store management (HQ + stores) with sync
- Product catalog (SKU, barcode, departments, suppliers, specials)
- Tax system (rates, groups, compound taxes)
- Sales processing (cart, discounts, multi-payment, price validation)
- Inventory management (adjustments, cross-store lookup)
- Inter-store transfers with variance tracking
- Purchase orders (full lifecycle)
- User/role/permission system (65+ permissions)
- Worksheets (price/product/tax changes with approval flow)
- Customer management, Sales representatives
- Register sessions (open/close with X/Z/ZZ reports)
- 13 report types with Chart.js visualizations
- Dark mode, PWA, i18n (EN+ES), barcode scanner, receipt printing
High Priority
7 featuresMedium Priority
9 featuresLow Priority
4 features// Missing Reports for Store Owners
Gross Margin Report
Margin by product, department, and store. "Where am I making the most money?"
Shrinkage Report
Track inventory loss over time. "How much am I losing?"
Sell-Through Rate
Sales velocity vs stock received. "What sells fast vs what's stuck?"
Customer Retention
Repeat customer rate over time. "Are customers coming back?"
Store Comparison
Same metric side by side across stores. "Which store performs best?"
ABC Analysis
Classify products by revenue contribution. "Which 20% of products drive 80% of revenue?"
// Items Completed in V1 from This Research
Database Design
What is the optimal schema design for a multi-store POS system? How do we balance normalization, performance, and auditability?
Research needed on optimal schema design for multi-store POS
- Normalization vs Denormalization — When to normalize for data integrity vs denormalize for read performance in POS workloads
- Audit Trails — Patterns for tracking all data changes: trigger-based, application-level, event sourcing, or temporal tables
- Soft Deletes — Strategy for soft deletes across all entities: deleted_at column, status enum, or separate archive tables
- Multi-tenant Schema — Schema-per-store vs shared schema with store_id column vs separate databases
- Index Strategy — Optimal indexes for POS query patterns: product lookup by barcode/SKU, sales by date range, inventory by store
- Migration Strategy — How to handle schema migrations across HQ + multiple store databases simultaneously
Authentication & Permissions
How should POS V2 handle authentication and permissions across HQ, Store, and POS apps — each with completely separate permission spaces?
◆ Models Evaluated
Three permission models were evaluated for multi-store retail POS.
RBAC (Role-Based) ✓
User → Role → Permissions. Simple, scalable, fits retail perfectly (Cashier, Manager, Admin).
ABAC (Attribute-Based)
User + Resource + Environment attributes = allow/deny. Overkill for POS — business rules handle complex conditions.
PBAC (Policy-Based)
Policy files define access rules. Too complex for our use case — adds indirection without benefit.
◆ Auth vs Rule Engine CRITICAL DISTINCTION
The most common mistake in POS permission design: confusing authorization with business rules. These are two completely separate layers.
// "Can you ATTEMPT this action?" pos:discount → yes/no ¿Puedes INTENTAR esta acción?
// "Does the action follow RULES?" ¿Exceeds 20%? → yes/no ¿La acción cumple las REGLAS?
| Layer | Responsibility | Example |
|---|---|---|
| Auth (RBAC) | Can you attempt this action? | pos:discount → "Do you have this permission?" |
| Rule Engine | Does the action follow business rules? | "Is discount under 20%?" → blocked or allowed |
◆ Scope Separation
Three completely separate permission namespaces. HQ permissions NEVER mix with Store/POS permissions.
hq:products.createhq:reports.viewhq:users.managehq:stores.provisionhq:taxes.managestore:inventory.adjuststore:transfers.createstore:reports.viewstore:employees.managestore:purchases.createpos:sellpos:refundpos:discountpos:voidpos:price_override◆ Super Admin
isSuperAdmin flag in users table
Checked FIRST before any permission lookup. Automatically has ALL permissions — no need to assign them. Cannot be accidentally locked out. This is the escape hatch that prevents admin lockout during misconfiguration.
◆ Why Custom RBAC (No Libraries)
Evaluated existing permission libraries. Custom RBAC wins for our use case.
| Library | Type | Why NOT for us |
|---|---|---|
| CASL | ABAC | Designed for attribute-based. Our model is RBAC. Overkill. |
| Casbin | PBAC | Requires external policy files. Complex to debug. |
| Custom | RBAC | 200 lines, full control, no deps, easy to debug ✓ |
If system grows to need ABAC → migrate to CASL later. For now, custom is cleaner.
◆ JWT Design
userId // user identifier username // display name isSuperAdmin // bypass all checks appContext // hq | store | pos storeId // which store (if store/pos)
permissions // loaded fresh from DB // on every request roles // derived from DB lookup // WHY: When HQ revokes a permission, // it takes effect IMMEDIATELY. // No waiting for token expiry.
◆ Security
Argon2id
OWASP #1 recommendation for 2026. Replaces bcrypt. Memory-hard, resistant to GPU and ASIC attacks.
Dual-Layer Protection
5 attempts per user + 20 per IP per 15 min window. Account lockout after 5 failed attempts (30 min cooldown).
Single Session per App
New login revokes previous session for same user + app context. Prevents ghost sessions on shared POS terminals.
httpOnly Refresh Cookie
Refresh token stored in httpOnly cookie — not accessible to JavaScript. Prevents XSS token theft.
◆ Audit Trail
Log ALL sensitive actions — immutable (insert only, never update/delete).
◆ Database Schema
users id, username, passwordHash, isSuperAdmin, isActive roles id, name, scope (hq | store | pos), description permissions id, code, scope, description user_roles userId, roleId, storeId (nullable — null for HQ roles) role_permissions roleId, permissionId audit_logs userId, action, entityType, entityId, storeId, appContext, changes (jsonb), ip, timestamp, status revocation_list userId, revokedAt, reason
◆ Real-World POS Systems Studied
Shopify POS
Custom roles per staff member. PIN login for fast cashier switching. Granular permissions per action (discounts, refunds, reports).
Lightspeed
3 default roles: Admin, Manager, Cashier. Custom roles available on Enterprise tier. Role-based register access control.
ERPNext
3-level security model: Role → Document Type → Field level. Deep granularity but complex to configure. Good for ERP, heavy for POS.
Odoo
User types (Internal/Portal/Public) combined with role permissions. Group-based access with record rules. Mature but monolithic.
◆ Implementation Plan
How it maps to our module structure:
packages/ → nothing auth-specific (custom RBAC is server-only) apps/hq-server/ src/ lib/auth.ts → hashPassword, verifyPassword, createToken, verifyToken lib/permission.ts → PermissionService (getUserPermissions, checkPermission) lib/authorize.ts → Fastify middleware (validateJWT, loadPermissions, requirePermission) modules/auth/routes.ts → login, refresh, logout, revoke db/schema/auth.ts → users, roles, permissions, user_roles, role_permissions, audit_logs
◆ Dependencies
Only 2 external packages. Everything else is custom.
argon2
Password hashing. OWASP-recommended. Memory-hard algorithm resistant to GPU/ASIC attacks.
jose
JWT sign/verify. Modern, maintained, standards-compliant. Universal JavaScript implementation.
Permission sync between HQ and Stores is a sync concern
Permission sync between HQ and Stores is covered in the Sync Architecture section. Auth module only handles authentication and authorization — how permissions propagate is a sync concern, not an auth concern.
- Permission Architecture: Auth0 — Role-Based Access Control
- Permission Architecture: Oso — RBAC Explained
- Permission Architecture: NIST — RBAC Standard
- JWT: RFC 7519 — JSON Web Token
- JWT: Auth0 — Refresh Token Rotation
- Security: OWASP — Password Storage Cheat Sheet (Argon2)
- Security: OWASP — Authentication Cheat Sheet
- Security: OWASP — JWT Security Best Practices
- POS Systems: Shopify POS — Staff Permissions
- POS Systems: Lightspeed — Staff Roles & Permissions
- POS Systems: ERPNext — Users and Permissions
- POS Systems: Odoo — Security and Access Rights
- Libraries: CASL — Isomorphic Authorization
- Libraries: Casbin — Authorization Library
- Libraries: jose — JavaScript JOSE Implementation
Offline-First Architecture
How should POS terminals handle network failures gracefully while ensuring data consistency when connectivity returns?
Research needed on offline-first patterns for POS terminals
- Local Database Strategy — SQLite vs IndexedDB vs OPFS for offline storage on POS terminals
- Conflict Resolution — Handling concurrent offline edits: CRDTs, operational transforms, or application-level merge
- Queue Management — Outbox queue for offline sales: ordering guarantees, retry policies, deduplication
- Data Freshness — How stale can product prices/inventory be? Configurable staleness thresholds
- Partial Sync — Syncing only changed data vs full snapshots: delta sync patterns for constrained bandwidth
- Offline Auth — Authenticating users when HQ is unreachable: cached credentials, time-limited tokens
- Service Worker Strategy — PWA offline caching: cache-first vs network-first per resource type
Architecture Decision Log
| Decision | Choice | Alternatives Considered | Origin | Status |
|---|---|---|---|---|
| Business Rules Engine | Before/After Hooks | Condition/Action, JSON Rules Engine | V1 | Done |
| Shared Code Strategy | pos-logic package | Copy-paste, git submodules | V1 | Done |
| Sync Version Tracking | Global version counter | Per-row version (V1, broken) | V1 | V2 |
| Sync Architecture | Registry-based per-entity handlers | Monolithic sync, event sourcing | V1 | V2 |
| Conflict Resolution | Per-entity ownership (HQ/Store wins) | Generic CRDT, last-write-wins for all | V1 | V2 |
| Backend Abstraction | Command-based adapter | Direct fetch(), separate API clients | V1 | Done |
| Money Representation | Cents (integers) | Decimals, strings | V1 | Done |
| Tax Rates | Basis points (825 = 8.25%) | Decimals, percentages | V1 | Done |
| Store ↔ HQ Communication | WS notifications + HTTP data | Pure WS, polling, gRPC | V1 | Done |
| Outbox Pattern | Transactional outbox | Direct push, event sourcing | V1 | Done |
| Database Schema Strategy | TBD | Normalized, denormalized, hybrid | V2 | Planned |
| Authentication Model | Custom RBAC + Argon2 + Jose | CASL (ABAC), Casbin (PBAC), bcrypt, sessions | V2 | Done |
| Auth vs Business Rules | RBAC for permissions, Rule Engine for business logic | ABAC with attribute-based rules, mixed auth+rules | V2 | Done |
| Offline Strategy | TBD | SQLite, IndexedDB, OPFS | V2 | Planned |
All Sources
// Rule Engine
- ERPNext/Frappe Document Lifecycle Hooks — Python before/after submit pattern
- Odoo ORM Constraints & Computed Fields — Decorator-based validation
- Mongoose Middleware (Hooks) — Pre/post hooks for document lifecycle
- Fastify Hooks Documentation — onRequest, preHandler, preSerialization pattern
- json-rules-engine (npm) — Declarative JSON rule evaluation
- ts-rule-engine — TypeScript condition/action rule framework
// Sync Architecture
- SQL Server Change Tracking — Global version counter pattern
- Couchbase Sync Gateway: Sequence Handling — Global sequence for sync
- Dynamics 365 Row Version Change Tracking
- Oracle Retail Integration Bus — Event-driven per-entity integration
- SAP Retail CIF Documentation — Modular IDoc-based entity sync
- Transactional Outbox Pattern — Reliable at-least-once delivery
- Event Sourcing vs Event-Driven — Architecture comparison
- Dynamics 365 Commerce Architecture — Multi-store sync
// Tauri + Shared Code
- GitButler — Tauri + pnpm monorepo (50k+ stars, reference architecture)
- Clash Nyanpasu — Tauri v2 monorepo with shared packages
- Tauri v2 Frontend Configuration — Official docs
// Feature Roadmap
- Best POS Features 2026 — Electronic Payments
- Essential POS Features — RetailCloud
- Multi-Store POS Guide 2026 — AppIntent
- Retail KPIs Guide 2026 — Improvado
- 25 Retail KPIs — NetSuite
- Retail Industry Metrics — Tableau