Lab
Facade
type Order = { id: string; customer: string; balance: number; status: string };
// What a pickup did, step by step, and why it stopped if it did.
type Pickup = { paid: boolean; released: boolean; texted: boolean; note: string };
const refused = (note: string): Pickup => ({ paid: false, released: false, texted: false, note });
const receipt = (order: Order) => `${order.id} collected, paid $${order.balance}`;
// Runs one step of a pickup: '' when it worked, otherwise why it failed.
function failed(step: () => void): string {
try {
step();
return '';
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
// ---- The shop's services. Checker plumbing: the checker opens a desk with
// the case's faults. You don't need to edit this part.
type Faults = { stuck?: boolean; declines?: boolean; textFailures?: number };
class OrderFile {
orders = new Map<string, Order>();
stuck = false; // the garment cannot be found on the rack
find(id: string): Order | undefined {
return this.orders.get(id);
}
release(id: string): void {
const order = this.orders.get(id);
if (!order || this.stuck) throw new Error('not on the rack');
order.status = 'collected';
}
}
class Till {
taken = 0;
declines = false;
charge(amount: number): void {
if (this.declines) throw new Error('card declined');
this.taken += amount;
}
}
class Texts {
sent: string[] = [];
failures = 0; // how many sends fail before the gateway recovers
send(customer: string, message: string): void {
if (this.failures > 0) {
this.failures--;
throw new Error('text gateway down');
}
this.sent.push(`${customer}: ${message}`);
}
}
// The pickup desk holds the shop's services.
class PickupDesk {
file = new OrderFile();
till = new Till();
texts = new Texts();
// The pickup operation every caller should share. Not written yet.
collect(orderId: string): Pickup {
return refused(`not written yet: ${orderId}`);
}
}
// Checker plumbing: a desk with the day's three orders and the case's faults.
function openDesk(faults: Faults = {}): PickupDesk {
const desk = new PickupDesk();
desk.file.orders.set('A-1', { id: 'A-1', customer: 'Mina', balance: 40, status: 'ready' });
desk.file.orders.set('A-2', { id: 'A-2', customer: 'Omar', balance: 25, status: 'in progress' });
desk.file.orders.set('A-3', { id: 'A-3', customer: 'Lena', balance: 30, status: 'collected' });
desk.file.stuck = faults.stuck ?? false;
desk.till.declines = faults.declines ?? false;
desk.texts.failures = faults.textFailures ?? 0;
return desk;
}
// Checker plumbing: the money the shop has taken.
function taken(desk: PickupDesk): number {
return desk.till.taken;
}
// Three callers, each running the pickup recipe itself.
// The counter tablet.
function counterPickup(desk: PickupDesk, orderId: string): Pickup {
const order = desk.file.find(orderId);
if (!order) return refused('no such order');
if (order.status !== 'ready') return refused(`order is ${order.status}`);
const problem = failed(() => {
desk.till.charge(order.balance);
desk.file.release(order.id);
});
if (problem) return refused(problem);
const unsent = failed(() => desk.texts.send(order.customer, receipt(order)));
return { paid: true, released: true, texted: !unsent, note: unsent };
}
// The locker kiosk: pay, and the locker opens.
function kioskPickup(desk: PickupDesk, orderId: string): Pickup {
const order = desk.file.find(orderId);
if (!order) return refused('no such order');
const declined = failed(() => desk.till.charge(order.balance));
if (declined) return refused(declined);
const lost = failed(() => desk.file.release(order.id));
if (lost) return { paid: true, released: false, texted: false, note: lost };
const unsent = failed(() => desk.texts.send(order.customer, receipt(order)));
return { paid: true, released: true, texted: !unsent, note: unsent };
}
// The courier app: a driver collects for the customer.
function courierPickup(desk: PickupDesk, orderId: string): Pickup {
const order = desk.file.find(orderId);
if (!order) return refused('no such order');
if (order.status !== 'ready') return refused(`order is ${order.status}`);
const declined = failed(() => desk.till.charge(order.balance));
if (declined) return refused(declined);
const problem = failed(() => {
desk.file.release(order.id);
desk.texts.send(order.customer, receipt(order));
});
if (problem) return { paid: true, released: false, texted: false, note: problem };
return { paid: true, released: true, texted: true, note: '' };
}