Something went wrong

Thank you for being patient! We're working hard on resolving the issue

The Integration + Row contract - Lona Docs Log in

The Integration + Row contract

Each reserved row pattern (e.g. :~strava:{authId}:activities) gets one Row impl. Rows group under one Integration that owns the per-account "grab bag" client.

Integration

One impl per provider account-type. Declares:

  • type Deps — host-supplied trait the connector threads into every Row::sync / Row::list_cells / write-verb call. Always a dyn <Provider>BackendDeps that you declare in the same module.
  • type Client — the per-account "grab bag". Typically holds an Arc<crate::Client> (your SDK client) plus any cross-cutting seams from Deps that you want available without indirection.
  • type Params — path-level params extracted from the rowkey (e.g. {authId} for garmin, {authId, calId} for google). Plain serde::Deserialize.
  • fn client(user, deps, params) — assembles the grab-bag. Resolves the typed credential via user.auth::<MyProviderAuth>(...), builds the HTTP client.

Optional:

  • fn client_for_write(deps) — assembles a write-side grab-bag without an authenticated user. Default: errors. Override for integrations that accept writes from system contexts.

Row

One impl per reserved pattern. Required:

  • type Integration — the parent Integration impl.
  • type Params — additional params beyond the integration's (often ()).
  • const ROW_LABEL: &'static str — human-friendly name.
  • fn lookup_key(int, own) — formats the canonical rowkey from the params.

Defaulted (override what you need):

MethodDefaultOverride when
const ROW_TYPE"data"Integration-scope rows that should not be listed as cell-bearing rows.
const CELL_ENCODINGNoneWire-cell encoding has a tagged decoder (e.g. "event.v1").
fn attributesNoneComputed/formula rows that stamp initial JSON onto the row record.
fn sync_shapeNoneSync-backed rows. None = no orchestrator registration.
fn syncerrorsRequired when sync_shape is Some.
fn alias_keyNoneShared-data providers (e.g. weather rows aliased to a system owner).
fn lock_resource_idNoneProviders needing serialised full-resyncs (e.g. Google Calendar's per-calendar sync token).
fn list_cellsNoneComputed-cell rows (logs, aggregators). Returning Some(cells) makes this row the canonical source of truth — the host won't fall through to its persistent cell store.
fn post / patch / deleteerrorsWriteable rows.

Ctx structures

Each callable receives an arg bundle:

  • SyncCtx<R> — passed to Row::sync. Carries the grab-bag client, integration params, row params, current stage, owner, pre-upserted row id, opaque dispatch payload, and the host deps reference.
  • WriteCtx<R> — passed to Row::post/patch/delete. Same shape as SyncCtx minus the stage/dispatch fields.
  • ReadCtx<R> — passed to Row::list_cells. Carries integration params, row params, owner, the requested date range, and deps.

The connector is platform-agnostic — it never invokes methods on deps. Your row body destructures the fields it needs, including ctx.deps.persist_cells(...) etc.

What goes in Client vs BackendDeps

  • Client is for read-side resources — typically the HTTP client plus any read-mostly host seams that get used in many rows. Built once per request via Integration::client.
  • BackendDeps is for host-side write/state seams — things that translate to host-data types (cell store, row store, log query). Threaded as a reference; never owned by the connector.

A single seam can live in either place. If it's only ever called from one row's sync body, putting it in BackendDeps keeps the grab-bag light. If multiple rows share it, putting it in Client saves a method call per row.