@xemahq/declarative-reconciler
v0.3.1
Published
Generic declarative reconciliation engine for computing and applying diffs between desired and observed state.
Readme
@xemahq/declarative-reconciler
Converge an actual set toward a desired set by key and content hash
Overview
A framework-agnostic engine that converges an actual set toward a desired set by a stable natural key and a content hash, with a retire pass for owned items that are no longer desired. It depends only on Node built-ins.
When to use it
- To seed or sync owned rows idempotently from a declarative source, with a dry-run plan.
Installation
pnpm add @xemahq/declarative-reconcilerUsage
Implement a ReconcilerPort for your domain and drive it:
import { reconcile, stableHash, type ReconcilerPort } from '@xemahq/declarative-reconciler';
const port: ReconcilerPort<DesiredPortal, ActualPortal> = {
computeDesired: () => derivePortalsFromInstalledBiomes(orgId),
readActual: () => listPortals({ orgId, managedBy: 'seeder' }), // own rows only
hashOf: (d) => stableHash(d.spec),
appliedHashOf: (a) => a.appliedHash,
create: (d) => createPortal(d),
update: (d, a) => updatePortal(a.physicalId, d),
retire: (a) => archivePortal(a.physicalId),
};
const result = await reconcile(port);
// { created, updated, retired, unchanged, declined, entries }planReconcile(port)returns the diff with no side effects (dry runs, drift views).reconcile(port)plans, then applies; any port error propagates.- Only reconcile rows you own: scope
readActualso the retire pass never touches rows owned elsewhere. create,updateandretiremay returnReconcileOutcome.Declinedwhen the target is owned by someone else. It is counted inresult.declined, is not an error, and returning nothing still means applied.- A plan is
pending, not a promise:result.entriesis the plan as computed, and a declined create still readsCreatethere.
License
Apache-2.0 © Xema — xema.dev
