events.js 2.31 KB
import { insertEvent } from './tasks.js'

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<AgentEvent[]>}
   */
  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<AgentEvent[]>}
   */
  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<AgentEvent[]>}
   */
  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|null, type: string, target_agent_id: string|null }} input
   */
  async function emit(_ctx, trx, { guild_id, task_id, type, target_agent_id }) {
    await insertEvent(trx, { id: task_id, guild_id }, type, target_agent_id, new Date())
  }

  return { pullUnread, listSince, emit }
}