import { isUnifiedTasksEnabled } from './task-unified-config.mjs'; import { mapLegacyTodoItemRow } from './task-unified-service.mjs'; function syncOpen(taskUnifiedService, fn) { return fn().catch((err) => { console.warn?.( '[task-unified-sync] failed open:', err instanceof Error ? err.message : err, ); }); } export function attachUnifiedTaskSync({ scheduleService, scheduledTaskService, taskUnifiedService, pool, env = process.env, logger = console, }) { if (!isUnifiedTasksEnabled(env) || !taskUnifiedService || !pool) { return { scheduleService, scheduledTaskService }; } const warn = (...args) => logger.warn?.(...args); if (scheduleService?.createItem) { const original = scheduleService.createItem.bind(scheduleService); scheduleService.createItem = async (payload) => { const item = await original(payload); if (item?.kind === 'task') { await syncOpen(taskUnifiedService, () => taskUnifiedService.syncFromLegacyMapped(mapLegacyTodoItemRow({ id: item.id, user_id: item.userId, title: item.title, timezone: item.timezone, status: item.status, source_channel: payload.sourceChannel ?? null, source_message_id: payload.sourceMessageId ?? null, source_text: payload.sourceText ?? null, }))); } return item; }; } if (scheduleService?.createReminder) { const original = scheduleService.createReminder.bind(scheduleService); scheduleService.createReminder = async (payload) => { const reminder = await original(payload); try { const [rows] = await pool.query( `SELECT i.*, r.id AS reminder_id, r.remind_at, r.channel, r.status AS reminder_status FROM h5_schedule_items i JOIN h5_schedule_reminders r ON r.item_id = i.id WHERE r.id = ? LIMIT 1`, [reminder.id], ); const row = rows?.[0]; if (row) { await taskUnifiedService.syncFromCommit({ userId: payload.userId, kind: 'timed_reminder', committed: { item: { id: row.id, title: row.title, startAt: row.start_at ?? reminder.remindAt, timezone: row.timezone, sourceChannel: row.source_channel, sourceMessageId: row.source_message_id, sourceText: row.source_text, }, reminder, }, }); } } catch (err) { warn('[task-unified-sync] reminder sync failed:', err instanceof Error ? err.message : err); } return reminder; }; } if (scheduleService?.createDailyTodoDigest) { const original = scheduleService.createDailyTodoDigest.bind(scheduleService); scheduleService.createDailyTodoDigest = async (payload) => { const subscription = await original(payload); await syncOpen(taskUnifiedService, () => taskUnifiedService.syncFromCommit({ userId: payload.userId, kind: 'create_daily_todo_digest', committed: { subscription }, })); return subscription; }; } if (scheduleService?.createBalanceLowAlert) { const original = scheduleService.createBalanceLowAlert.bind(scheduleService); scheduleService.createBalanceLowAlert = async (payload) => { const subscription = await original(payload); await syncOpen(taskUnifiedService, () => taskUnifiedService.syncFromCommit({ userId: payload.userId, kind: 'create_balance_alert', committed: { subscription }, })); return subscription; }; } if (scheduledTaskService?.createTask) { const original = scheduledTaskService.createTask.bind(scheduledTaskService); scheduledTaskService.createTask = async (payload) => { const task = await original(payload); await syncOpen(taskUnifiedService, () => taskUnifiedService.syncFromCommit({ userId: payload.userId, kind: 'scheduled_task', committed: { task }, })); return task; }; } if (scheduledTaskService?.cancelTask) { const original = scheduledTaskService.cancelTask.bind(scheduledTaskService); scheduledTaskService.cancelTask = async (payload) => { const task = await original(payload); if (task?.id && payload?.userId) { await syncOpen(taskUnifiedService, () => taskUnifiedService.markLegacyCancelled({ userId: payload.userId, legacyRef: { table: 'h5_scheduled_tasks', id: task.id }, })); } return task; }; } const automationLegacyRef = (task) => ({ table: 'h5_scheduled_tasks', id: task.id, }); if (scheduledTaskService?.markTaskSucceeded) { const original = scheduledTaskService.markTaskSucceeded.bind(scheduledTaskService); scheduledTaskService.markTaskSucceeded = async (task, options) => { const updated = await original(task, options); await syncOpen(taskUnifiedService, () => taskUnifiedService.syncLegacyLifecycle({ userId: updated.userId, legacyRef: automationLegacyRef(updated), status: updated.status === 'completed' ? 'completed' : 'active', nextRunAt: updated.nextRunAt ?? null, lastRunAt: updated.lastRunAt ?? null, lastError: null, })); return updated; }; } if (scheduledTaskService?.markTaskFailed) { const original = scheduledTaskService.markTaskFailed.bind(scheduledTaskService); scheduledTaskService.markTaskFailed = async (task, error, options) => { const updated = await original(task, error, options); await syncOpen(taskUnifiedService, () => taskUnifiedService.syncLegacyLifecycle({ userId: updated.userId, legacyRef: automationLegacyRef(updated), status: updated.status === 'failed' ? 'failed' : 'active', nextRunAt: updated.nextRunAt ?? null, lastError: updated.lastError ?? null, })); return updated; }; } return { scheduleService, scheduledTaskService }; }