import { Value } from 'preserves'; type Assertion = Value; type ActorId = number; type ExitReason = null | { ok: true } | { ok: false, err: Error }; let nextActorId: ActorId = 0; type AssertionHandle = object; const assert = Symbol('assert'); const retract = Symbol('retract'); const message = Symbol('message'); type RestParameters any> = T extends (arg: any, ...args: infer P) => any ? P : never; type Event = | { type: typeof assert, args: RestParameters } | { type: typeof retract, args: RestParameters } | { type: typeof message, args: RestParameters } type Action = Event; class Peer { readonly actor: Actor; readonly target: Facet; constructor(actor: Actor, target: Facet) { this.actor = actor; this.target = target; } } interface Facet { [assert](turn: Turn, assertion: Assertion, handle: AssertionHandle): void; [retract](turn: Turn, handle: AssertionHandle): void; [message](turn: Turn, message: Assertion): void; } class Actor { readonly id: ActorId = nextActorId++; exitReason: ExitReason = null; scheduleTurn(target: Facet, turn: Turn) { queueMicrotask(() => { if (this.alive) { try { const event = turn.event; (target as any)[event.type](turn, ...event.args); // ^ This is safe. Try replacing it with the following to see: // // switch (event.type) { // case assert: target[event.type](turn, ...event.args); break; // case retract: target[event.type](turn, ...event.args); break; // case message: target[event.type](turn, ...event.args); break; // } turn.finish(); } catch (err) { this.terminateWith({ ok: false, err }); } } }); } get alive(): boolean { return this.exitReason === null; } stop() { this.terminateWith({ ok: true }); } terminateWith(reason: Exclude) { if (this.alive) { this.exitReason = reason; // TODO cleanup } } } class Turn { readonly recipient: Actor; readonly event: Event; readonly actions: Map> = new Map(); constructor(recipient: Actor, event: Event) { this.recipient = recipient; this.event = event; } enqueueAction(peer: Peer, action: Action) { let targetMap = this.actions.get(peer.actor); if (targetMap === void 0) { targetMap = new Map(); this.actions.set(peer.actor, targetMap); } let actions = targetMap.get(peer.target); if (actions === void 0) { actions = []; targetMap.set(peer.target, actions); } actions.push(action); } finish() { this.actions.forEach((targetMap, actor) => { targetMap.forEach((actions, target) => { actions.forEach(action => actor.scheduleTurn(target, new Turn(actor, action))); }); }); } }