const EVENTS = 'vk_events' const PAGE = 50 /** * @typedef {{ id: number, type: string, task_id: string|null, created_at: Date }} AgentEvent */ /** * 事件流的读取:发给我的事件 + 我所在公会的广播。`vk_events.id` 是游标。 * @param {{ db: import('knex').Knex }} deps */ export function createEvents({ db }) { if (typeof db !== 'function') throw new Error('createEvents: db is required') /** * @param {import('./agents.js').Agent} agent * @param {number} since * @returns {Promise} */ async function after(agent, since) { return db(EVENTS) .where('id', '>', since) .where((q) => q.where({ target_agent_id: agent.id }).orWhere((g) => g.whereNull('target_agent_id').where({ guild_id: agent.guild_id }))) .orderBy('id', 'asc') .limit(PAGE) .select('id', 'type', 'task_id', 'created_at') } /** * `/home` 用:返回上次已读游标之后的事件,并把游标推进到本次返回的最后一条。 * @param {object} _ctx * @param {{ agent: import('./agents.js').Agent }} input * @returns {Promise} */ async function pullUnread(_ctx, { agent }) { const row = await db('vk_agents').where({ id: agent.id }).first('events_cursor') const events = await after(agent, row.events_cursor) if (events.length > 0) await db('vk_agents').where({ id: agent.id }).where('events_cursor', '<', events[events.length - 1].id).update({ events_cursor: events[events.length - 1].id }) return events } /** * `/events?since=` 用:补取,不推进游标。 * @param {object} _ctx * @param {{ agent: import('./agents.js').Agent, since: number }} input * @returns {Promise} */ async function listSince(_ctx, { agent, since }) { return after(agent, since) } /** * 在调用方的事务里写一条事件(任务状态机之外的来源,如打赏)。 * @param {object} _ctx * @param {import('knex').Knex} trx * @param {{ guild_id: string, task_id: string, type: string, target_agent_id: string|null }} input */ async function emit(_ctx, trx, { guild_id, task_id, type, target_agent_id }) { await trx(EVENTS).insert({ guild_id, target_agent_id, type, task_id, payload: '{}', created_at: new Date() }) } return { pullUnread, listSince, emit } }