Upload files to "src/dispatcher"
This commit is contained in:
@@ -0,0 +1,83 @@
|
|||||||
|
import type { ProviderResult } from "../providers/types";
|
||||||
|
import type { Sender } from "../sender/base";
|
||||||
|
import { logUtil } from "../utils/logger";
|
||||||
|
|
||||||
|
export interface DispatcherOptions {
|
||||||
|
/** Senders in priority order: index 0 tried first. */
|
||||||
|
senders: (Sender | null)[];
|
||||||
|
/** Preflight check before attempting send. Default: true. */
|
||||||
|
preflight?: boolean;
|
||||||
|
/** If true, dispatch succeeds even when every sender fails. Default: false. */
|
||||||
|
dryRun?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Dispatcher {
|
||||||
|
private readonly senders: Sender[];
|
||||||
|
private readonly preflight: boolean;
|
||||||
|
|
||||||
|
constructor(opts: DispatcherOptions) {
|
||||||
|
this.senders = opts.senders.filter((s): s is Sender => s !== null);
|
||||||
|
this.preflight = opts.preflight ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point passed to Collector as its `dispatch` callback.
|
||||||
|
* Encrypts once, then tries senders in priority order until one succeeds.
|
||||||
|
* Throws only if every sender fails (unless dryRun is enabled).
|
||||||
|
*/
|
||||||
|
dispatch = async (batch: ProviderResult[]): Promise<void> => {
|
||||||
|
if (batch.length === 0) return;
|
||||||
|
|
||||||
|
if (this.senders.length === 0) {
|
||||||
|
logUtil.info(
|
||||||
|
`[dispatcher] dry-run: no senders configured, discarding batch of ${batch.length}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt once; reuse across fallback attempts.
|
||||||
|
const envelope = await this.senders[0]!.createEnvelope(batch);
|
||||||
|
|
||||||
|
const failures: Array<{ sender: string; error: unknown }> = [];
|
||||||
|
|
||||||
|
for (const sender of this.senders) {
|
||||||
|
if (this.preflight) {
|
||||||
|
try {
|
||||||
|
if (!(await sender.healthy())) {
|
||||||
|
logUtil.warn(
|
||||||
|
`[dispatcher] skipping unhealthy sender ${sender.name}`,
|
||||||
|
);
|
||||||
|
failures.push({
|
||||||
|
sender: sender.name,
|
||||||
|
error: new Error("unhealthy"),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logUtil.warn(
|
||||||
|
`[dispatcher] healthcheck threw for ${sender.name}:`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
failures.push({ sender: sender.name, error: err });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sender.send(envelope);
|
||||||
|
logUtil.info(
|
||||||
|
`[dispatcher] delivered batch of ${batch.length} via ${sender.name}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
logUtil.warn(`[dispatcher] ${sender.name} failed, falling back:`, err);
|
||||||
|
failures.push({ sender: sender.name, error: err });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logUtil.warn(
|
||||||
|
`[dispatcher] all ${this.senders.length} sender(s) failed for batch of ${batch.length}`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user