fix(core): mint guest-local runtime-data ids in a separate space from host ids

This commit is contained in:
Vance Ingalls
2026-08-30 12:16:17 -07:00
parent 7b1e4ff6a9
commit 3beb0f63b8
3 changed files with 25 additions and 2 deletions
@@ -88,4 +88,24 @@ describe("runtime data registry", () => {
expect(applied).toHaveBeenCalledTimes(1);
});
it("never reports a composition-side delivery under a pending host request id", async () => {
const applied = vi.fn();
setRuntimeDataAppliedReporter(applied);
const resolvers: Array<() => void> = [];
registerRuntimeDataHandler(
"captions",
() => new Promise<void>((resolve) => resolvers.push(resolve)),
);
// The host mints id 1 and waits on it; the composition then calls the two-argument
// public form, which mints an id of its own.
setRuntimeData("captions", "first", 1);
setRuntimeData("captions", "latest");
resolvers[1]?.();
await vi.waitFor(() => expect(applied).toHaveBeenCalledTimes(1));
const [, reportedId] = applied.mock.calls[0] ?? [];
expect(reportedId).not.toBe(1);
});
});
+4 -1
View File
@@ -28,7 +28,10 @@ function nextGeneration(channel: string): number {
function resolveRequestId(requestId: number | undefined): number {
if (typeof requestId === "number" && Number.isSafeInteger(requestId) && requestId > 0)
return requestId;
localRequestId += 1;
// Guest-local ids count down so they can never collide with a host id, which is
// required above to be positive. A shared id space lets a composition-side call
// report `applied` under a host request's id while that host payload is still in flight.
localRequestId -= 1;
return localRequestId;
}
+1 -1
View File
@@ -35,7 +35,7 @@ declare global {
channel: string,
handler: (payload: unknown) => void,
) => () => void;
setRuntimeData?: (channel: string, payload: unknown) => void;
setRuntimeData?: (channel: string, payload: unknown, requestId?: number) => void;
clearRuntimeData?: (channel: string) => void;
[key: string]: unknown;
};