Documentation
IntroductionArchitectureMonorepo & ToolingRunning Locally
Screen Flow MapUser JourneysPersonas & RolesWeb ScreensClient Portal
Convex OverviewData ModelFunctions ReferenceAuth & SessionsRoles & PermissionsJobs & Providers
Web Design SystemAdmin Console DesignPatterns & Conventions
Web AppiOS AppMarketing Website
Testing & HarnessCI Pipeline

Convex Overview

Convex Overview

packages/backend is a Convex deployment — the single source of truth for schema, business rules, realtime state, storage, crons, and HTTP endpoints. Everything else in the platform is a client of it.

Why Convex

  • Realtime by default. Every query is a live subscription. The facility board, dashboard, badges, and message threads update across sessions with no polling or refresh — the harness verifies a second browser session sees board changes live.
  • Typed end to end. The schema generates TypeScript types (_generated/dataModel), and the function surface generates a typed api object consumed by the web app (@pawdentity/backend/api).
  • Transactional mutations. Each mutation is atomic — order creation can compute totals, decrement inventory, assign an order number, and write an audit log without partial states.

Module map

convex/
├── schema.ts            31 tables + shared validators (see Data Model)
├── lib/
│   ├── functions.ts     orgQuery / orgMutation wrappers, requireRole,
│   │                    requirePolicy, hasPolicy, logAudit, authError
│   └── password.ts      PBKDF2-SHA256 hashing (Web Crypto)
├── auth.ts              signIn / signOut / me / sessions
├── orgs.ts              shell bootstrap, general settings, logo
├── clients.ts  pets.ts  petPhotos, CRM
├── visits.ts            booking + the state machine transition()
├── careEvents.ts        care log
├── medications.ts       standing needs + administrations
├── issues.ts            pet issue lifecycle
├── agreements.ts        required-agreement checks, desk signing
├── documents.ts         uploads with MIME allowlist
├── products.ts          catalog
├── orders.ts            POS, payments, refunds, fulfillment
├── paymentMethods.ts    dev card vault + charging
├── portal.ts            customer self-service surface
├── messages.ts          threads + messages + attachments
├── notes.ts             team board with acks
├── staff.ts             roster, invites, memberships
├── timeclock.ts         punches + approvals
├── schedules.ts         weekly shift grid
├── board.ts             the live facility board query
├── dashboard.ts         the daily dashboard query
├── reports.ts           7 report queries
├── settings.ts          locations, policies, agreements, reminders, food brands, feeds
├── search.ts            global client+pet search
├── platform.ts          landlord surface (applications, directory)
├── seed.ts              deterministic demo world
├── crons.ts             visit sweep + reminder dispatch
├── http.ts              ICS/JSON calendar feed endpoints
├── providers/
│   ├── stripe.ts        production Stripe driver
│   └── email.ts         Resend driver
└── authz.test.ts        27-case authorization suite

Life of a request

Page component
useOrgQuery / useOrgMutation
passes domain args only
Wrapper injects
{ sessionToken, orgSlug }
resolveOrgContext
session → org → membership
typed errors on failure
ctx gains
{ user, org, membership }
Feature handler
requirePolicy("area.action") → reads ctx.org
db writes + logAudit
result / reactive push to every subscriber
Feature code never filters by orgId — reading ctx.org is the isolation guarantee, and the authz suite proves cross-tenant ids resolve to null.
Life of an org-scoped call — tenancy is resolved once, in the wrapper, never in feature code

Function conventions

  • Org-scoped functions are built with orgQuery / orgMutation (convex-helpers custom functions). They implicitly take sessionToken + orgSlug, resolve {user, org, membership} into ctx, and typed-error out on failure. Feature code reads ctx.org — it never filters by orgId manually.
  • Actions are used only where external I/O is needed: password hashing at sign-in, Stripe API calls, Resend email.
  • Internal functions (internalQuery / internalMutation / internalAction) back crons, seeding, and provider callbacks.
  • Typed errors: failures throw ConvexError({code}) with UNAUTHENTICATED / FORBIDDEN / ORG_NOT_FOUND so codes survive production redaction and the web app's error boundaries can map them to screens. Detail reads return null (not errors) for missing or cross-tenant ids — no existence leaks.
  • Audit: nearly every mutation calls logAudit(ctx, action, entity, entityId, summary) into the auditLogs table.

Realtime surfaces worth knowing

QueryPowersNotable payload
board.liveFacility board / kioskrooms, in-house pets with care chips, arrivals with agreement + deposit prompts, counts
dashboard.dailyStaff dashboardKPIs, arrivals/departures, HR row, birthdays
orgs.shellThe app shellbranding, my role, locations, unread badges, clocked-in state
messages.threadListInboxunread counts, previews

Scheduled work & HTTP

Two crons (visit status sweep every 15 min, reminder dispatch every 30 min) and three token-authenticated HTTP feed endpoints (ICS + JSON). Detail: Jobs & Providers.

Related

  • Data Model — every table and field
  • Functions Reference — every exported function
  • Auth & Sessions · Roles & Permissions