feat(canvas): implement dropped connection resolution and enhance connection handling

This commit is contained in:
2026-04-04 09:56:01 +02:00
parent 12202ad337
commit 90d6fe55b1
18 changed files with 1288 additions and 165 deletions

View File

@@ -2,6 +2,8 @@ import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { optionalAuth, requireAuth } from "./helpers";
const PERFORMANCE_LOG_THRESHOLD_MS = 100;
// ============================================================================
// Queries
// ============================================================================
@@ -30,14 +32,33 @@ export const list = query({
export const get = query({
args: { canvasId: v.id("canvases") },
handler: async (ctx, { canvasId }) => {
const startedAt = Date.now();
const authStartedAt = Date.now();
const user = await optionalAuth(ctx);
const authMs = Date.now() - authStartedAt;
if (!user) {
return null;
}
const canvasLookupStartedAt = Date.now();
const canvas = await ctx.db.get(canvasId);
const canvasLookupMs = Date.now() - canvasLookupStartedAt;
if (!canvas || canvas.ownerId !== user.userId) {
return null;
}
const durationMs = Date.now() - startedAt;
if (durationMs >= PERFORMANCE_LOG_THRESHOLD_MS) {
console.warn("[canvases.get] slow canvas query", {
canvasId,
userId: user.userId,
authMs,
canvasLookupMs,
canvasUpdatedAt: canvas.updatedAt,
durationMs,
});
}
return canvas;
},
});

View File

@@ -90,23 +90,34 @@ export const list = query({
args: { canvasId: v.id("canvases") },
handler: async (ctx, { canvasId }) => {
const startedAt = Date.now();
const authStartedAt = Date.now();
const user = await requireAuth(ctx);
const authMs = Date.now() - authStartedAt;
const canvasLookupStartedAt = Date.now();
const canvas = await ctx.db.get(canvasId);
const canvasLookupMs = Date.now() - canvasLookupStartedAt;
if (!canvas || canvas.ownerId !== user.userId) {
return [];
}
const collectStartedAt = Date.now();
const edges = await ctx.db
.query("edges")
.withIndex("by_canvas", (q) => q.eq("canvasId", canvasId))
.collect();
const collectMs = Date.now() - collectStartedAt;
const durationMs = Date.now() - startedAt;
if (durationMs >= PERFORMANCE_LOG_THRESHOLD_MS) {
console.warn("[edges.list] slow list query", {
canvasId,
userId: user.userId,
authMs,
canvasLookupMs,
collectMs,
edgeCount: edges.length,
canvasUpdatedAt: canvas.updatedAt,
durationMs,
});
}
@@ -191,6 +202,13 @@ export const create = mutation({
targetHandle: args.targetHandle,
});
console.info("[canvas.updatedAt] touch", {
canvasId: args.canvasId,
source: "edges.create",
edgeId,
sourceNodeId: args.sourceNodeId,
targetNodeId: args.targetNodeId,
});
await ctx.db.patch(args.canvasId, { updatedAt: Date.now() });
if (args.clientRequestId) {
await ctx.db.insert("mutationRequests", {
@@ -239,6 +257,11 @@ export const remove = mutation({
}
await ctx.db.delete(edgeId);
console.info("[canvas.updatedAt] touch", {
canvasId: edge.canvasId,
source: "edges.remove",
edgeId,
});
await ctx.db.patch(edge.canvasId, { updatedAt: Date.now() });
console.info("[edges.remove] success", {

View File

@@ -568,21 +568,32 @@ export const list = query({
args: { canvasId: v.id("canvases") },
handler: async (ctx, { canvasId }) => {
const startedAt = Date.now();
const authStartedAt = Date.now();
const user = await requireAuth(ctx);
await getCanvasOrThrow(ctx, canvasId, user.userId);
const authMs = Date.now() - authStartedAt;
const canvasLookupStartedAt = Date.now();
const canvas = await getCanvasOrThrow(ctx, canvasId, user.userId);
const canvasLookupMs = Date.now() - canvasLookupStartedAt;
const collectStartedAt = Date.now();
const nodes = await ctx.db
.query("nodes")
.withIndex("by_canvas", (q) => q.eq("canvasId", canvasId))
.collect();
const collectMs = Date.now() - collectStartedAt;
const durationMs = Date.now() - startedAt;
if (durationMs >= PERFORMANCE_LOG_THRESHOLD_MS) {
console.warn("[nodes.list] slow list query", {
canvasId,
userId: user.userId,
authMs,
canvasLookupMs,
collectMs,
nodeCount: nodes.length,
approxPayloadBytes: estimateSerializedBytes(nodes),
canvasUpdatedAt: canvas.updatedAt,
durationMs,
});
}
@@ -1221,6 +1232,11 @@ export const move = mutation({
await getCanvasOrThrow(ctx, node.canvasId, user.userId);
await ctx.db.patch(nodeId, { positionX, positionY });
console.info("[canvas.updatedAt] touch", {
canvasId: node.canvasId,
source: "nodes.move",
nodeId,
});
await ctx.db.patch(node.canvasId, { updatedAt: Date.now() });
},
});
@@ -1245,6 +1261,12 @@ export const resize = mutation({
? ADJUSTMENT_MIN_WIDTH
: width;
await ctx.db.patch(nodeId, { width: clampedWidth, height });
console.info("[canvas.updatedAt] touch", {
canvasId: node.canvasId,
source: "nodes.resize",
nodeId,
nodeType: node.type,
});
await ctx.db.patch(node.canvasId, { updatedAt: Date.now() });
},
});
@@ -1277,6 +1299,11 @@ export const batchMove = mutation({
await ctx.db.patch(nodeId, { positionX, positionY });
}
console.info("[canvas.updatedAt] touch", {
canvasId,
source: "nodes.batchMove",
moveCount: moves.length,
});
await ctx.db.patch(canvasId, { updatedAt: Date.now() });
},
});
@@ -1297,6 +1324,13 @@ export const updateData = mutation({
await getCanvasOrThrow(ctx, node.canvasId, user.userId);
const normalizedData = normalizeNodeDataForWrite(node.type, data);
await ctx.db.patch(nodeId, { data: normalizedData });
console.info("[canvas.updatedAt] touch", {
canvasId: node.canvasId,
source: "nodes.updateData",
nodeId,
nodeType: node.type,
approxDataBytes: estimateSerializedBytes(normalizedData),
});
await ctx.db.patch(node.canvasId, { updatedAt: Date.now() });
},
});