I am implementing a TypeScript form whose valid values form a dependency chain:
model -> mode -> source image count -> aspect ratio -> resolution
The server returns a versioned capability graph. A refresh can remove the selected model, change the modes supported by that model, or alter the valid ratios and resolutions farther down the chain. I need the client to preserve still-valid choices while replacing invalid ones deterministically.
A simplified reconciler looks like this:
type State = {
model: string;
mode: string;
sourceCount: number;
ratio: string;
resolution: string;
};
function choose<T>(current: T, allowed: readonly T[]): T {
if (allowed.length === 0) throw new Error("no valid option");
return allowed.includes(current) ? current : allowed[0];
}
function reconcile(input: State, caps: Capabilities): State {
const model = choose(input.model, caps.models);
const mode = choose(input.mode, caps.modesFor(model));
const sourceCount = clamp(
input.sourceCount,
caps.minSources(model, mode),
caps.maxSources(model, mode),
);
const ratio = choose(input.ratio, caps.ratiosFor(model, mode, sourceCount));
const resolution = choose(
input.resolution,
caps.resolutionsFor(model, mode, sourceCount, ratio),
);
return { model, mode, sourceCount, ratio, resolution };
}
This works if the dependency direction is truly one-way. The difficulty is that some capability data can make a downstream constraint affect an upstream choice. For example, the requested source count may be valid for only one mode, or an empty resolution list may mean “the server chooses” rather than “no valid option.” A single ordered pass can then discard a user choice that another valid combination could preserve.
The invariants I want are:
- The output is valid under one immutable capability version.
- Calling
reconcile()again with that output and the same capabilities makes no change. - The algorithm preserves as many current choices as possible without depending on object iteration order.
- Submission sends both the reconciled state and capability version, and the server validates them again.
Would you model this as a strictly directed dependency graph and reject capability data containing cycles, repeatedly reconcile until reaching a fixed point, or enumerate valid combinations and choose the closest one with an explicit cost function?
I prefer the last option when the state space is small because “closest” can be defined and tested, but I would like to know if there is a simpler invariant-preserving approach.