perf: refactor sse handleing

This commit is contained in:
d-kimsuon
2025-09-18 20:42:44 +09:00
parent a90ef520dd
commit eb5a8ddeeb
38 changed files with 727 additions and 597 deletions

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@tanstack/react-query";
import { honoClient } from "../lib/api/client";
import { fileCompletionQuery } from "../lib/api/queries";
export type FileCompletionEntry = {
name: string;
@@ -19,21 +19,8 @@ export const useFileCompletion = (
enabled = true,
) => {
return useQuery({
queryKey: ["file-completion", projectId, basePath],
queryFn: async (): Promise<FileCompletionResult> => {
const response = await honoClient.api.projects[":projectId"][
"file-completion"
].$get({
param: { projectId },
query: { basePath },
});
if (!response.ok) {
throw new Error("Failed to fetch file completion");
}
return response.json();
},
queryKey: fileCompletionQuery(projectId, basePath).queryKey,
queryFn: fileCompletionQuery(projectId, basePath).queryFn,
enabled: enabled && !!projectId,
staleTime: 1000 * 60 * 5, // 5分間キャッシュ
});

View File

@@ -1,113 +0,0 @@
import { useQueryClient } from "@tanstack/react-query";
import { useSetAtom } from "jotai";
import { useCallback, useEffect } from "react";
import { aliveTasksAtom } from "../app/projects/[projectId]/sessions/[sessionId]/store/aliveTasksAtom";
import { projetsQueryConfig } from "../app/projects/hooks/useProjects";
import { honoClient } from "../lib/api/client";
import type { SSEEvent } from "../server/service/events/types";
type ParsedEvent = {
event: string;
data: SSEEvent;
id: string;
};
const parseSSEEvent = (text: string): ParsedEvent => {
const lines = text.split("\n");
const eventIndex = lines.findIndex((line) => line.startsWith("event:"));
const dataIndex = lines.findIndex((line) => line.startsWith("data:"));
const idIndex = lines.findIndex((line) => line.startsWith("id:"));
const endIndex = (index: number) => {
const targets = [eventIndex, dataIndex, idIndex, lines.length].filter(
(current) => current > index,
);
return Math.min(...targets);
};
if (eventIndex === -1 || dataIndex === -1 || idIndex === -1) {
console.error("failed", text);
throw new Error("Failed to parse SSE event");
}
const event = lines.slice(eventIndex, endIndex(eventIndex)).join("\n");
const data = lines.slice(dataIndex, endIndex(dataIndex)).join("\n");
const id = lines.slice(idIndex, endIndex(idIndex)).join("\n");
return {
id: id.slice("id:".length).trim(),
event: event.slice("event:".length).trim(),
data: JSON.parse(
data.slice(data.indexOf("{"), data.lastIndexOf("}") + 1),
) as SSEEvent,
};
};
const parseSSEEvents = (text: string): ParsedEvent[] => {
const eventTexts = text
.split("\n\n")
.filter((eventText) => eventText.length > 0);
return eventTexts.map((eventText) => parseSSEEvent(eventText));
};
let isInitialized = false;
export const useServerEvents = () => {
const queryClient = useQueryClient();
const setAliveTasks = useSetAtom(aliveTasksAtom);
const listener = useCallback(async () => {
console.log("listening to events");
const response = await honoClient.api.events.state_changes.$get();
if (!response.ok) {
throw new Error("Failed to fetch events");
}
const reader = response.body?.getReader();
if (!reader) {
throw new Error("Failed to get reader");
}
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const events = parseSSEEvents(decoder.decode(value));
for (const event of events) {
console.log("data", event);
if (event.data.type === "project_changed") {
await queryClient.invalidateQueries({
queryKey: projetsQueryConfig.queryKey,
});
}
if (event.data.type === "session_changed") {
await queryClient.invalidateQueries({ queryKey: ["sessions"] });
}
if (event.data.type === "task_changed") {
setAliveTasks(event.data.data);
}
}
}
}, [queryClient, setAliveTasks]);
useEffect(() => {
if (isInitialized === false) {
void listener()
.then(() => {
console.log("registered events listener");
isInitialized = true;
})
.catch((error) => {
console.error("failed to register events listener", error);
isInitialized = true;
});
}
}, [listener]);
};