Lab
Proxy
type GaugeSnapshot = { id: string; station: string; reading: string };
type ReadRequest = { operator: string; id: string };
type ReadResult =
| { ok: true; snapshot: GaugeSnapshot }
| { ok: false; error: string };
// The contract the console is given. The collector and the proxy both keep it.
interface StationReader {
read(request: ReadRequest): ReadResult;
}
class AccessRules {
private grants = new Map<string, Set<string>>();
allow(operator: string, id: string) {
this.grants.set(operator, (this.grants.get(operator) ?? new Set()).add(id));
}
revoke(operator: string, id: string) {
this.grants.get(operator)?.delete(id);
}
canRead(operator: string, id: string): boolean {
return this.grants.get(operator)?.has(id) ?? false;
}
}
// The resource: historical snapshots. Each call is one collector read.
class GaugeCollector implements StationReader {
reads = 0;
constructor(private snapshots: GaugeSnapshot[]) {}
read(request: ReadRequest): ReadResult {
this.reads++;
const snapshot = this.snapshots.find((s) => s.id === request.id);
if (!snapshot) return { ok: false, error: 'not found' };
return { ok: true, snapshot };
}
}
// The stand-in: the same contract, a grant check, then a cache.
class GaugeProxy implements StationReader {
private cache = new Map<string, GaugeSnapshot>();
constructor(private origin: StationReader, private rules: AccessRules) {}
read(request: ReadRequest): ReadResult {
if (!this.rules.canRead(request.operator, request.id)) {
return { ok: false, error: 'forbidden' };
}
const cached = this.cache.get(request.id);
if (cached) return { ok: true, snapshot: cached };
const result = this.origin.read(request);
if (result.ok) this.cache.set(request.id, result.snapshot);
return result;
}
}
const collector = new GaugeCollector([
{ id: 'north-fork', station: 'North Fork', reading: '2.4 m, rising' },
{ id: 'harbor', station: 'Harbor', reading: '1.8 m, steady' },
]);
const rules = new AccessRules();
rules.allow('mira', 'north-fork');
rules.allow('mira', 'harbor');
rules.allow('lee', 'harbor');
// The console only ever sees a StationReader.
const reader: StationReader = new GaugeProxy(collector, rules);
const first = reader.read({ operator: 'mira', id: 'north-fork' });
const warm = reader.read({ operator: 'mira', id: 'north-fork' });
const lee = reader.read({ operator: 'lee', id: 'north-fork' });
rules.revoke('mira', 'north-fork');
const revoked = reader.read({ operator: 'mira', id: 'north-fork' });
const reads = collector.reads;
const describe = (s: GaugeSnapshot) => `${s.station} ${s.reading}`;
const show = (name: string, result: ReadResult) => {
const shown = result.ok ? describe(result.snapshot) : result.error;
console.log(`${name}: ${shown}`);
};
show('first', first);
show('warm', warm);
show('lee', lee);
show('revoked', revoked);
console.log(`collector reads: ${reads}`);
TODO
Visual to come