Files

34 lines
1.4 KiB
JavaScript

/** Execution completion, not HTTP disconnection, is the drain boundary. */
export const createInvocationRegistry = () => {
const entries = new Map();
return {
begin(id) {
if (!id || entries.has(id))
throw new Error("Invocation ID is missing or already used; invocations are never replayed implicitly");
// Completed identities remain until process retirement. A bounded process
// may reject new work; it must not evict and accidentally replay a call.
if (entries.size >= 100_000)
throw new Error("Invocation registry full; explicit runtime retirement required");
const entry = { state: "running", controller: new AbortController() };
entries.set(id, entry);
return {
signal: entry.controller.signal,
finish(failed = false) {
entry.state = failed ? "failed" : "completed";
},
};
},
status(id) {
return { invocationId: id, state: entries.get(id)?.state ?? "unknown" };
},
cancel(id) {
const entry = entries.get(id);
if (entry && ["running", "cancellation-requested"].includes(entry.state)) {
entry.state = "cancellation-requested";
entry.controller.abort();
}
return this.status(id);
},
};
};