Skip to content

Instantly share code, notes, and snippets.

@ztrehagem
Created September 14, 2023 05:45
Show Gist options
  • Save ztrehagem/2fd3aefba449f16eec448290d40ac560 to your computer and use it in GitHub Desktop.
Save ztrehagem/2fd3aefba449f16eec448290d40ac560 to your computer and use it in GitHub Desktop.
declare const historyIdBrand: unique symbol;
let historyId = 0;
type HistoryId = number & { [historyIdBrand]: never };
interface HistoryItem<T, A> {
readonly id: HistoryId;
readonly action: A;
readonly state: T;
}
type Mutate<T, A> = (state: T, action: A) => T;
class RevokableHistory<T, A> {
readonly #initialState: T;
readonly #history: HistoryItem<T, A>[] = []
readonly #mutate: Mutate<T, A>;
constructor(initialState: T, mutate: Mutate<T, A>) {
this.#initialState = initialState;
this.#mutate = mutate;
}
getLastState(): T {
return this.#history.at(-1)?.state ?? this.#initialState;
}
push(action: A): HistoryItem<T, A> {
const item: HistoryItem<T, A> = {
id: historyId++ as HistoryId,
action,
state: this.#mutate(this.getLastState(), action),
}
this.#history.push(item);
return item;
}
revoke(id: HistoryId): void {
const revokedHistoryIndex = this.#history.findIndex((item) => item.id == id);
if (revokedHistoryIndex == -1) {
throw new TypeError();
}
const [_revoked, ...histories] = this.#history.splice(revokedHistoryIndex, Infinity);
for (const history of histories) {
this.push(history.action);
}
}
}
(() => {
const h = new RevokableHistory("", (leading, trailing: string) => leading + trailing);
console.log(h.getLastState()); //=> ""
h.push("1");
const { id } = h.push("2");
h.push("3");
console.log(h.getLastState()); //=> "123"
h.revoke(id);
console.log(h.getLastState()); //=> "13"
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment