Lab
Memento
type Preferences = { branch: string; format: string; alerts: string[] };
// The memento: an empty token. Only the record that made it can read it.
type Checkpoint = Readonly<object>;
const copy = (p: Preferences): Preferences => ({ ...p, alerts: [...p.alerts] });
// The originator: it owns the preferences and decides what a checkpoint holds.
class MemberRecord {
prefs: Preferences = {
branch: 'East Branch',
format: 'print',
alerts: ['history', 'maps'],
};
loans = 2; // live: never captured, never restored
private saved = new WeakMap<Checkpoint, Preferences>();
capture(): Checkpoint {
const token: Checkpoint = Object.freeze({});
this.saved.set(token, copy(this.prefs));
return token;
}
restore(token: Checkpoint): void {
const saved = this.saved.get(token);
if (!saved) throw new Error('foreign checkpoint');
this.prefs = copy(saved);
}
edit(branch: string, format: string): void {
this.prefs.branch = branch;
this.prefs.format = format;
}
follow(subject: string): void {
this.prefs.alerts.push(subject);
}
unfollow(subject: string): void {
const at = this.prefs.alerts.indexOf(subject);
if (at >= 0) this.prefs.alerts.splice(at, 1);
}
}
// The caretaker: it keeps labelled tokens and never looks inside one.
class CheckpointHistory {
private entries: { label: string; token: Checkpoint }[] = [];
private member: MemberRecord;
constructor(member: MemberRecord) {
this.member = member;
}
save(label: string): void {
this.entries.push({ label, token: this.member.capture() });
}
restore(label: string): void {
const entry = this.entries.find((e) => e.label === label);
if (!entry) throw new Error('no checkpoint');
this.member.restore(entry.token);
}
}
function summary(member: MemberRecord): string {
const { branch, format, alerts } = member.prefs;
const subjects = JSON.stringify(alerts);
return `${branch}, ${format}, ${subjects}, loans ${member.loans}`;
}
const member = new MemberRecord();
const checkpoints = new CheckpointHistory(member);
checkpoints.save('My usual settings');
// The first experiment, then back. A book is checked out meanwhile.
member.edit('Riverside Branch', 'digital');
member.unfollow('history');
member.loans = 3;
checkpoints.restore('My usual settings');
const first = summary(member);
// The second experiment, then back to the same checkpoint.
member.edit('Harbor Branch', 'large print');
member.unfollow('history');
member.follow('poetry');
checkpoints.restore('My usual settings');
const second = summary(member);
console.log(`first: ${first}`);
console.log(`second: ${second}`);
TODO
Visual to come