Build a settings patch boundary without copying the event gateway. Settings has readonly enabled:boolean and minutes:number. Derive Patch from a mapped type so each key is paired with its own value. Decode unknown patches explicitly: enabled requires boolean; minutes requires a finite integer from 0 through 180. Reject unknown keys and do not coerce text.
Apply a decoded patch by constructing a new Settings record. The original must stay unchanged. Extra input properties should not enter the resulting settings. Preserve zero and false as meaningful values rather than replacing them with truthy defaults.
Use about twenty minutes before looking at the worked answer. Test both valid branches, missing values, the two numeric bounds, fractional/text values and unknown keys. This is a fresh API with no event subscriptions, transport or brand requirement. Explain which relationships the mapped union checks and which rules the decoder implements.
Predict, check, and run
Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.
type Settings = {
readonly enabled: boolean;
readonly minutes: number;
};
type Patch = {
[K in keyof Settings]: {
key: K;
value: Settings[K];
};
}[keyof Settings];
const patch: Patch = { key: "enabled", value: false };
console.log(patch.key, patch.value);Enable JavaScript to edit and type-check this example.
Check your prediction
enabled false Build it yourself
On Your Own: Settings Patches: practise
Implement PatchOf<S>, decodePatch and apply. From {enabled:true,minutes:25}, apply enabled:false and minutes:0 without changing the source. Print false 0 and true 25. Assert text minutes, out-of-range counts, missing values and unknown keys fail; print invalid patches rejected.
// Start from the stated contract. Add a valid call and an edge case.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
Derive the correlated union, but validate each supported branch explicitly at runtime.
Hint 2: a first step
Narrow patch.key before assigning a value in a new settings object.
Show one solution
type Settings = {
readonly enabled: boolean;
readonly minutes: number;
};
type PatchOf<S> = {
[K in keyof S]: {
key: K;
value: S[K];
};
}[keyof S];
type Patch = PatchOf<Settings>;
type Result<T> =
| {
ok: true;
value: T;
}
| {
ok: false;
message: string;
};
function decodePatch(raw: unknown): Result<Patch> {
if (
typeof raw !== "object" ||
raw === null ||
!("key" in raw) ||
!("value" in raw)
) {
return { ok: false, message: "Expected patch" };
}
if (raw.key === "enabled" && typeof raw.value === "boolean") {
return { ok: true, value: { key: "enabled", value: raw.value } };
}
if (
raw.key === "minutes" &&
typeof raw.value === "number" &&
Number.isFinite(raw.value) &&
Number.isInteger(raw.value) &&
raw.value >= 0 &&
raw.value <= 180
) {
return { ok: true, value: { key: "minutes", value: raw.value } };
}
return { ok: false, message: "Invalid patch" };
}
function apply(settings: Settings, patch: Patch): Settings {
if (patch.key === "enabled") {
return { enabled: patch.value, minutes: settings.minutes };
}
return { enabled: settings.enabled, minutes: patch.value };
}
const source: Settings = { enabled: true, minutes: 25 };
const enabled = decodePatch({ key: "enabled", value: false, secret: true });
const minutes = decodePatch({ key: "minutes", value: 0 });
if (!enabled.ok || !minutes.ok) {
throw new Error("Valid patch rejected");
}
const updated = apply(apply(source, enabled.value), minutes.value);
if (
updated === source ||
"secret" in updated ||
source.enabled !== true ||
source.minutes !== 25
) {
throw new Error("Ownership failed");
}
console.log(updated.enabled, updated.minutes);
console.log(source.enabled, source.minutes);
for (const raw of [
null,
{ key: "enabled" },
{ key: "other", value: true },
{ key: "enabled", value: "false" },
...[-1, 181, 1.5, "0", Infinity, NaN].map(value => ({
key: "minutes",
value
}))
]) {
if (decodePatch(raw).ok) {
throw new Error("Invalid patch accepted");
}
}
if (!decodePatch({ key: "minutes", value: 180 }).ok) {
throw new Error("Upper bound rejected");
}
console.log("invalid patches rejected");Enable JavaScript to edit and type-check this example.
false 0
true 25
invalid patches rejectedTrace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.
Change it and test it
Add a public negative caller pairing enabled with a number. Remove a runtime bound and prove the corresponding test fails. Then explain whether adding a third setting should require an explicit decoder branch.
Completion is saved in this browser only. It does not measure mastery.