Archived
app 装配 mail:channel 作首个订阅方、出站经 mail.send、mail.send 工具真发信(dryRun 只校验)、轮询工作者;上传件存取抽出共用
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TM4kUBvekALu9qqmp7D3wr
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 邮件轮询工作者 —— 每 tick 挑到期账号各拉一轮(设计稿 §4);跨进程租约在 store 里,
|
||||
* 两个后端连同一个库时一个信箱同一时刻只有一个在拉。
|
||||
* 与 startDispatcher / startTaskAdvancer 同形;上一 tick 没跑完(账号多、信多)就跳过这一拍,不叠着跑。
|
||||
*/
|
||||
import type { MailPollOutcome } from '@modules/mail';
|
||||
import type { Container } from '../container.ts';
|
||||
|
||||
export type MailPollerDeps = { readonly container: Pick<Container, 'mail'>; readonly now?: () => Date };
|
||||
|
||||
export async function pollMailOnce(deps: MailPollerDeps): Promise<MailPollOutcome[]> {
|
||||
const outcomes = await deps.container.mail.pollDue((deps.now ?? (() => new Date()))());
|
||||
for (const outcome of outcomes) {
|
||||
if (outcome.error !== undefined) process.stderr.write(`[mail-poller] ${outcome.accountId}:${outcome.error}\n`);
|
||||
}
|
||||
return outcomes;
|
||||
}
|
||||
|
||||
export function startMailPoller(deps: MailPollerDeps, tickMs: number): { stop(): void } {
|
||||
let stopped = false;
|
||||
let running = false;
|
||||
const tick = async (): Promise<void> => {
|
||||
if (stopped || running) return;
|
||||
running = true;
|
||||
try {
|
||||
await pollMailOnce(deps);
|
||||
} catch (error) {
|
||||
process.stderr.write(`[mail-poller] ${error instanceof Error ? error.message : String(error)}\n`);
|
||||
} finally {
|
||||
running = false;
|
||||
}
|
||||
};
|
||||
const timer = setInterval(() => void tick(), tickMs);
|
||||
timer.unref();
|
||||
return {
|
||||
stop() {
|
||||
stopped = true;
|
||||
clearInterval(timer);
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user