Sajt · Docs

Convex functions

The authz-first calling pattern, Convex 1.41 conventions, and a map of the key backend function modules.

Synthesis note: there is no single convex-functions.md source doc. This page is assembled from the directory map and conventions in docs/ARCHITECTURE.md plus the gate definitions in docs/auth-and-permissions.md. When in doubt, read the module under convex/.

The Convex backend (1.41) holds the database, queries, mutations, actions, file storage, and HTTP actions. Stack details live in Architecture.

The authz-first pattern (non-negotiable)

Every website-scoped function takes the client-supplied id, then immediately calls a require*Access helper that loads the row and verifies the current verified user's membership before the id is used for anything. Client ids are never trusted directly; identity always comes from ctx.auth.getUserIdentity(), never from a function argument.

const { user, website, role } = await requireWebsiteAccess(ctx, args.websiteId);
// safe to proceed — identity verified, edit permission confirmed

The capability gates (convex/lib/authz.ts):

HelperGate
requireUser(ctx)signed-in
requireWebsiteRead(ctx, websiteId)any member (viewer ok) — viewer-visible reads only
requireWebsiteAccess(ctx, websiteId)owner / editor — the default for reads + writes
requireWebsiteOwner(ctx, websiteId)workspace owner — delete site, manage people
requirePageRead / requirePageAccess(ctx, pageId)resolved via page.websiteId
requireSectionRead / requireSectionAccess(ctx, sectionId)resolved via section.websiteId

The default is fail-closed: a forgotten gate denies viewers, never grants access. Any new function that uses a client id without a require* gate is a bug. Full detail in Auth & permissions.

Convex conventions

  • Queries: use .withIndex(...), never .filter(...). Use .take(n) / .paginate(...), never an unbounded .collect() on a growable table.
  • db API (1.41): ctx.db.get("table", id), insert("table", v), patch("table", id, v), delete("table", id).
  • Content paths: mutate section content via lib/editor/contentPath.ts setPath / getPath (these reject __proto__ / constructor / prototype).
  • Ordering: sections.order is a LexoRank string (lib/editor/fractionalIndex.ts); moves are single-row writes.
  • Undo: mutations that change content record an inverse op in changeHistory so history.ts can replay it (undoLast / redoLast).
  • Cross-reference defense in depth: after the gate verifies the parent row, a function re-checks that a referenced child belongs to the same website (e.g. asset.websiteId === section.websiteId in assets.ts setSectionImage).
  • Tests: put *.test.ts in convex/; inject identity with t.withIdentity({ tokenIdentifier, subject }) and run bun run test.

Key function modules

ModuleResponsibility
editor.tsReactive draft reads: getDraftSite, getPageSections
sections.tsEditor mutations: updateSectionContent, reorder, hide, duplicate, delete, changeSectionVariant, changeSectionTone, addSection, updateTheme
history.tsLinear undo / redo over the inverse-op log (undoLast, canUndo)
publish.tspublishWebsite / publishWebsiteCore, getPrepublishChecks (QA), getPublishedSnapshot, getSiteByHost
assets.tsImage upload + setSectionImage + getAssetUrls
websites.tsSettings: updateBusinessInfo, updateSlug, setStatus, deleteWebsite, route resolution
posts.tsPosts (pages with pageType:"post"): createPost, listPostsAdmin, updatePostMeta, setFeaturedImage
services.tsPhase S canonical offering CRUD (OCC rev)
offers.ts / invoices.tsQuotes and invoices (own number series, token pages)
inbox.tsLead + booking management feed
domains.tsconnectDomain / verifyDomain (DoH), getSlugByHost
aiChat.tsAI editor assistant: sendMessage (agent loop), applyStagedEdits, applyTool (the single tenant-safe executor)
generate.ts + generation/*Deterministic site generation + optional LLM polish
onboarding.tsOnboarding draft CRUD + getLatestDraft (resume)
invites.ts / members.tsSharing: invite create/accept/revoke; member list/remove (owner-only)
analytics.ts + http.tsPublic /track, /lead, /booking endpoints + summaries
presence.tsLive multiplayer editor presence (ephemeral)
restorePoints.tsVersion-history timeline (normalized draft snapshots)
maintenance.tsDerived in-app QA nudges

Public (no-account) endpoints

Published sites POST to Convex HTTP endpoints in convex/http.ts (/lead, /booking, /booking-slots, /track). These are public by design but rate-limited transactionally (convex/lib/rateLimit.ts, rateLimits table) keyed by slug / IP, and they write only into runtime tables. They resolve the site by slug server-side and re-validate against the published config — see Routes and Auth & permissions.

On this page