src/cart-store.tsTypeScript
@@ -0,0 +1,82 @@
1+
export interface Cart {2+
id: string;3+
revision: number;4+
lines: Array<{ itemId: string; quantity: number }>;5+
}6+
7+
export interface CartSnapshotMessage {8+
sourceTabId: string;9+
cart: Cart;10+
}11+
12+
export interface CartClient {13+
replaceCart(request: {14+
requestId: string;15+
cart: Cart;16+
}): Promise<{ cart: Cart }>;17+
}18+
19+
function cloneCart(cart: Cart): Cart {20+
return { ...cart, lines: cart.lines.map((line) => ({ ...line })) };21+
}22+
23+
type Listener = () => void;24+
25+
export class CartStore {26+
#cart: Cart;27+
#pending = false;28+
#listeners = new Set<Listener>();29+
30+
constructor(initialCart: Cart) {31+
this.#cart = cloneCart(initialCart);32+
}33+
34+
subscribe(listener: Listener) {35+
this.#listeners.add(listener);36+
return () => {37+
this.#listeners.delete(listener);38+
};39+
}40+
41+
snapshot() {42+
return {43+
cart: cloneCart(this.#cart),44+
pending: this.#pending,45+
};46+
}47+
48+
beginMutation() {49+
this.#pending = true;50+
this.#emit();51+
}52+
53+
finishMutation() {54+
this.#pending = false;55+
this.#emit();56+
}57+
58+
setQuantity(itemId: string, quantity: number) {59+
const nextLines = this.#cart.lines.filter((line) => line.itemId !== itemId);60+
if (quantity > 0) {61+
nextLines.push({ itemId, quantity });62+
}63+
64+
this.#cart = {65+
...this.#cart,66+
revision: this.#cart.revision + 1,67+
lines: nextLines,68+
};69+
this.#emit();70+
}71+
72+
replace(cart: Cart) {73+
this.#cart = cloneCart(cart);74+
this.#emit();75+
}76+
77+
#emit() {78+
for (const listener of this.#listeners) {79+
listener();80+
}81+
}82+
}