← Facade lesson
Lab

Facade

Sign in to run your code

Read the brief and the starter here. With a free account you can edit it, run the sample, step through it and check your work, and your progress is kept.

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: '' };
}