fix(chord): allow promise probes on settled drafts

This commit is contained in:
Mario Zechner 2026-09-24 19:33:02 +02:00
parent d5629e2048
commit 6966636dbe
2 changed files with 40 additions and 5 deletions

View file

@ -388,6 +388,10 @@ const sharedObjectHandler: ProxyHandler<object> = {
throw new TypeError("Defining overlay properties is not supported");
},
get(target, property) {
if (property === "then") {
const node = (target as Record<PropertyKey, unknown>)[NODE] as OverlayNode | undefined;
if (node === undefined || isSettledContext(node.context)) return undefined;
}
const node = nodeForTarget(target);
if (property === NODE) return node;
return getProperty(node, property);
@ -499,15 +503,17 @@ function nodeForTarget(target: object): OverlayNode {
return node;
}
function assertReadable(context: OverlayContext): void {
if (
function isSettledContext(context: OverlayContext): boolean {
return (
context.overlayReleased ||
context.status.value === "consumed" ||
context.status.value === "aborted" ||
context.status.value === "stale"
) {
throw new TypeError("Cannot use a settled overlay");
}
);
}
function assertReadable(context: OverlayContext): void {
if (isSettledContext(context)) throw new TypeError("Cannot use a settled overlay");
}
function assertWritable(context: OverlayContext): void {

View file

@ -86,6 +86,35 @@ describe("delta tracker transactional overlay lifecycle", () => {
tracker.adopt(prepared);
});
it("allows Promise resolution to inspect a settled draft without reporting a false failure", async () => {
const initial = JSON.parse('{"then":"document-value","value":1}') as { then: string; value: number };
const tracker = track(initial);
const change = tracker.beginChange();
const draft = change.state;
expect(draft.then).toBe("document-value");
draft.value = 2;
const prepared = change.prepare();
const resolved = await Promise.resolve(draft);
expect(resolved).toBe(draft);
expect(resolved.then).toBeUndefined();
expect(() => resolved.value).toThrow(/settled/);
tracker.adopt(prepared);
expect(tracker.value.then).toBe("document-value");
expect(tracker.value.value).toBe(2);
const largeTracker = track({ rows: Array.from({ length: 4_100 }, (_, value) => ({ value })) });
const largeChange = largeTracker.beginChange();
const largeDraft = largeChange.state;
for (const row of largeDraft.rows) expect(row.value).toBeGreaterThanOrEqual(0);
largeDraft.rows[0]!.value = -1;
const largePrepared = largeChange.prepare();
expect(await Promise.resolve(largeDraft)).toBe(largeDraft);
expect(() => largeDraft.rows).toThrow(/settled/);
largeTracker.adopt(largePrepared);
});
it("aborts idempotently and revokes held descendants", () => {
const tracker = track({ child: { value: 1 } });
const change = tracker.beginChange();