Use lenses
Every migration is a lens. The lens API gives you direct access to the bidirectional transform: get lifts data forward, put projects new data back to the old shape, complement records what get discarded so put can restore it.
Prerequisites
A migration (Build a migration) or a hand-written lens via the lens DSL.
The task
A CompiledMigration is itself a lens; reach for LensHandle only when you want a free-standing protolens chain.
const { view, complement } = mig.get(oldRecord);
const editedView = { ...view, age: view.age + 1 };
const { data: updatedOld } = mig.put(editedView, complement);
mig.get returns the forward view together with the complement (the data discarded by get); mig.put consumes them and returns a LiftResult { data, ... } reconstructed with the edit applied. The round-trip laws guarantee this is well-defined.
To compose two compiled migrations sequentially:
const composed = p.compose(mig_ab, mig_bc);
To compose two free-standing protolens chains:
const composedChain = p.composeLenses(chainAB, chainBC);
Both are methods on Panproto; composition fails (throws) if the intermediate schemas do not chain.
Verification
const result = lens.checkLaws(instanceBytes);
console.log(result.holds, result.violation);
// For individual laws:
const getput = lens.checkGetPut(instanceBytes);
const putget = lens.checkPutGet(instanceBytes);
LensHandle.checkLaws(instance) returns a LawCheckResult { holds, violation } covering GetPut and PutGet together. checkGetPut and checkPutGet test each law individually; the Rust property tests in panproto-lens cover PutPut as well, exercised continuously in CI.
Common mistakes
- Calling
putwith a complement from a different source.Complement::composewill refuse withComplementFingerprintMismatch. Recompute the complement from the current source rather than reusing one. - Reading
getand then mutating the source before callingput. The complement is computed against the source as it was atgettime; if you mutate the source, the complement is stale. - Composing lenses whose intermediate schemas are isomorphic but not equal. The structural-equality check on
protolens_composablewill reject; rebuild one of the lenses against the other’s schema.