Skip to main content

Data Model and Migrations

SalesArck data definitions live in packages/db/src/schema/index.ts and are migrated via Drizzle SQL files under packages/db/drizzle.

Migration Sequence

0000_puzzling_proudstar.sql

Initial schema bootstrap:

  • users, tenants, tenant_users
  • pos_connections, pos_webhook_events_raw
  • transactions
  • wallets, wallet_ledger
  • reward_rules, reward_events
  • idempotency_keys
  • admin_audit_logs
  • constraints and indexes

0001_lovely_patch.sql

Merchant onboarding fields added to tenants:

  • pos_provider_preference
  • onboarding_profile (JSONB)
  • updated_at

0002_tired_cannonball.sql

Square polling watermark added:

  • pos_connections.square_payments_watermark_at

Table Reference

Users and Identity

ColumnTypeConstraintsDescription
iduuidPKSupabase user UUID (not auto-generated)
emailtextunique, nullableUser email address
mobiletextunique, nullableUser phone number
roletextnot nullconsumer, client, pos_operator, admin
statustextnot nullactive, pending_approval, suspended
created_attimestampdefault now()
updated_attimestamp

Tenants

ColumnTypeConstraintsDescription
iduuidPK, default genTenant identifier
nametextnot nullBusiness name
countrytextnot null, default 'US'ISO 3166-1 alpha-2
statustextnot null, default 'active'
pos_provider_preferencetextnullablesquare, clover, other
onboarding_profilejsonbnullableBusiness setup fields
created_attimestampdefault now()
updated_attimestampnullable

Tenant Users

ColumnTypeConstraintsDescription
iduuidPK
user_iduuidFK -> users, not null
tenant_iduuidFK -> tenants, not null
roletextnot nullclient, pos_operator
(user_id, tenant_id)uniquePrevents duplicate membership

POS Connections

ColumnTypeConstraintsDescription
iduuidPK
tenant_iduuidFK -> tenants, not null
pos_typetextnot nullsquare, clover
access_token_enctextnot nullAES-256-GCM encrypted
refresh_token_enctextnot nullAES-256-GCM encrypted (empty for Clover)
merchant_account_idtextnot nullProvider's merchant identifier
statustextnot nullactive, disconnected, token_expired, error
token_expires_attimestampnullable
last_synced_attimestampnullable
square_payments_watermark_attimestampnullableHigh-water mark for polling
created_attimestampdefault now()

POS Webhook Events Raw

ColumnTypeConstraintsDescription
iduuidPK
tenant_iduuidFK -> tenants, not null
providertextnot nullsquare, clover
event_typetextnot null
raw_payloadjsonbnot nullUnmodified webhook body
signature_headertextnullableFor replay verification
processedbooleandefault falseProcessing marker
created_attimestampdefault now()

Transactions

ColumnTypeConstraintsDescription
iduuidPK
tenant_iduuidFK -> tenants, not null
user_iduuidFK -> users, nullableNull for anonymous transactions
providertextnot nullsquare, clover
provider_transaction_idtextnot null
amount_minor_unitsintegernot nullCents
currencytextnot null, default 'USD'
customer_refjsonbnullable{ email?, mobile?, externalCustomerId? }
line_itemsjsonbnullable
transaction_attimestampnot null
created_attimestampdefault now()
(tenant_id, provider, provider_transaction_id)uniqueDedup guard

Wallets

ColumnTypeConstraintsDescription
iduuidPK
tenant_iduuidFK -> tenants, not null
user_iduuidFK -> users, not null
points_balanceintegernot null, default 0Fast-read snapshot (truth is ledger)
statustextnot null, default 'active'active, frozen
created_attimestampdefault now()
updated_attimestamp
(tenant_id, user_id)uniqueOne wallet per tenant+user

Wallet Ledger

Append-only. No UPDATE or DELETE in production.

ColumnTypeConstraintsDescription
iduuidPK
wallet_iduuidFK -> wallets, not null
entry_typetextnot null, CHECK in (credit, debit)
pointsintegernot null, CHECK > 0Always positive; direction from entry_type
descriptiontextnullable
source_transaction_iduuidFK -> transactions, nullable
created_by_adminuuidFK -> users, nullableFor admin adjustments
created_attimestampdefault now()

Reward Rules

ColumnTypeConstraintsDescription
iduuidPK
tenant_iduuidFK -> tenants, not null
versionintegernot nullAuto-incremented per tenant
is_activebooleandefault trueOnly one active per tenant
rule_configjsonbnot nullRuleConfig schema
effective_fromtimestampnot null
created_attimestampdefault now()

rule_config JSONB schema:

{
"conversionRate": 1.0,
"minSpendMinorUnits": 0,
"maxPointsPerTransaction": 500,
"minBalanceToRedeem": 100,
"maxRedemptionPercentage": 100,
"roundingPolicy": "floor"
}

Reward Events

ColumnTypeConstraintsDescription
iduuidPK
wallet_iduuidFK -> wallets, not null
transaction_iduuidFK -> transactions, nullable
rule_version_iduuidFK -> reward_rules, nullable
points_earnedintegerdefault 0
points_redeemedintegerdefault 0
created_attimestampdefault now()

Idempotency Keys

ColumnTypeConstraintsDescription
keytextPK{provider}:{merchantId}:{providerTxnId}
tenant_iduuidFK -> tenants, nullable
handlertextnot nullE.g. square_webhook, clover_webhook
result_snapshotjsonbnullable
expires_attimestampnot nullDefault: 30 days from creation
created_attimestampdefault now()

Admin Audit Logs

Immutable. No UPDATE or DELETE.

ColumnTypeConstraintsDescription
iduuidPK
actor_user_iduuidFK -> users, not nullAdmin who performed action
actiontextnot nullE.g. user.approve, wallet.freeze
target_entity_idtextnullable
target_entity_typetextnullable
before_statejsonbnullableState before action
after_statejsonbnullableState after action
reasontextnullable
created_attimestampdefault now()

Indexing and Query Behavior

Indexes are aligned to operational queries:

  • tenant_users_tenant_idx — membership lookups
  • Transaction timeline and tenant/user filters
  • processed marker on pos_webhook_events_raw — unprocessed event queries
  • Active reward rule lookup per tenant
  • Wallet ledger time-series reads
  • Audit actor/target/time queries

ORM Client

packages/db/src/client.ts uses pg.Pool + Drizzle with:

  • Max connections: 10
  • Idle timeout: 20s
  • Connection timeout: 15s

Runtime uses DATABASE_URL (typically Supabase Transaction Pooler). Migrations should use direct DB URL where required by platform.

Data Integrity Conventions

  1. Tenant ID appears on all tenant-scoped aggregate tables.
  2. Mutable snapshots are paired with immutable ledger/event records.
  3. Dedup is enforced at DB layer, not only application memory.
  4. Audit logs are additive, never update-in-place.
  5. Amounts stored in minor units (cents) to avoid floating-point errors.
  6. JSONB columns used for flexible extensibility (onboardingProfile, ruleConfig, customerRef).
Written byDhruv Doshi