refactor: une seule fonction decide ce qu-une mutation peut rendre

Trois copies d-un raisonnement sur une valeur de retour, c-est trois occasions
de se tromper et c-est ce qui a produit le defaut. `handBack` porte la regle
une fois : la collection devient la vue, un scalaire passe, le reste refuse.
This commit is contained in:
Sylvain Duchesne
2026-09-24 19:04:04 +02:00
parent 98e46effc2
commit 84699c42b5
@@ -243,6 +243,19 @@ export function makeReadFilteredView<S extends object>(set: S, caps: CapRegistry
return self.view;
};
/**
* What a MUTATION may hand back - the same question this file asks of any other member,
* asked of a RESULT rather than of a property. Written once and called from each of
* them, because the defect it answers was three copies of a reasoned-about return value:
* the collection becomes the view, a scalar passes, and anything else refuses rather
* than being guessed at.
*/
const handBack = (out: unknown, target: object, prop: string | symbol): unknown => {
if (out === target) return viewOf(prop);
if (cannotReachAnItem(out)) return out;
throw refusalOf(prop, "the collection view");
};
/** What the view answers for one member. The `get` trap and the descriptor trap BOTH
* call this, so neither can answer what the other refuses. */
const read = (target: object, prop: string | symbol): unknown => {
@@ -334,12 +347,9 @@ export function makeReadFilteredView<S extends object>(set: S, caps: CapRegistry
if (prop === "add") {
const fn = Reflect.get(target, prop, target);
if (typeof fn !== "function") return fn;
return (...args: unknown[]): unknown => {
const out = (fn as (...a: unknown[]) => unknown).apply(target, args);
if (out === target) return viewOf(prop); // chaining lands on the view
if (cannotReachAnItem(out)) return out;
throw refusalOf(prop, "the collection view");
};
// `add` chains, so its result is the collection and must become the view.
return (...args: unknown[]): unknown =>
handBack((fn as (...a: unknown[]) => unknown).apply(target, args), target, prop);
}
if (prop === "delete") {
const fn = Reflect.get(target, prop, target);
@@ -347,7 +357,7 @@ export function makeReadFilteredView<S extends object>(set: S, caps: CapRegistry
// The same question `has` already refuses to answer, with a worse consequence. The
// two disagreed: `has(item)` said no while `delete(item)` removed it.
return (item: unknown): unknown =>
keep(item) ? (fn as (i: unknown) => unknown).call(target, item) : false;
keep(item) ? handBack((fn as (i: unknown) => unknown).call(target, item), target, prop) : false;
}
if (prop === "clear") {
const del = Reflect.get(target, "delete", target);