modernx-admin — Ingest delta detection (computeDelta)
Ingest (lib/ingest/) is what feeds the automations engine (see
Automations engine entity model) its
TriggerEvents in the first place: as Magento data lands in the warehouse,
ingest decides which rows are new or changed and enqueues the matching
trigger. Until 2026-07-27 that decision logic lived only inside
upsertOrders, bespoke to orders. It’s now extracted into a reusable,
domain-agnostic helper so the other six domains that already populate the
warehouse silently (customers, products, creditmemos, invoices, shipments,
quotes) have a mechanism to plug into once their own trigger types exist.
computeDelta — pure delta classification
Section titled “computeDelta — pure delta classification”lib/ingest/delta.ts exports:
computeDelta<T, Key>( prior: Map<Key, T>, incoming: T[], opts: { keyOf: (row: T) => Key; watched: Array<{ field: keyof T; triggerType: string }> },): { created: T[]; changed: Array<{ row: T; triggerType: string }> }created— incoming rows whosekeyOfresult is absent fromprior.changed— one entry per watched field whose prior value differs from the incoming value, for every incoming row that is present inprior. A row with two watched fields that both changed yields two entries; callers decide whether to enqueue one event per entry or dedupe upstream (orders only watches one field today, so this doesn’t come up yet).- Classification is decided solely from the
priorsnapshot map — never from whether a row is still present in some other store — because callers delete-then-insert every row in the batch before calling this, which makes table presence useless for telling “new” from “re-ingested”. A key present inprioris never misclassified ascreated, even when the incoming row shares no object identity with anything inprior. - Zero dependencies on
Db/drizzle/automation types — pure and unit-testable without a DB fixture.
delta-config.ts — per-domain wiring
Section titled “delta-config.ts — per-domain wiring”lib/ingest/delta-config.ts exports a DomainDeltaConfig<T, Key> type (key
extractor, createdTriggerType, watched list — the shape computeDelta’s
opts expects, plus the created-trigger type) and a domainDeltaConfigs map
with exactly one populated entry so far:
domainDeltaConfigs.order = { keyOf: (row) => row.sourceOrderId, createdTriggerType: ORDER_PLACED_TRIGGER, watched: [{ field: 'status', triggerType: ORDER_STATUS_CHANGED_TRIGGER }],}Adding a new domain (customer, product, …) once it has real trigger types
is one more entry here — computeDelta itself never changes.
upsertOrders rewrite
Section titled “upsertOrders rewrite”upsertOrders (lib/ingest/upsert.ts) now builds its prior map (keyed by
sourceOrderId, holding the prior status) from a read inside the same
transaction as the delete-then-insert, then calls
computeDelta(prior, orders, domainDeltaConfigs.order) to derive
newOrders/changed, replacing the old bespoke priorById filter logic.
Everything downstream of that call is unchanged:
- Same post-commit, failure-isolated block — a
computeDelta/enqueue error is caught and logged, never fails the already-committed ingest batch. - Same
hasActive(...)gate per trigger type before building events. - Same event shapes, including the
entityKey=`${sourceOrderId}:${status}`composite key for status-changed events.
No other upsert function (upsertCustomers/Products/Creditmemos/Invoices/ Shipments/Quotes), applyTombstones, or processBatch changed — those six
domains still populate the warehouse without detecting deltas or enqueuing
anything, same as before this change.
Why this exists
Section titled “Why this exists”This is scaffolding only, same posture as the engine’s entity-model
generalization: no new trigger types are registered, and order behavior is
byte-identical to before (every existing order-trigger and status-changed
test in upsert.test.ts passes unmodified). It’s bead A4 of the same
trigger-breadth arc that produced the DomainEntity/entityLoaders
registry — later beads (B1 customer, B2 product, B3 order-lifecycle) add a
domain’s trigger-type constants and its domainDeltaConfigs entry, then
wire its upsert function through computeDelta the same way orders is
wired here.
See also Scan-trigger infrastructure for the counterpart mechanism covering the three trigger types that can’t be delta-detected on an ingest push at all (cart-abandoned, spending-threshold-crossed, inactive-customer) because they’re states an entity ages into rather than a change that arrives on a batch.
Source: modernx-admin PR #391.