diff --git a/src/lib/deltas.ts b/src/lib/deltas.ts index 20d542710..082982a75 100644 --- a/src/lib/deltas.ts +++ b/src/lib/deltas.ts @@ -9,49 +9,73 @@ export type OperationInsert = { insert: [number, string] }; export type Operation = OperationDelete | OperationInsert; export namespace Operation { - export const isDelete = (operation: Operation): operation is OperationDelete => - 'delete' in operation; + export const isDelete = (operation: Operation): operation is OperationDelete => + 'delete' in operation; - export const isInsert = (operation: Operation): operation is OperationInsert => - 'insert' in operation; + export const isInsert = (operation: Operation): operation is OperationInsert => + 'insert' in operation; } export type Delta = { timestampMs: number; operations: Operation[] }; export type DeltasEvent = { - deltas: Delta[]; - filePath: string; + deltas: Delta[]; + filePath: string; }; -export const list = (params: { projectId: string; sessionId: string; paths?: string[] }) => - invoke>('list_deltas', params); +const cache: Record>>> = {}; + +export const list = async (params: { projectId: string; sessionId: string; paths?: string[] }) => { + const sessionCache = cache[params.projectId] || {}; + if (params.sessionId in sessionCache) { + return sessionCache[params.sessionId].then((deltas) => + Object.fromEntries( + Object.entries(deltas).filter(([path]) => + params.paths ? params.paths.includes(path) : true + ) + ) + ); + } + + const promise = invoke>('list_deltas', { + projectId: params.projectId, + sessionId: params.sessionId + }); + sessionCache[params.sessionId] = promise; + cache[params.projectId] = sessionCache; + return promise.then((deltas) => + Object.fromEntries( + Object.entries(deltas).filter(([path]) => (params.paths ? params.paths.includes(path) : true)) + ) + ); +}; export const subscribe = ( - params: { projectId: string; sessionId: string }, - callback: (filepath: string, deltas: Delta[]) => void + params: { projectId: string; sessionId: string }, + callback: (filepath: string, deltas: Delta[]) => void ) => { - log.info(`Subscribing to deltas for ${params.projectId}, ${params.sessionId}`); - return appWindow.listen( - `project://${params.projectId}/sessions/${params.sessionId}/deltas`, - (event) => { - log.info( - `Received deltas for ${params.projectId}, ${params.sessionId}, ${event.payload.filePath}` - ); - callback(event.payload.filePath, event.payload.deltas); - } - ); + log.info(`Subscribing to deltas for ${params.projectId}, ${params.sessionId}`); + return appWindow.listen( + `project://${params.projectId}/sessions/${params.sessionId}/deltas`, + (event) => { + log.info( + `Received deltas for ${params.projectId}, ${params.sessionId}, ${event.payload.filePath}` + ); + callback(event.payload.filePath, event.payload.deltas); + } + ); }; export default async (params: { projectId: string; sessionId: string }) => { - const init = await list(params); + const init = await list(params); - const store = writable>(init); - subscribe(params, (filepath, newDeltas) => - store.update((deltas) => ({ - ...deltas, - [filepath]: newDeltas - })) - ); + const store = writable>(init); + subscribe(params, (filepath, newDeltas) => + store.update((deltas) => ({ + ...deltas, + [filepath]: newDeltas + })) + ); - return store as Readable>; + return store as Readable>; }; diff --git a/src/lib/sessions.ts b/src/lib/sessions.ts index 996f37982..0c0f4e119 100644 --- a/src/lib/sessions.ts +++ b/src/lib/sessions.ts @@ -4,54 +4,100 @@ import { writable, type Readable } from 'svelte/store'; import { log } from '$lib'; export type Activity = { - type: string; - timestampMs: number; - message: string; + type: string; + timestampMs: number; + message: string; }; export namespace Session { - export const within = (session: Session | undefined, timestampMs: number) => { - if (!session) return false; - const { startTimestampMs, lastTimestampMs } = session.meta; - return startTimestampMs <= timestampMs && timestampMs <= lastTimestampMs; - }; + export const within = (session: Session | undefined, timestampMs: number) => { + if (!session) return false; + const { startTimestampMs, lastTimestampMs } = session.meta; + return startTimestampMs <= timestampMs && timestampMs <= lastTimestampMs; + }; } export type Session = { - id: string; - hash?: string; - meta: { - startTimestampMs: number; - lastTimestampMs: number; - branch?: string; - commit?: string; - }; - activity: Activity[]; + id: string; + hash?: string; + meta: { + startTimestampMs: number; + lastTimestampMs: number; + branch?: string; + commit?: string; + }; + activity: Activity[]; }; -export const listFiles = (params: { projectId: string; sessionId: string; paths?: string[] }) => - invoke>('list_session_files', params); +const filesCache: Record>>> = {}; -const list = (params: { projectId: string }) => invoke('list_sessions', params); +export const listFiles = async (params: { + projectId: string; + sessionId: string; + paths?: string[]; +}) => { + const sessionFilesCache = filesCache[params.projectId] || {}; + if (params.sessionId in sessionFilesCache) { + return sessionFilesCache[params.sessionId].then((files) => { + return Object.fromEntries( + Object.entries(files).filter(([path]) => + params.paths ? params.paths.includes(path) : true + ) + ); + }); + } + + const promise = invoke>('list_session_files', { + sessionId: params.sessionId, + projectId: params.projectId + }); + sessionFilesCache[params.sessionId] = promise; + filesCache[params.projectId] = sessionFilesCache; + return promise.then((files) => { + return Object.fromEntries( + Object.entries(files).filter(([path]) => (params.paths ? params.paths.includes(path) : true)) + ); + }); +}; + +const sessionsCache: Record> = {}; + +const list = async (params: { projectId: string; earliestTimestampMs?: number }) => { + if (params.projectId in sessionsCache) { + return sessionsCache[params.projectId].then((sessions) => + sessions.filter((s) => + params.earliestTimestampMs ? s.meta.startTimestampMs >= params.earliestTimestampMs : true + ) + ); + } + sessionsCache[params.projectId] = invoke('list_sessions', { + projectId: params.projectId + }); + return sessionsCache[params.projectId].then((sessions) => + sessions.filter((s) => + params.earliestTimestampMs ? s.meta.startTimestampMs >= params.earliestTimestampMs : true + ) + ); +}; export default async (params: { projectId: string; earliestTimestampMs?: number }) => { - const store = writable([] as Session[]); - list(params).then((sessions) => { - store.set(sessions); - }); + const store = writable([] as Session[]); + list(params).then((sessions) => { + store.set(sessions); + }); - appWindow.listen(`project://${params.projectId}/sessions`, async (event) => { - log.info(`Received sessions event, projectId: ${params.projectId}`); - const session = event.payload; - store.update((sessions) => { - const index = sessions.findIndex((session) => session.id === event.payload.id); - if (index === -1) { - return [...sessions, session]; - } else { - return [...sessions.slice(0, index), session, ...sessions.slice(index + 1)]; - } - }); - }); + appWindow.listen(`project://${params.projectId}/sessions`, async (event) => { + log.info(`Received sessions event, projectId: ${params.projectId}`); + const session = event.payload; + store.update((sessions) => { + const index = sessions.findIndex((session) => session.id === event.payload.id); + if (index === -1) { + return [...sessions, session]; + } else { + return [...sessions.slice(0, index), session, ...sessions.slice(index + 1)]; + } + }); + }); - return store as Readable; + return store as Readable; };