feat(wasm): suppress stale client completions
req: client_local/011\nreq: client_local/012\nreq: operations/003
This commit is contained in:
+50
-1
@@ -19,6 +19,7 @@
|
||||
const atomStores = new WeakMap();
|
||||
const dragKeys = new WeakMap();
|
||||
const clientHandlers = new Map();
|
||||
const clientRuns = new WeakMap();
|
||||
|
||||
function roots() {
|
||||
const found = [];
|
||||
@@ -241,6 +242,11 @@
|
||||
async function runClient(el, event) {
|
||||
const name = el.getAttribute("data-hemx-client");
|
||||
const root = rootOf(el);
|
||||
const policy = el.getAttribute("data-hemx-client-policy") || "latest";
|
||||
const run = { root, generation: (clientRuns.get(root)?.generation || 0) + 1 };
|
||||
if (policy === "drop" && clientRuns.has(root)) return;
|
||||
clientRuns.set(root, run);
|
||||
const active = () => clientRuns.get(root) === run && root.isConnected;
|
||||
const handler = clientHandlers.get(name);
|
||||
showError(el, null);
|
||||
showPending(el, true);
|
||||
@@ -257,14 +263,17 @@
|
||||
root.getAttribute(STATE) || "",
|
||||
);
|
||||
if (!(wire instanceof Uint8Array)) throw new Error(`client-local hemx handler ${name} returned an invalid effect batch`);
|
||||
if (!active()) return;
|
||||
applyBatch(wire, root);
|
||||
} catch (error) {
|
||||
if (!active()) return;
|
||||
const fallback = el.hasAttribute("data-hemx-client-fallback");
|
||||
showError(el, error);
|
||||
emit(root, "hemx:client-error", { handler: name, message: String(error), fallback });
|
||||
if (fallback) await send(el, event.type, el);
|
||||
} finally {
|
||||
showPending(el, false);
|
||||
if (clientRuns.get(root) === run) clientRuns.delete(root);
|
||||
if (el.isConnected) showPending(el, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,6 +952,35 @@
|
||||
anchor.origin === location.origin && !anchor.download && anchor.target !== "_blank";
|
||||
}
|
||||
|
||||
function descendantRoots(node) {
|
||||
const roots = [];
|
||||
for (const child of node.children || []) {
|
||||
if (child.hasAttribute(ROOT)) roots.push(child);
|
||||
roots.push(...descendantRoots(child));
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
function stopDescendantPolling(node) {
|
||||
for (const child of node.children || []) {
|
||||
if (child.hasAttribute("data-hemx-every") || child.hasAttribute("data-hemx-interval")) {
|
||||
stopPolling(child);
|
||||
}
|
||||
stopDescendantPolling(child);
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupRemovedRoot(root) {
|
||||
clientRuns.delete(root);
|
||||
const source = sseSources.get(root);
|
||||
if (source) source.close();
|
||||
sseSources.delete(root);
|
||||
const observer = revealObservers.get(root);
|
||||
if (observer) observer.disconnect();
|
||||
revealObservers.delete(root);
|
||||
stopDescendantPolling(root);
|
||||
}
|
||||
|
||||
function start() {
|
||||
roots().forEach((root) => {
|
||||
try {
|
||||
@@ -961,6 +999,15 @@
|
||||
emit(root, "hemx:sse-error", String(error));
|
||||
}
|
||||
});
|
||||
new MutationObserver((records) => {
|
||||
records.forEach((record) => {
|
||||
record.removedNodes.forEach((node) => {
|
||||
if (!(node instanceof Element)) return;
|
||||
if (node.hasAttribute(ROOT)) cleanupRemovedRoot(node);
|
||||
descendantRoots(node).forEach(cleanupRemovedRoot);
|
||||
});
|
||||
});
|
||||
}).observe(document.documentElement, { childList: true, subtree: true });
|
||||
try {
|
||||
history.replaceState(history.state || { hemx: true }, "", location.href);
|
||||
} catch (error) {
|
||||
@@ -985,7 +1032,9 @@
|
||||
decodeAtomState,
|
||||
registerClientHandler(name, handler) {
|
||||
if (!name || typeof handler !== "function") throw new Error("client handler registration requires a name and function");
|
||||
const previous = clientHandlers.get(name);
|
||||
clientHandlers.set(name, handler);
|
||||
return previous;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user