execute returns items — covering upsert matching, change detection, and what happens to items that are no longer returned.
Prerequisite: read Data sources and sync first; this page goes deeper on the mechanics.
How the upsert works
Afterexecute returns its ExuluItem[], IMP calls context.createItem(item, config, user, role, upsert) for each item, where upsert is true whenever the item carries external_id or id.
The upsert is implemented in ExuluContext.createItem (src/exulu/context.ts, line 644–651):
external_id column has a UNIQUE constraint (set at table-creation time), so onConflict("external_id").merge() is a true upsert: it inserts when no row with that external_id exists and updates all supplied fields when one does.
The same upsert path runs whether the source fires via the admin UI, the queue worker, or a programmatic call to
source.execute.
Hash-based change detection
IMP does not compare old and new values automatically — that is the source’s responsibility. The standard pattern is to store a content hash in a customcontent_hash field and compare it before returning an item:
content_hash to the context’s fields array so IMP creates the column:
Deletion handling
IMP does not delete items that a source stops returning. Whenexecute returns a set of items, IMP upserts them but performs no reconciliation against items already in the table that are absent from the current result. A row persists until it is deleted explicitly via the admin UI, the GraphQL mutation, or context.deleteItem().
For sources where items can disappear (a ticket is permanently erased, a document is removed from a bucket), the recommended pattern is an archive flag:
- Add an
activeboolean field to the context. - In the source, after fetching the current set of external IDs from the API, query
getItemsfor all rows, and return a synthetic item for each that is no longer present — withactive: falseand no content change — to mark it as inactive. - Filter inactive items out of retrieval using item filters on the retrieval side.
context.deleteItem({ external_id: "…" }) from outside execute, or use the admin interface.
Watermark params
Use the source’sparams to pass a watermark into execute:
start_time param defaults to 25 hours ago when omitted, giving a one-hour overlap that catches items updated near the previous sync boundary. Pass full_sync=true in the admin dialog to rebuild from scratch.
Related recipes
S3-backed context
List an S3 bucket, hash files, and upsert items with file references.
API sync — ticketing
Paginated incremental fetch with retry/rate-limit hardening.
SQL source
Query rows from an external database with an updated_at watermark.
Data sources and sync tutorial
The full tutorial that introduces sources and sync.