Files
WRNexusJS/packages/csr/src/outputs.ts
T
ClintchizandClaude Opus 5 8393a953b7 fix(db): drop dead quote tracking in hasExecutableSql, clear lint
hasExecutableSql returns as soon as it meets a quote character, so the quote
variable was assigned and never read: the `if (quote)` branch could never run.
eslint reported it as a useless assignment and the error blocks the release
gate on main. Removing the variable and the unreachable branch keeps behaviour
identical, since encountering a quote already means the SQL is executable.

Also drops an eslint-disable directive in csr's output error reporter that
suppressed nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 16:57:34 +05:30

75 lines
2.5 KiB
TypeScript

export type OutputHandler<T = unknown> = (payload: T) => void | Promise<void>;
export interface OutputHost extends HTMLElement {
__wrnexusOutputHandlers?: Map<string, Set<OutputHandler>>;
}
export function registerOutputHandler<T>(
host: OutputHost,
name: string,
handler: OutputHandler<T>,
): () => void {
const registry = (host.__wrnexusOutputHandlers ??= new Map());
const handlers = registry.get(name) ?? new Set();
handlers.add(handler as OutputHandler);
registry.set(name, handlers);
return () => {
handlers.delete(handler as OutputHandler);
if (!handlers.size) registry.delete(name);
};
}
/**
* Surface a throwing output handler.
*
* Callers of `invokeOutput` do not await it — an output call is fire-and-forget
* from the component's point of view. A handler that throws therefore becomes an
* unhandled rejection that never reaches the console, and the symptom is a
* control that silently does nothing. Reporting here turns that into a named
* diagnostic instead of a dead button.
*/
function reportOutputError(host: OutputHost, name: string, error: unknown): void {
const code = "WRN-DEV-OUTPUT-HANDLER-ERROR";
const message = `The handler bound to output '${name}' threw. The control will appear to do nothing.`;
console.error(`[${code}] ${message}`, error);
try {
window.dispatchEvent(
new CustomEvent("wrnexus:diagnostic", {
detail: {
code,
message,
hydrationId: host.getAttribute ? host.getAttribute("data-wrn-hydration") : null,
detail: { output: name, error: String(error) },
},
}),
);
} catch {
// CustomEvent can be unavailable in minimal DOM test environments.
}
}
export async function invokeOutput<T>(host: OutputHost, name: string, payload?: T): Promise<void> {
const handlers = host.__wrnexusOutputHandlers?.get(name);
if (handlers?.size) {
for (const handler of handlers) {
try {
await handler(payload);
} catch (error) {
reportOutputError(host, name, error);
}
}
return;
}
// Compatibility path for legacy listeners outside a hydrated WRN parent.
host.dispatchEvent(new CustomEvent(name, { detail: payload }));
host.dispatchEvent(new CustomEvent(`wrnexus:${name}`, { detail: payload }));
}
export function createOutputProxy<T extends Record<string, (...args: any[]) => void>>(
host: OutputHost,
): T {
return new Proxy({} as T, {
get: (_target, property) => (payload?: unknown) =>
invokeOutput(host, String(property), payload),
});
}