Fans one event out to every sink. Each sink runs even if an earlier one
throws: a sink's SYNCHRONOUS throw is caught and isolated so the rest still
run, and every collected sync error is rethrown together — after all sinks
have run — as one AggregateError, so AuditLogger.record's existing
try/catch -> onSinkError still surfaces them (matching the containment a
single sink already gets).
A sink MAY return a promise. Every such promise is collected and awaited
via Promise.allSettled rather than Promise.all: allSettled always waits
for every pending sink to SETTLE (fulfilled or rejected) before this
function's own returned promise resolves, so one async sink rejecting
early never short-circuits — and therefore never hides — a later sink's
failure the way Promise.all's reject-on-first-rejection would. Every
pending promise still gets a handler attached (via allSettled itself, not
a hand-rolled per-promise .catch), so nothing is ever left as an unhandled
rejection — including the sync-throw-plus-pending-promises case: sync
errors collected during the loop are merged with any async rejections
once every pending promise has settled, and the combined AggregateError
(if any) is thrown from the returned promise. When no sink returned a
promise, the sync errors (if any) are thrown immediately and the function
returns void synchronously, keeping AuditLogger.record's plain sync
try/catch path for an all-sync sink set.
Fans one event out to every sink. Each sink runs even if an earlier one throws: a sink's SYNCHRONOUS throw is caught and isolated so the rest still run, and every collected sync error is rethrown together — after all sinks have run — as one AggregateError, so AuditLogger.record's existing try/catch -> onSinkError still surfaces them (matching the containment a single sink already gets).
A sink MAY return a promise. Every such promise is collected and awaited via Promise.allSettled rather than Promise.all: allSettled always waits for every pending sink to SETTLE (fulfilled or rejected) before this function's own returned promise resolves, so one async sink rejecting early never short-circuits — and therefore never hides — a later sink's failure the way Promise.all's reject-on-first-rejection would. Every pending promise still gets a handler attached (via allSettled itself, not a hand-rolled per-promise .catch), so nothing is ever left as an unhandled rejection — including the sync-throw-plus-pending-promises case: sync errors collected during the loop are merged with any async rejections once every pending promise has settled, and the combined AggregateError (if any) is thrown from the returned promise. When no sink returned a promise, the sync errors (if any) are thrown immediately and the function returns void synchronously, keeping AuditLogger.record's plain sync try/catch path for an all-sync sink set.