33 lines
1.3 KiB
TypeScript
33 lines
1.3 KiB
TypeScript
/** Execution completion, not HTTP disconnection, is the drain boundary. */
|
|
export const createInvocationRegistry = () => {
|
|
const entries = new Map<string, { state: string; controller: AbortController }>();
|
|
return {
|
|
begin(id: string) {
|
|
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: string) {
|
|
return { invocationId: id, state: entries.get(id)?.state ?? "unknown" };
|
|
},
|
|
cancel(id: string) {
|
|
const entry = entries.get(id);
|
|
if (entry && ["running", "cancellation-requested"].includes(entry.state)) {
|
|
entry.state = "cancellation-requested";
|
|
entry.controller.abort();
|
|
}
|
|
return this.status(id);
|
|
},
|
|
};
|
|
};
|