| 1 | /** Tiny hot-path bridge. The full recorder is loaded only by test builds. */ |
| 2 | export type FrontendDiagnosticFields = Record<string, unknown>; |
| 3 | type DiagnosticSink = (source: string, type: string, fields: FrontendDiagnosticFields) => void; |
| 4 | type DiagnosticStartHook = () => void; |
| 5 | |
| 6 | let sink: DiagnosticSink | undefined; |
| 7 | const startHooks = new Set<DiagnosticStartHook>(); |
| 8 | |
| 9 | export function setFrontendDiagnosticSink(next: DiagnosticSink): void { |
| 10 | sink = next; |
| 11 | } |
| 12 | |
| 13 | export function recordFrontendDiagnostic(source: string, type: string, fields: FrontendDiagnosticFields = {}): void { |
| 14 | sink?.(source, type, fields); |
| 15 | } |
| 16 | |
| 17 | /** Register a lightweight producer that should publish its initial snapshot |
| 18 | * whenever the opt-in recorder starts. The bridge stays inert when no recorder |
| 19 | * is loaded, so stable builds pay only for this small Set and effect cleanup. */ |
| 20 | export function registerFrontendDiagnosticStartHook(hook: DiagnosticStartHook): () => void { |
| 21 | startHooks.add(hook); |
| 22 | return () => startHooks.delete(hook); |
| 23 | } |
| 24 | |
| 25 | export function notifyFrontendDiagnosticStart(): void { |
| 26 | for (const hook of startHooks) { |
| 27 | try { |
| 28 | hook(); |
| 29 | } catch { |
| 30 | // A diagnostic producer must never prevent the recorder from starting. |
| 31 | } |
| 32 | } |
| 33 | } |
| 34 |