import { query, mutation, internalMutation } from "./_generated/server"; import { v } from "convex/values"; import { internal } from "./_generated/api"; const TYPING_TTL_MS = 6000; export const startTyping = mutation({ args: { channelId: v.id("channels"), userId: v.id("userProfiles"), username: v.string(), }, returns: v.null(), handler: async (ctx, args) => { const expiresAt = Date.now() + TYPING_TTL_MS; const existing = await ctx.db .query("typingIndicators") .withIndex("by_channel", (q) => q.eq("channelId", args.channelId)) .collect(); const userTyping = existing.find((t) => t.userId === args.userId); if (userTyping) { await ctx.db.patch(userTyping._id, { expiresAt }); } else { await ctx.db.insert("typingIndicators", { channelId: args.channelId, userId: args.userId, username: args.username, expiresAt, }); } await ctx.scheduler.runAfter(TYPING_TTL_MS, internal.typing.cleanExpired, {}); return null; }, }); export const stopTyping = mutation({ args: { channelId: v.id("channels"), userId: v.id("userProfiles"), }, returns: v.null(), handler: async (ctx, args) => { const indicators = await ctx.db .query("typingIndicators") .withIndex("by_channel", (q) => q.eq("channelId", args.channelId)) .collect(); const mine = indicators.find((t) => t.userId === args.userId); if (mine) { await ctx.db.delete(mine._id); } return null; }, }); export const getTyping = query({ args: { channelId: v.id("channels") }, returns: v.array( v.object({ userId: v.id("userProfiles"), username: v.string(), }) ), handler: async (ctx, args) => { const now = Date.now(); const indicators = await ctx.db .query("typingIndicators") .withIndex("by_channel", (q) => q.eq("channelId", args.channelId)) .collect(); return indicators .filter((t) => t.expiresAt > now) .map((t) => ({ userId: t.userId, username: t.username })); }, }); export const cleanExpired = internalMutation({ args: {}, returns: v.null(), handler: async (ctx) => { const now = Date.now(); const expired = await ctx.db.query("typingIndicators").collect(); for (const t of expired) { if (t.expiresAt <= now) { await ctx.db.delete(t._id); } } return null; }, });