Concepts

Sync & cursors

How Clivly tracks what has already synced, and why request-scoped hosts need a persistent cursor store.

Persist the cursor on request-scoped hosts

Serverless and edge runtimes need persisted cursors so delta sync survives cold starts.

clivly.config.ts
const clivly = createClivlySDK({
apiKey: process.env.CLIVLY_SECRET_KEY ?? process.env.CLIVLY_API_KEY!,
entities,
source: { /* ... */ },
cursorStore: {
  get: async (entity) => {
    const raw = await kv.get(`clivly:cursor:${entity}`);
    return raw ? { id: raw.id, time: new Date(raw.time) } : null;
  },
  set: (entity, cursor) =>
    kv.put(`clivly:cursor:${entity}`, cursor && {
      id: cursor.id,
      time: cursor.time.toISOString(),
    }),
},
});

If every cold start re-reads the entire dataset, add a persistent cursorStore. Without one, start() keeps its cursor in memory, which is fine on a long-lived Node host but is lost on every invocation of a request-scoped one.

If rows sync on insert but never on edit, the cursor column does not change when a row is updated. Add an updated_at column and use it as cursorField.

The sync trigger

The sync trigger is how Clivly Cloud reaches your app to ask for a sync, rather than your app dialling in.

clivly.config.ts
syncTrigger: {
// `path` lets the SDK build its own public URL from the hosting platform's
// environment, so on those platforms you never set the URL by hand.
path: "/api/clivly/tick",
url: process.env.CLIVLY_SYNC_TRIGGER_URL,
secret: process.env.CLIVLY_SYNC_TRIGGER_SECRET,
},

CLIVLY_SYNC_TRIGGER_SECRET signs the cloud's requests to your tick route. With it set, unsigned requests are rejected with 401. Without a secret the route stays open to anyone who can reach it — always set one if the route is publicly routable.

CLIVLY_SYNC_TRIGGER_URL tells the cloud where the route lives. The SDK also self-reports this on each heartbeat, deriving it from your platform's env (Vercel, Cloudflare Pages, Railway, Render, Fly).

Rotating the secret requires redeploying the host — remote sync fails until both sides match.

Where the trigger URL resolves in development vs production

See How Clivly reaches your app for the full precedence order between clivly dev, the platform-detected origin, and an explicit CLIVLY_SYNC_TRIGGER_URL.