74 lines
1.9 KiB
TypeScript
74 lines
1.9 KiB
TypeScript
import { query } from '../db/client.js'
|
|
|
|
export type RealtimeEventRow = {
|
|
id: number
|
|
event_type: string
|
|
entity_id: number
|
|
scopes: unknown
|
|
operation: string
|
|
data: unknown
|
|
admin_audience: boolean
|
|
worker_ids: unknown
|
|
broadcast_workers: boolean
|
|
occurred_at: string
|
|
}
|
|
|
|
export async function createRealtimeEvent(input: {
|
|
eventType: string
|
|
entityId: number
|
|
scopes: string[]
|
|
operation: string
|
|
data?: unknown
|
|
adminAudience: boolean
|
|
workerIds: number[]
|
|
broadcastWorkers: boolean
|
|
occurredAt: string
|
|
}): Promise<RealtimeEventRow> {
|
|
const result = await query<RealtimeEventRow>(
|
|
`
|
|
INSERT INTO realtime_events (
|
|
event_type, entity_id, scopes, operation, data,
|
|
admin_audience, worker_ids, broadcast_workers, occurred_at
|
|
) VALUES ($1, $2, $3::jsonb, $4, $5::jsonb, $6, $7::jsonb, $8, $9)
|
|
RETURNING *
|
|
`,
|
|
[
|
|
input.eventType,
|
|
input.entityId,
|
|
JSON.stringify(input.scopes),
|
|
input.operation,
|
|
input.data === undefined ? null : JSON.stringify(input.data),
|
|
input.adminAudience,
|
|
JSON.stringify(input.workerIds),
|
|
input.broadcastWorkers,
|
|
input.occurredAt,
|
|
],
|
|
)
|
|
const row = result.rows[0]
|
|
if (!row) throw new Error('realtime_event_create_failed')
|
|
|
|
// 事件仅用于短期断线补发;每 100 条清理一次,避免每次业务事件都额外触发 DELETE。
|
|
if (Number(row.id) % 100 === 0) {
|
|
await query(
|
|
`
|
|
DELETE FROM realtime_events
|
|
WHERE id < GREATEST(0, (SELECT COALESCE(MAX(id), 0) - 500 FROM realtime_events))
|
|
`,
|
|
)
|
|
}
|
|
return row
|
|
}
|
|
|
|
export async function listRealtimeEventsAfter(eventId: number, limit = 500) {
|
|
const result = await query<RealtimeEventRow>(
|
|
`
|
|
SELECT * FROM realtime_events
|
|
WHERE id > $1
|
|
ORDER BY id ASC
|
|
LIMIT $2
|
|
`,
|
|
[Math.max(0, Number(eventId) || 0), Math.min(500, Math.max(1, Number(limit) || 500))],
|
|
)
|
|
return result.rows
|
|
}
|