Ingesting changes

Webhook handlers and other trusted backend code can update a registered dataset with a primary key. This example uses the people dataset:

TS
import { change } from "@sixb/core"
import { people } from "../datasets/people"

await sixb.datasets.ingest(people, {
  changes: [
    change.upsert({ id: "42", name: "Sam", updatedAt: "2026-09-09T10:00:00.123Z" }),
    change.delete({ id: "9" }, { sequence: "2026-09-09T10:01:00.000Z" }),
  ],
})

ingest validates the changes and stores them. It returns once they are stored, before they are in the dataset. A worker then writes the changes waiting for the dataset as one new version, usually within a second, so many webhooks give a few versions instead of one each.

ContractBehavior
InputIterable or async iterable of complete-row upserts and primary-key deletes
CancellationOptional signal: AbortSignal, until the changes are stored
ValidationRegistered schema, primary key, source sequence, referenced blobs, and values the lake cannot hold, such as a date DuckDB does not read; errors throw from ingest
Result{ deliveryId, changes }: the id that names this call's changes in failure reports (null when there were none), and their count
OrderChanges apply in the order ingest calls stored them, as one call after another would
Downstream workEach new version emits dataset.version.committed; pipelines and projections run asynchronously
AuthorityTrusted backend executions, or explicitly disabled authorization; dataset-view grants do not permit ingestion

Use sequenceBy when webhooks and syncs share a dataset.

DatasetConcurrent writesSnapshot sync
With sequenceByNewer source values winOrdered upserts; omitted rows stay
Without sequenceByThe later write winsReplaces rows

Existing unkeyed dataset? Create a new keyed dataset → backfill → repoint consumers. Stored primary keys and sequenceBy are immutable.

For bulk loads, use a sync: ingest keeps each call in the database until a worker writes it.

React to new data

Code that needs the ingested data does not wait for ingest. Trigger it from the new version instead, here a sync that reads the dataset:

TS
import { defineSchedule, events } from "@sixb/core"
import { people } from "../datasets/people"

export const peopleUpdated = defineSchedule("people-updated").on(events.dataset(people).updated())

Attach it to the sync, pipeline or workflow with .when(peopleUpdated). It fires for versions that changed rows, and a burst of them joins one waiting run.

Objects and relationships

Ingestion updates source data. Existing pipelines and projections determine the object result:

ChangeDownstream behavior
Source record updatedPipeline recalculates merged data; projection updates its properties and links
Application editProjection's conflict policy still applies
Source row removedProjection withdraws its claims; the ontology object is not automatically deleted
Foreign-key target missingProperties can materialize, but no edge is created; the old source-owned link is withdrawn

Where it runs and what can fail

  • The lake worker writes ingested changes. sixb dev and sixb worker-group include it; a deployment that names its worker types must list lake, and sixb deploy warns when it does not.
  • Changes reach the lake with no orchestrator running. Keep it on all the same: it reports changes that wait, and asks again for a write that was lost when no other ingest call comes.
  • A server embedded with createSixbServer has no lake worker, so ingest writes the changes before it returns. See Custom server setup.
  • Console › Lake health shows the changes still waiting per dataset, and since when, and sixb check names the datasets whose changes nothing has written for 10 minutes.
  • A worker that stops while writing resumes where it stopped, and never writes changes twice.
FailureWhat happens
The lake or blob storage is unavailableRetried until it succeeds; dataset.ingest.flush.failed reaches onError at the fifth failure in a row
The queue refuses the write or is downingest still succeeds, after waiting one second at most: the changes are stored, and the next ingest after 30 seconds asks again, as the orchestrator does on its own
Changes wait 10 minutes and nothing writes themThe orchestrator reports dataset.ingest.flush.failed to onError
A stored change is refused when written: no longer valid for the schema, a deleted blob, a value the lake cannot hold that ingest could not tell, or a source sequence another call holds with other contentThat call's changes stay aside for a week, whole, and dataset.ingest.rejected names it and the refused change; the other calls' changes are written. sixb lake rejected prints them to fix and send again, and Console › Lake health counts them
A rollout runs a lake worker older than the API, and the worker refuses what the API storedNothing is refused: the changes stay stored and writing goes on trying until the worker runs the same code

Search docs

Search the documentation