Lab
Adapter
// The dashboard's contract: a permit, null for "no permit",
// or PermitUnavailable when the source cannot answer.
type Permit = { plate: string; zone: string; expiresAt: string };
class PermitUnavailable extends Error {}
interface PermitLookup {
lookupPermit(plate: string, zone: string): Permit | null;
}
// Two existing APIs, as they already are.
type LegacyReply = {
state: string;
registration: string;
curb: string;
end: string;
};
interface LegacyMeter {
lookup(plate: string, curb: string): LegacyReply;
}
type PartnerReply = {
status: number;
vehicle: string;
district: string;
paidThrough: string;
};
interface PartnerMeter {
fetchPermit(plate: string, zone: string): PartnerReply;
}
// Each adapter holds one source and answers the dashboard's question with it.
class LegacyPermitAdapter implements PermitLookup {
constructor(private source: LegacyMeter) {}
lookupPermit(plate: string, zone: string): Permit | null {
const reply = this.source.lookup(plate, zone);
if (reply.state === 'none') return null;
if (reply.state !== 'active') throw new PermitUnavailable(reply.state);
const { registration, curb, end } = reply;
return { plate: registration, zone: curb, expiresAt: end };
}
}
class PartnerPermitAdapter implements PermitLookup {
constructor(private source: PartnerMeter) {}
lookupPermit(plate: string, zone: string): Permit | null {
const reply = this.source.fetchPermit(plate, zone);
if (reply.status === 404) return null;
if (reply.status !== 200) {
throw new PermitUnavailable(`status ${reply.status}`);
}
const { vehicle, district, paidThrough } = reply;
return { plate: vehicle, zone: district, expiresAt: paidThrough };
}
}
// The dashboard knows PermitLookup and no provider.
function showPermit(reader: PermitLookup, plate: string, zone: string): string {
try {
const permit = reader.lookupPermit(plate, zone);
if (!permit) return `${plate} in ${zone}: no active permit`;
return `${permit.plate} in ${permit.zone}: permit through ${permit.expiresAt}`;
} catch {
return `${plate} in ${zone}: check again, source unavailable`;
}
}
// What each provider says about zone C7 today, in memory.
const legacy: LegacyMeter = {
lookup: (plate) =>
plate === 'KLM-204'
? { state: 'active', registration: plate, curb: 'C7', end: '18:30' }
: { state: 'none', registration: '', curb: '', end: '' },
};
const blank = { vehicle: '', district: '', paidThrough: '' };
const partner: PartnerMeter = {
fetchPermit: (plate) =>
plate === 'KLM-204'
? { status: 200, vehicle: plate, district: 'C7', paidThrough: '18:30' }
: plate === 'RTE-118'
? { status: 429, ...blank } // too many requests: try later
: { status: 404, ...blank },
};
const partnerLookup = new PartnerPermitAdapter(partner);
const fromLegacy = showPermit(new LegacyPermitAdapter(legacy), 'KLM-204', 'C7');
const fromPartner = showPermit(partnerLookup, 'KLM-204', 'C7');
const noPermit = showPermit(partnerLookup, 'BXT-551', 'C7');
const rateLimited = showPermit(partnerLookup, 'RTE-118', 'C7');
console.log(`[legacy] ${fromLegacy}`);
console.log(`[partner] ${fromPartner}`);
console.log(`[partner] ${noPermit}`);
console.log(`[partner] ${rateLimited}`);
Legacy meterstate · registration · curb · end
Payment partnerstatus · vehicle · district · paid through
Kerbside dashboard · PermitLookup
—in zone—?
Source-call statement · observed
Waiting for a recorded source callSource reply · observedExisting API
Not observed The source’s structured reply appears when it is recorded.
adapter
boundary
boundary
Caller contract · observedPermitLookup
Awaiting the caller’s value A source reply alone does not establish the adapted outcome.
Dashboard line · actual runtime observation
No dashboard answer observed yet.
PermitNo permitCheck again
This source changes the scene’s named bindings. Use Recorded objects to inspect its data.