Download packages/app/src/context/server-session.test.ts from SaylorTwift/opencode: direct link, hf CLI and curl.
- Browser
- Download file 66.6 kB
-
https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/app/src/context/server-session.test.ts
- Command line
-
hf download hf://SaylorTwift/opencode/packages/app/src/context/server-session.test.ts
-
curl -L -o server-session.test.ts https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/app/src/context/server-session.test.ts
66.6 kB
| import { describe, expect, test } from "bun:test" | |
| import type { retry } from "@opencode-ai/core/util/retry" | |
| import type { OpenCodeEvent, SessionApi } from "@opencode-ai/client/promise" | |
| import type { Message, OpencodeClient, Part, Session } from "@opencode-ai/sdk/v2/client" | |
| import { createServerSession } from "./server-session" | |
| import type { ServerApi } from "@/utils/server" | |
| type MessageApi = ServerApi["message"] | |
| const session = (id: string, parentID?: string): Session => ({ | |
| id, | |
| slug: id, | |
| projectID: "project", | |
| directory: "/repo", | |
| title: id, | |
| version: "1", | |
| parentID, | |
| time: { created: 1, updated: 1 }, | |
| }) | |
| type UserMessage = Extract<Message, { role: "user" }> | |
| type AssistantMessage = Extract<Message, { role: "assistant" }> | |
| type TextPart = Extract<Part, { type: "text" }> | |
| type MessageResponse = { | |
| data: { info: Message; parts: Part[] }[] | |
| response: { headers: Headers } | |
| } | |
| type SingleMessageResponse = { data: MessageResponse["data"][number] } | |
| const userMessage = (id: string, input: Partial<UserMessage> = {}): UserMessage => ({ | |
| id, | |
| sessionID: "child", | |
| role: "user", | |
| time: { created: 1 }, | |
| agent: "build", | |
| model: { providerID: "provider", modelID: "model" }, | |
| ...input, | |
| }) | |
| const assistantMessage = (id: string, parentID: string, input: Partial<AssistantMessage> = {}): AssistantMessage => ({ | |
| id, | |
| sessionID: "child", | |
| role: "assistant", | |
| time: { created: Number(id.at(-1)), completed: Number(id.at(-1)) }, | |
| parentID, | |
| modelID: "model", | |
| providerID: "provider", | |
| mode: "build", | |
| agent: "build", | |
| path: { cwd: "/repo", root: "/repo" }, | |
| cost: 0, | |
| tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, | |
| ...input, | |
| }) | |
| const textPart = (messageID: string, input: Partial<TextPart> = {}): TextPart => ({ | |
| id: "part", | |
| sessionID: "child", | |
| messageID, | |
| type: "text", | |
| text: "text", | |
| ...input, | |
| }) | |
| const response = (data: MessageResponse["data"] = [], cursor?: string): MessageResponse => ({ | |
| data, | |
| response: { headers: new Headers(cursor ? { "x-next-cursor": cursor } : undefined) }, | |
| }) | |
| const singleResponse = (info: Message, parts: Part[] = []): SingleMessageResponse => ({ data: { info, parts } }) | |
| const deferredResponse = () => Promise.withResolvers<MessageResponse>() | |
| function messageClient(...responses: Array<MessageResponse | Promise<MessageResponse>>) { | |
| let index = 0 | |
| const requests: unknown[] = [] | |
| const waiting = new Map<number, () => void>() | |
| const client = { | |
| session: { | |
| get: async () => ({ data: session("child", "root") }), | |
| messages: (input: unknown) => { | |
| requests.push(input) | |
| waiting.get(requests.length)?.() | |
| waiting.delete(requests.length) | |
| return responses[index++] | |
| }, | |
| }, | |
| } as unknown as OpencodeClient | |
| return Object.assign(client, { | |
| requests, | |
| requested(count: number) { | |
| if (requests.length >= count) return Promise.resolve() | |
| return new Promise<void>((resolve) => waiting.set(count, resolve)) | |
| }, | |
| }) | |
| } | |
| function rootMessageClient( | |
| pages: Array<MessageResponse | Promise<MessageResponse>>, | |
| roots: Array<SingleMessageResponse | Promise<SingleMessageResponse>>, | |
| ) { | |
| let pageIndex = 0 | |
| let rootIndex = 0 | |
| const requests: unknown[] = [] | |
| const rootRequests: unknown[] = [] | |
| const rootWaiting = new Map<number, () => void>() | |
| const client = { | |
| session: { | |
| get: async () => ({ data: session("child", "root") }), | |
| messages: (input: unknown) => { | |
| requests.push(input) | |
| return pages[pageIndex++] | |
| }, | |
| message: (input: unknown) => { | |
| rootRequests.push(input) | |
| rootWaiting.get(rootRequests.length)?.() | |
| rootWaiting.delete(rootRequests.length) | |
| return roots[rootIndex++] | |
| }, | |
| }, | |
| } as unknown as OpencodeClient | |
| return Object.assign(client, { | |
| requests, | |
| rootRequests, | |
| rootRequested(count: number) { | |
| if (rootRequests.length >= count) return Promise.resolve() | |
| return new Promise<void>((resolve) => rootWaiting.set(count, resolve)) | |
| }, | |
| }) | |
| } | |
| const retryImmediately: typeof retry = async (task, options = {}) => { | |
| const attempts = options.attempts ?? 3 | |
| for (let attempt = 0; ; attempt++) { | |
| try { | |
| return await task() | |
| } catch (error) { | |
| if (attempt === attempts - 1) throw error | |
| } | |
| } | |
| } | |
| function setup(sessions: Record<string, Session>) { | |
| const get: unknown[] = [] | |
| const messages: unknown[] = [] | |
| const client = { | |
| session: { | |
| get: async (input: unknown) => { | |
| get.push(input) | |
| const id = (input as { sessionID: string }).sessionID | |
| return { data: sessions[id] } | |
| }, | |
| messages: async (input: unknown) => { | |
| messages.push(input) | |
| return response() | |
| }, | |
| diff: async () => ({ data: [] }), | |
| todo: async () => ({ data: [] }), | |
| }, | |
| } as unknown as OpencodeClient | |
| return { get, messages, store: createServerSession(client) } | |
| } | |
| describe("server session", () => { | |
| test("projects V2 session events into current and legacy message state", () => { | |
| const ctx = setup({ child: session("child") }) | |
| ctx.store.remember(session("child")) | |
| ctx.store.set("session_message", "child", [ | |
| { | |
| id: "msg_1_user", | |
| type: "user", | |
| text: "hello", | |
| time: { created: 1 }, | |
| }, | |
| ]) | |
| const apply = (input: object) => ctx.store.applyV2(input as OpenCodeEvent) | |
| apply({ | |
| id: "evt_step", | |
| created: 2, | |
| type: "session.step.started", | |
| durable: { aggregateID: "child", seq: 1, version: 1 }, | |
| location: { directory: "/repo" }, | |
| data: { | |
| sessionID: "child", | |
| assistantMessageID: "msg_2_assistant", | |
| agent: "build", | |
| model: { id: "model", providerID: "provider" }, | |
| }, | |
| }) | |
| apply({ | |
| id: "evt_text_start", | |
| created: 3, | |
| type: "session.text.started", | |
| durable: { aggregateID: "child", seq: 2, version: 1 }, | |
| location: { directory: "/repo" }, | |
| data: { sessionID: "child", assistantMessageID: "msg_2_assistant", ordinal: 0 }, | |
| }) | |
| apply({ | |
| id: "evt_text_delta", | |
| created: 4, | |
| type: "session.text.delta", | |
| location: { directory: "/repo" }, | |
| data: { sessionID: "child", assistantMessageID: "msg_2_assistant", ordinal: 0, delta: "world" }, | |
| }) | |
| expect(ctx.store.data.session_message.child?.at(-1)).toMatchObject({ | |
| id: "msg_2_assistant", | |
| type: "assistant", | |
| content: [{ type: "text", text: "world" }], | |
| }) | |
| expect(ctx.store.data.message.child?.map((message) => message.id)).toEqual(["msg_1_user", "msg_2_assistant"]) | |
| expect(ctx.store.data.part.msg_2_assistant).toMatchObject([{ type: "text", text: "world" }]) | |
| }) | |
| test("resolves lineage by session ID without directory", async () => { | |
| const ctx = setup({ child: session("child", "root"), root: session("root") }) | |
| const result = await ctx.store.lineage.resolve("child") | |
| expect(result.root.id).toBe("root") | |
| expect(ctx.get).toEqual([{ sessionID: "child" }, { sessionID: "root" }]) | |
| expect(ctx.store.lineage.peek("child")).toEqual(result) | |
| }) | |
| test("loads session content through the server client", async () => { | |
| const ctx = setup({ root: session("root") }) | |
| await ctx.store.sync("root") | |
| expect(ctx.get).toEqual([{ sessionID: "root" }]) | |
| expect(ctx.messages).toEqual([{ sessionID: "root", limit: 20, before: undefined }]) | |
| expect(ctx.store.data.message.root).toEqual([]) | |
| }) | |
| test("loads current session content through the current message API", async () => { | |
| const requests: unknown[] = [] | |
| const user = { id: "msg_z_user", type: "user", text: "hello", time: { created: 1 } } | |
| const assistant = { | |
| id: "msg_a_assistant", | |
| type: "assistant", | |
| agent: "build", | |
| model: { id: "model", providerID: "provider" }, | |
| content: [{ type: "text", text: "hi" }], | |
| time: { created: 2, completed: 3 }, | |
| } | |
| const client = { | |
| session: { | |
| messages: () => { | |
| throw new Error("legacy message endpoint called") | |
| }, | |
| }, | |
| } as unknown as OpencodeClient | |
| const messageApi = { | |
| list: async (input: unknown) => { | |
| requests.push(input) | |
| return { data: [assistant, user], cursor: { previous: null, next: null } } | |
| }, | |
| } as unknown as MessageApi | |
| const store = createServerSession(client, {} as SessionApi, messageApi) | |
| store.remember(session("root")) | |
| await store.sync("root") | |
| expect(requests).toEqual([{ sessionID: "root", limit: 20, order: "desc" }]) | |
| expect(store.data.session_message.root.map((message) => message.id)).toEqual([user.id, assistant.id]) | |
| expect(store.data.message.root.map((message) => message.id)).toEqual([user.id, assistant.id]) | |
| }) | |
| test("extends a current page to include the user for split assistant turns", async () => { | |
| const user = { id: "msg_1_user", type: "user", text: "hello", time: { created: 1 } } as const | |
| const assistant = (id: string, created: number) => ({ | |
| id, | |
| type: "assistant" as const, | |
| agent: "build", | |
| model: { id: "model", providerID: "provider" }, | |
| content: [{ type: "text" as const, text: id }], | |
| time: { created, completed: created }, | |
| }) | |
| const assistants = [ | |
| assistant("msg_2_assistant", 2), | |
| assistant("msg_3_assistant", 3), | |
| assistant("msg_4_assistant", 4), | |
| ] | |
| const pages = [ | |
| { data: assistants.slice(1).toReversed(), cursor: { previous: null, next: "older" } }, | |
| { data: [assistants[0], user], cursor: { previous: null, next: null } }, | |
| ] | |
| const requests: unknown[] = [] | |
| const messageApi = { | |
| list: async (input: unknown) => { | |
| requests.push(input) | |
| return pages.shift()! | |
| }, | |
| } as unknown as MessageApi | |
| const store = createServerSession({} as OpencodeClient, {} as SessionApi, messageApi) | |
| store.remember(session("root")) | |
| await store.sync("root") | |
| expect(requests).toEqual([ | |
| { sessionID: "root", limit: 20, order: "desc" }, | |
| { sessionID: "root", limit: 20, cursor: "older" }, | |
| ]) | |
| expect(store.data.message.root.map((message) => message.id)).toEqual([ | |
| user.id, | |
| ...assistants.map((item) => item.id), | |
| ]) | |
| expect(assistants.map((item) => store.data.part[item.id]?.[0]?.type)).toEqual(["text", "text", "text"]) | |
| }) | |
| test("indexes V1 messages for the current timeline projection", async () => { | |
| const user = userMessage("message-1", { sessionID: "root" }) | |
| const assistant = assistantMessage("message-2", user.id, { sessionID: "root" }) | |
| const client = messageClient( | |
| response([ | |
| { info: user, parts: [textPart(user.id, { sessionID: "root" })] }, | |
| { info: assistant, parts: [textPart(assistant.id, { sessionID: "root" })] }, | |
| ]), | |
| ) | |
| const messageApi = { | |
| list: () => { | |
| throw new Error("current message endpoint called") | |
| }, | |
| } as unknown as MessageApi | |
| const store = createServerSession(client, {} as SessionApi, messageApi, { | |
| protocol: Promise.resolve("v1"), | |
| }) | |
| store.remember(session("root")) | |
| await store.sync("root") | |
| expect(store.data.message.root.map((message) => message.id)).toEqual([user.id, assistant.id]) | |
| expect(store.data.session_message.root).toMatchObject([ | |
| { id: user.id, type: "user", text: "text" }, | |
| { id: assistant.id, type: "assistant" }, | |
| ]) | |
| const next = userMessage("message-3", { sessionID: "root" }) | |
| store.apply({ type: "message.updated", properties: { info: next } }) | |
| expect(store.data.session_message.root.map((message) => message.id)).toEqual([user.id, assistant.id, next.id]) | |
| store.apply({ type: "message.removed", properties: { sessionID: "root", messageID: next.id } }) | |
| expect(store.data.session_message.root.map((message) => message.id)).toEqual([user.id, assistant.id]) | |
| }) | |
| test("backfills an assistant-only initial page through its user root", async () => { | |
| const user = userMessage("message-1") | |
| const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] | |
| const client = rootMessageClient( | |
| [ | |
| response( | |
| assistants.map((info) => ({ info, parts: [] })), | |
| "older", | |
| ), | |
| ], | |
| [singleResponse(user)], | |
| ) | |
| const store = createServerSession(client) | |
| await store.sync("child") | |
| expect(client.requests).toEqual([{ sessionID: "child", limit: 20, before: undefined }]) | |
| expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: user.id }]) | |
| expect(store.data.message.child).toEqual([user, ...assistants]) | |
| expect(store.history.more("child")).toBe(true) | |
| }) | |
| test("keeps assistant history when its deleted parent cannot be backfilled", async () => { | |
| const missing = Promise.withResolvers<SingleMessageResponse>() | |
| const assistant = assistantMessage("message-2", "message-missing") | |
| const client = rootMessageClient([response([{ info: assistant, parts: [] }], "older")], [missing.promise]) | |
| const store = createServerSession(client) | |
| const loading = store.sync("child") | |
| await client.rootRequested(1) | |
| missing.reject(new Error("Message not found: message-missing", { cause: { status: 404 } })) | |
| await loading | |
| expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: "message-missing" }]) | |
| expect(store.data.message.child).toEqual([assistant]) | |
| expect(store.history.more("child")).toBe(true) | |
| }) | |
| test("drops a cached parent when a forced refresh confirms it was deleted", async () => { | |
| const missing = Promise.withResolvers<SingleMessageResponse>() | |
| const parent = userMessage("message-1") | |
| const part = textPart(parent.id) | |
| const assistant = assistantMessage("message-2", parent.id) | |
| const client = rootMessageClient( | |
| [ | |
| response([ | |
| { info: parent, parts: [part] }, | |
| { info: assistant, parts: [] }, | |
| ]), | |
| response([{ info: assistant, parts: [] }], "older"), | |
| ], | |
| [missing.promise], | |
| ) | |
| const store = createServerSession(client) | |
| await store.sync("child") | |
| const loading = store.sync("child", { force: true }) | |
| await client.rootRequested(1) | |
| missing.reject(new Error(`Message not found: ${parent.id}`, { cause: { status: 404 } })) | |
| await loading | |
| expect(store.data.message.child).toEqual([assistant]) | |
| expect(store.data.part[parent.id]).toBeUndefined() | |
| }) | |
| test("does not let an optimistic user suppress initial root backfill", async () => { | |
| const user = userMessage("message-1") | |
| const part = textPart(user.id) | |
| const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] | |
| const client = rootMessageClient( | |
| [ | |
| response( | |
| assistants.map((info) => ({ info, parts: [] })), | |
| "older", | |
| ), | |
| ], | |
| [singleResponse(user)], | |
| ) | |
| const store = createServerSession(client) | |
| store.optimistic.add({ sessionID: "child", message: user, parts: [part] }) | |
| await store.sync("child") | |
| store.optimistic.remove({ sessionID: "child", messageID: user.id }) | |
| expect(client.requests).toHaveLength(1) | |
| expect(client.rootRequests).toHaveLength(1) | |
| expect(store.data.message.child).toEqual([user, ...assistants]) | |
| }) | |
| test("backfills the parent of fetched assistants when another user is cached", async () => { | |
| const unrelated = userMessage("message-0", { time: { created: 0 } }) | |
| const user = userMessage("message-1") | |
| const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] | |
| const client = rootMessageClient( | |
| [ | |
| response([{ info: unrelated, parts: [] }]), | |
| response( | |
| assistants.map((info) => ({ info, parts: [] })), | |
| "older", | |
| ), | |
| ], | |
| [singleResponse(user)], | |
| ) | |
| const store = createServerSession(client) | |
| await store.sync("child") | |
| await store.sync("child", { force: true }) | |
| expect(client.requests).toHaveLength(2) | |
| expect(client.rootRequests).toHaveLength(1) | |
| expect(store.data.message.child).toEqual([unrelated, user, ...assistants]) | |
| }) | |
| test("preserves cached history between an injected parent and the page boundary", async () => { | |
| const user = userMessage("message-1") | |
| const cached = userMessage("message-3", { time: { created: 3 } }) | |
| const assistant = assistantMessage("message-4", user.id) | |
| const client = rootMessageClient( | |
| [response([{ info: cached, parts: [] }]), response([{ info: assistant, parts: [] }], "older")], | |
| [singleResponse(user)], | |
| ) | |
| const store = createServerSession(client) | |
| await store.sync("child") | |
| await store.sync("child", { force: true }) | |
| expect(store.data.message.child).toEqual([user, cached, assistant]) | |
| }) | |
| test("refreshes a cached parent omitted by an assistant-only replacement page", async () => { | |
| const stale = userMessage("message-1", { summary: { title: "stale", diffs: [] } }) | |
| const fresh = { ...stale, summary: { title: "fresh", diffs: [] } } | |
| const stalePart = textPart(stale.id, { text: "stale" }) | |
| const freshPart = { ...stalePart, text: "fresh" } | |
| const assistant = assistantMessage("message-2", stale.id) | |
| const client = rootMessageClient( | |
| [response([{ info: stale, parts: [stalePart] }]), response([{ info: assistant, parts: [] }], "older")], | |
| [singleResponse(fresh, [freshPart])], | |
| ) | |
| const store = createServerSession(client) | |
| await store.sync("child") | |
| await store.sync("child", { force: true }) | |
| expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: stale.id }]) | |
| expect(store.data.message.child).toEqual([fresh, assistant]) | |
| expect(store.data.part[stale.id]).toEqual([freshPart]) | |
| }) | |
| test("refreshes a confirmed optimistic parent while preserving pending parts", async () => { | |
| const stale = userMessage("message-1", { summary: { title: "stale", diffs: [] } }) | |
| const fresh = { ...stale, summary: { title: "fresh", diffs: [] } } | |
| const confirmed = textPart(stale.id, { id: "confirmed", text: "stale" }) | |
| const refreshed = { ...confirmed, text: "fresh" } | |
| const pending = textPart(stale.id, { id: "pending", text: "pending" }) | |
| const assistant = assistantMessage("message-2", stale.id) | |
| const client = rootMessageClient( | |
| [response([{ info: stale, parts: [confirmed] }]), response([{ info: assistant, parts: [] }], "older")], | |
| [singleResponse(fresh, [refreshed])], | |
| ) | |
| const store = createServerSession(client) | |
| store.optimistic.add({ sessionID: "child", message: stale, parts: [confirmed, pending] }) | |
| await store.sync("child") | |
| await store.sync("child", { force: true }) | |
| expect(client.rootRequests).toEqual([{ sessionID: "child", messageID: stale.id }]) | |
| expect(store.data.message.child).toEqual([fresh, assistant]) | |
| expect(store.data.part[stale.id]).toEqual([refreshed, pending]) | |
| }) | |
| test("uses a parent received by SSE during the replacement load", async () => { | |
| const pending = deferredResponse() | |
| const user = userMessage("message-1") | |
| const assistant = assistantMessage("message-2", user.id) | |
| const client = rootMessageClient([pending.promise], []) | |
| const store = createServerSession(client) | |
| const loading = store.sync("child") | |
| store.apply({ type: "message.updated", properties: { info: user } }) | |
| pending.resolve(response([{ info: assistant, parts: [] }], "older")) | |
| await loading | |
| expect(client.rootRequests).toEqual([]) | |
| expect(store.data.message.child).toEqual([user, assistant]) | |
| }) | |
| test("uses a successful retry over events received by a failed backfill attempt", async () => { | |
| const failed = deferredResponse() | |
| const user = userMessage("message-1") | |
| const live = { ...user, agent: "stale" } | |
| const assistants = [assistantMessage("message-2", user.id), assistantMessage("message-3", user.id)] | |
| const client = rootMessageClient( | |
| [ | |
| response( | |
| assistants.map((info) => ({ info, parts: [] })), | |
| "older", | |
| ), | |
| ], | |
| [failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)], | |
| ) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| const loading = store.sync("child") | |
| await client.rootRequested(1) | |
| store.apply({ type: "message.updated", properties: { info: live } }) | |
| failed.reject(new Error("retry")) | |
| await loading | |
| expect(client.requests).toHaveLength(1) | |
| expect(client.rootRequests).toHaveLength(2) | |
| expect(store.data.message.child).toEqual([user, ...assistants]) | |
| }) | |
| test("preserves newer-page events across a failed parent retry", async () => { | |
| const failed = deferredResponse() | |
| const user = userMessage("message-1") | |
| const assistant = assistantMessage("message-2", user.id) | |
| const live = { ...assistant, cost: 1 } | |
| const client = rootMessageClient( | |
| [response([{ info: assistant, parts: [] }], "older")], | |
| [failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)], | |
| ) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| const loading = store.sync("child") | |
| await client.rootRequested(1) | |
| store.apply({ type: "message.updated", properties: { info: live } }) | |
| failed.reject(new Error("retry")) | |
| await loading | |
| expect(store.data.message.child).toEqual([user, live]) | |
| }) | |
| test("preserves unrelated message events across a failed parent retry", async () => { | |
| const failed = deferredResponse() | |
| const user = userMessage("message-1") | |
| const assistant = assistantMessage("message-2", user.id) | |
| const live = userMessage("message-4", { time: { created: 4 } }) | |
| const client = rootMessageClient( | |
| [response([{ info: assistant, parts: [] }], "older")], | |
| [failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)], | |
| ) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| const loading = store.sync("child") | |
| await client.rootRequested(1) | |
| store.apply({ type: "message.updated", properties: { info: live } }) | |
| failed.reject(new Error("retry")) | |
| await loading | |
| expect(store.data.message.child).toEqual([user, assistant, live]) | |
| }) | |
| test("preserves newer-page part events across a failed parent retry", async () => { | |
| const failed = deferredResponse() | |
| const user = userMessage("message-1") | |
| const assistant = assistantMessage("message-2", user.id) | |
| const stale = textPart(assistant.id, { text: "stale" }) | |
| const live = { ...stale, text: "live" } | |
| const client = rootMessageClient( | |
| [response([{ info: assistant, parts: [stale] }], "older")], | |
| [failed.promise.then((result) => ({ data: result.data[0]! })), singleResponse(user)], | |
| ) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| const loading = store.sync("child") | |
| await client.rootRequested(1) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: live, time: 2 } }) | |
| failed.reject(new Error("retry")) | |
| await loading | |
| expect(store.data.part[assistant.id]).toEqual([live]) | |
| }) | |
| test("merges live events into the initial page", async () => { | |
| const pending = deferredResponse() | |
| const user = userMessage("message-1") | |
| const live = userMessage("message-2", { time: { created: 2 } }) | |
| const livePart = textPart(live.id, { text: "live" }) | |
| const store = createServerSession(messageClient(pending.promise)) | |
| const loading = store.sync("child") | |
| store.apply({ type: "message.updated", properties: { info: live } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: livePart, time: 2 } }) | |
| pending.resolve(response([{ info: user, parts: [] }])) | |
| await loading | |
| expect(store.data.message.child).toEqual([user, live]) | |
| expect(store.data.part[live.id]).toEqual([livePart]) | |
| }) | |
| test("preserves same-ID live updates over the initial page", async () => { | |
| const pending = deferredResponse() | |
| const fetched = userMessage("message") | |
| const fetchedPart = textPart(fetched.id, { text: "fetched" }) | |
| const live = { ...fetched, time: { created: 2 } } | |
| const livePart = { ...fetchedPart, text: "live" } | |
| const store = createServerSession(messageClient(pending.promise)) | |
| const loading = store.sync("child") | |
| store.apply({ type: "message.updated", properties: { info: live } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: livePart, time: 2 } }) | |
| pending.resolve(response([{ info: fetched, parts: [fetchedPart] }])) | |
| await loading | |
| expect(store.data.message.child).toEqual([live]) | |
| expect(store.data.part[live.id]).toEqual([livePart]) | |
| }) | |
| test("preserves removals received during the initial load", async () => { | |
| const pending = deferredResponse() | |
| const removed = userMessage("message-1") | |
| const kept = { ...removed, id: "message-2" } | |
| const part = textPart(kept.id, { text: "removed" }) | |
| const store = createServerSession(messageClient(pending.promise)) | |
| const loading = store.sync("child") | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: removed.id } }) | |
| store.apply({ | |
| type: "message.part.removed", | |
| properties: { sessionID: "child", messageID: kept.id, partID: part.id }, | |
| }) | |
| pending.resolve( | |
| response([ | |
| { info: removed, parts: [] }, | |
| { info: kept, parts: [part] }, | |
| ]), | |
| ) | |
| await loading | |
| expect(store.data.message.child).toEqual([kept]) | |
| expect(store.data.part[kept.id]).toBeUndefined() | |
| }) | |
| test("keeps removal tracking isolated across load generations", async () => { | |
| const firstResponse = deferredResponse() | |
| const secondResponse = deferredResponse() | |
| const message = userMessage("message") | |
| const store = createServerSession(messageClient(firstResponse.promise, secondResponse.promise)) | |
| const first = store.sync("child") | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| store.apply({ | |
| type: "session.deleted", | |
| properties: { sessionID: "child", info: session("child", "root") }, | |
| }) | |
| const second = store.sync("child") | |
| firstResponse.resolve(response()) | |
| await first | |
| secondResponse.resolve(response([{ info: message, parts: [] }])) | |
| await second | |
| expect(store.data.message.child).toEqual([message]) | |
| }) | |
| test("tracks removals in a replacement load generation", async () => { | |
| const firstResponse = deferredResponse() | |
| const secondResponse = deferredResponse() | |
| const message = userMessage("message") | |
| const store = createServerSession(messageClient(firstResponse.promise, secondResponse.promise)) | |
| const first = store.sync("child") | |
| store.apply({ | |
| type: "session.deleted", | |
| properties: { sessionID: "child", info: session("child", "root") }, | |
| }) | |
| const second = store.sync("child") | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| firstResponse.resolve(response()) | |
| await first | |
| secondResponse.resolve(response([{ info: message, parts: [] }])) | |
| await second | |
| expect(store.data.message.child).toEqual([]) | |
| }) | |
| test("preserves remove then re-add when a refresh omits the message", async () => { | |
| const pending = deferredResponse() | |
| const message = userMessage("message") | |
| const store = createServerSession(messageClient(response([{ info: message, parts: [] }]), pending.promise)) | |
| await store.sync("child") | |
| const refreshing = store.sync("child", { force: true }) | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| store.apply({ type: "message.updated", properties: { info: message } }) | |
| pending.resolve(response()) | |
| await refreshing | |
| expect(store.data.message.child).toEqual([message]) | |
| }) | |
| test("preserves a re-added message without restoring removed parts", async () => { | |
| const pending = deferredResponse() | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "stale" }) | |
| const store = createServerSession(messageClient(response([{ info: message, parts: [] }]), pending.promise)) | |
| await store.sync("child") | |
| const refreshing = store.sync("child", { force: true }) | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| store.apply({ type: "message.updated", properties: { info: message } }) | |
| pending.resolve(response([{ info: message, parts: [part] }])) | |
| await refreshing | |
| expect(store.data.message.child).toEqual([message]) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("preserves optimistic parts re-added after removal during a refresh", async () => { | |
| const pending = deferredResponse() | |
| const message = userMessage("message") | |
| const stale = textPart(message.id, { id: "stale", text: "stale" }) | |
| const part = textPart(message.id, { id: "optimistic", text: "optimistic" }) | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [] }]), pending.promise, response()), | |
| ) | |
| await store.sync("child") | |
| const refreshing = store.sync("child", { force: true }) | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| store.optimistic.add({ sessionID: "child", message, parts: [part] }) | |
| pending.resolve(response([{ info: message, parts: [stale] }])) | |
| await refreshing | |
| expect(store.data.message.child).toEqual([message]) | |
| expect(store.data.part[message.id]).toEqual([part]) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.message.child).toEqual([message]) | |
| expect(store.data.part[message.id]).toEqual([part]) | |
| }) | |
| test("drops stale event content omitted by a complete initial page", async () => { | |
| const stale = userMessage("stale") | |
| const store = createServerSession(messageClient(response())) | |
| store.apply({ type: "message.updated", properties: { info: stale } }) | |
| await store.sync("child") | |
| expect(store.data.message.child).toEqual([]) | |
| }) | |
| test("preserves event content outside an incomplete initial page", async () => { | |
| const live = userMessage("message-1") | |
| const fetched = userMessage("message-2", { time: { created: 2 } }) | |
| const store = createServerSession(messageClient(response([{ info: fetched, parts: [] }], "older"))) | |
| store.apply({ type: "message.updated", properties: { info: live } }) | |
| await store.sync("child") | |
| expect(store.data.message.child).toEqual([live, fetched]) | |
| }) | |
| test("does not restore removed optimistic content on refresh", async () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "removed" }) | |
| const kept = { ...message, id: "kept" } | |
| const keptPart = { ...part, id: "kept-part", messageID: kept.id } | |
| const store = createServerSession(messageClient(response([{ info: kept, parts: [] }]))) | |
| store.optimistic.add({ sessionID: "child", message, parts: [part] }) | |
| store.optimistic.add({ sessionID: "child", message: kept, parts: [keptPart] }) | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| store.apply({ | |
| type: "message.part.removed", | |
| properties: { sessionID: "child", messageID: kept.id, partID: keptPart.id }, | |
| }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.message.child).toEqual([kept]) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| expect(store.data.part[kept.id]).toBeUndefined() | |
| }) | |
| test("replaces confirmed optimistic content with the initial page", async () => { | |
| const optimistic = userMessage("message") | |
| const fetched = { ...optimistic, time: { created: 2 } } | |
| const store = createServerSession(messageClient(response([{ info: fetched, parts: [] }]))) | |
| store.optimistic.add({ sessionID: "child", message: optimistic, parts: [] }) | |
| await store.sync("child") | |
| expect(store.data.message.child).toEqual([fetched]) | |
| }) | |
| test("replaces a confirmed optimistic part with fetched content", async () => { | |
| const pending = deferredResponse() | |
| const message = userMessage("message") | |
| const optimistic = textPart(message.id, { text: "optimistic" }) | |
| const fetched = { ...optimistic, text: "fetched" } | |
| const store = createServerSession(messageClient(pending.promise)) | |
| const loading = store.sync("child") | |
| store.optimistic.add({ sessionID: "child", message, parts: [optimistic] }) | |
| pending.resolve(response([{ info: message, parts: [fetched] }])) | |
| await loading | |
| expect(store.data.part[message.id]).toEqual([fetched]) | |
| }) | |
| test("rolls back only unconfirmed optimistic parts", async () => { | |
| const pending = deferredResponse() | |
| const message = userMessage("message") | |
| const confirmed = textPart(message.id, { id: "confirmed", text: "confirmed" }) | |
| const pendingPart = textPart(message.id, { id: "pending", text: "pending" }) | |
| const store = createServerSession(messageClient(pending.promise)) | |
| const loading = store.sync("child") | |
| store.optimistic.add({ sessionID: "child", message, parts: [confirmed, pendingPart] }) | |
| pending.resolve(response([{ info: message, parts: [confirmed] }])) | |
| await loading | |
| store.optimistic.remove({ sessionID: "child", messageID: message.id }) | |
| expect(store.data.message.child).toEqual([message]) | |
| expect(store.data.part[message.id]).toEqual([confirmed]) | |
| }) | |
| test("updates confirmed optimistic parts from later pages", async () => { | |
| const message = userMessage("message") | |
| const confirmed = textPart(message.id, { id: "confirmed", text: "first" }) | |
| const updated = { ...confirmed, text: "updated" } | |
| const pendingPart = textPart(message.id, { id: "pending", text: "pending" }) | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [confirmed] }]), response([{ info: message, parts: [updated] }])), | |
| ) | |
| store.optimistic.add({ sessionID: "child", message, parts: [confirmed, pendingPart] }) | |
| await store.sync("child") | |
| await store.sync("child", { force: true }) | |
| store.optimistic.remove({ sessionID: "child", messageID: message.id }) | |
| expect(store.data.part[message.id]).toEqual([updated]) | |
| }) | |
| test("does not restore a confirmed optimistic part after its removal event", async () => { | |
| const message = userMessage("message") | |
| const confirmed = textPart(message.id, { id: "confirmed", text: "confirmed" }) | |
| const pendingPart = textPart(message.id, { id: "pending", text: "pending" }) | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [confirmed] }]), response([{ info: message, parts: [] }])), | |
| ) | |
| store.optimistic.add({ sessionID: "child", message, parts: [confirmed, pendingPart] }) | |
| await store.sync("child") | |
| store.apply({ | |
| type: "message.part.removed", | |
| properties: { sessionID: "child", messageID: message.id, partID: confirmed.id }, | |
| }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.part[message.id]).toEqual([pendingPart]) | |
| }) | |
| test("clears delta buffers when removing optimistic content", () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "optimistic" }) | |
| const store = setup({ child: session("child") }).store | |
| store.optimistic.add({ sessionID: "child", message, parts: [part] }) | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, | |
| }) | |
| store.optimistic.remove({ sessionID: "child", messageID: message.id }) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() | |
| }) | |
| test("does not remove content confirmed by a message event", () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const store = setup({ child: session("child") }).store | |
| store.optimistic.add({ sessionID: "child", message, parts: [part] }) | |
| store.apply({ type: "message.updated", properties: { sessionID: "child", info: message } }) | |
| store.optimistic.remove({ sessionID: "child", messageID: message.id }) | |
| expect(store.data.message.child).toEqual([message]) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("does not remove parts confirmed by part events", () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const store = setup({ child: session("child") }).store | |
| store.optimistic.add({ sessionID: "child", message, parts: [part] }) | |
| store.apply({ type: "message.updated", properties: { sessionID: "child", info: message } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) | |
| store.optimistic.remove({ sessionID: "child", messageID: message.id }) | |
| expect(store.data.message.child).toEqual([message]) | |
| expect(store.data.part[message.id]).toEqual([part]) | |
| }) | |
| test("treats a part event as confirmation when it precedes the message event", () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const store = setup({ child: session("child") }).store | |
| store.optimistic.add({ sessionID: "child", message, parts: [part] }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) | |
| store.optimistic.remove({ sessionID: "child", messageID: message.id }) | |
| expect(store.data.message.child).toEqual([message]) | |
| expect(store.data.part[message.id]).toEqual([part]) | |
| }) | |
| test("clears stale parts when the initial page has none", async () => { | |
| const pending = deferredResponse() | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "stale" }) | |
| const store = createServerSession(messageClient(pending.promise)) | |
| store.apply({ type: "message.updated", properties: { info: message } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 1 } }) | |
| const loading = store.sync("child") | |
| pending.resolve(response([{ info: message, parts: [] }])) | |
| await loading | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("clears delta buffers for parts omitted by the initial page", async () => { | |
| const pending = deferredResponse() | |
| const message = userMessage("message") | |
| const kept = textPart(message.id, { id: "part-1", text: "kept" }) | |
| const removed: Part = { ...kept, id: "part-2", text: "removed" } | |
| const store = createServerSession(messageClient(pending.promise)) | |
| store.apply({ type: "message.updated", properties: { info: message } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: kept, time: 1 } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: removed, time: 1 } }) | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: removed.id, field: "text", delta: " delta" }, | |
| }) | |
| const loading = store.sync("child") | |
| pending.resolve(response([{ info: message, parts: [kept] }])) | |
| await loading | |
| expect(store.data.part[message.id]).toEqual([kept]) | |
| expect(store.data.part_text_accum_delta[removed.id]).toBeUndefined() | |
| }) | |
| test("clears a stale delta buffer when a refresh replaces its part", async () => { | |
| const message = userMessage("message") | |
| const stale = textPart(message.id, { text: "stale" }) | |
| const fetched = { ...stale, text: "fetched" } | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [stale] }]), response([{ info: message, parts: [fetched] }])), | |
| ) | |
| await store.sync("child") | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: stale.id, field: "text", delta: " delta" }, | |
| }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.part[message.id]).toEqual([fetched]) | |
| expect(store.data.part_text_accum_delta[stale.id]).toBeUndefined() | |
| }) | |
| test("preserves a non-durable delta received before refresh", async () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "stale" }) | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [{ ...part }] }])), | |
| ) | |
| await store.sync("child") | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, | |
| }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.part[message.id]).toEqual([{ ...part, text: "stale delta" }]) | |
| expect(store.data.part_text_accum_delta[part.id]).toBe("stale delta") | |
| }) | |
| test("accepts fetched text that intentionally replaces an accumulated prefix", async () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "abc" }) | |
| const fetched = { ...part, text: "ab" } | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [fetched] }])), | |
| ) | |
| await store.sync("child") | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: "def" }, | |
| }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.part[message.id]).toEqual([fetched]) | |
| expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() | |
| }) | |
| test("preserves an unpersisted delta suffix after partial server catch-up", async () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "a" }) | |
| const fetched = { ...part, text: "ab" } | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [fetched] }])), | |
| ) | |
| await store.sync("child") | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: "bc" }, | |
| }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.part[message.id]).toEqual([{ ...part, text: "abc" }]) | |
| expect(store.data.part_text_accum_delta[part.id]).toBe("abc") | |
| }) | |
| test("clears delta state after exact server catch-up", async () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "a" }) | |
| const fetched = { ...part, text: "ab" } | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [fetched] }])), | |
| ) | |
| await store.sync("child") | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: "b" }, | |
| }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.part[message.id]).toEqual([fetched]) | |
| expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() | |
| }) | |
| test("uses the successful retry response over events from a failed attempt", async () => { | |
| const failed = Promise.withResolvers<MessageResponse>() | |
| const retried = Promise.withResolvers<MessageResponse>() | |
| const message = userMessage("message") | |
| const stale = textPart(message.id, { text: "stale" }) | |
| const intermediate = { ...stale, text: "intermediate" } | |
| const fetched = { ...stale, text: "fetched" } | |
| const client = messageClient(failed.promise, retried.promise) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| store.apply({ type: "message.updated", properties: { info: message } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: stale, time: 1 } }) | |
| const loading = store.sync("child") | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: intermediate, time: 2 } }) | |
| failed.reject(new Error("failed to fetch")) | |
| await client.requested(2) | |
| retried.resolve(response([{ info: message, parts: [fetched] }])) | |
| await loading | |
| expect(store.data.part[message.id]).toEqual([fetched]) | |
| }) | |
| test("preserves non-durable deltas across message retries", async () => { | |
| const failed = Promise.withResolvers<MessageResponse>() | |
| const retried = Promise.withResolvers<MessageResponse>() | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "stale" }) | |
| const client = messageClient(failed.promise, retried.promise) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| store.apply({ type: "message.updated", properties: { info: message } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 1 } }) | |
| const loading = store.sync("child") | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, | |
| }) | |
| failed.reject(new Error("failed to fetch")) | |
| await client.requested(2) | |
| retried.resolve(response([{ info: message, parts: [part] }])) | |
| await loading | |
| expect(store.data.part[message.id]).toEqual([{ ...part, text: "stale delta" }]) | |
| }) | |
| test("preserves part removals across message retries", async () => { | |
| const failed = Promise.withResolvers<MessageResponse>() | |
| const retried = Promise.withResolvers<MessageResponse>() | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const client = messageClient(response([{ info: message, parts: [part] }]), failed.promise, retried.promise) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| await store.sync("child") | |
| const loading = store.sync("child", { force: true }) | |
| store.apply({ | |
| type: "message.part.removed", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id }, | |
| }) | |
| failed.reject(new Error("failed to fetch")) | |
| await client.requested(3) | |
| retried.resolve(response([{ info: message, parts: [part] }])) | |
| await loading | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("preserves message removals across message retries", async () => { | |
| const failed = Promise.withResolvers<MessageResponse>() | |
| const retried = Promise.withResolvers<MessageResponse>() | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const client = messageClient(response([{ info: message, parts: [part] }]), failed.promise, retried.promise) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| await store.sync("child") | |
| const loading = store.sync("child", { force: true }) | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| failed.reject(new Error("failed to fetch")) | |
| await client.requested(3) | |
| retried.resolve(response([{ info: message, parts: [part] }])) | |
| await loading | |
| expect(store.data.message.child).toEqual([]) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("preserves optimistic re-adds across message retries", async () => { | |
| const failed = Promise.withResolvers<MessageResponse>() | |
| const retried = Promise.withResolvers<MessageResponse>() | |
| const message = userMessage("message") | |
| const stale = textPart(message.id, { id: "stale", text: "stale" }) | |
| const optimistic = textPart(message.id, { id: "optimistic", text: "optimistic" }) | |
| const client = messageClient(response([{ info: message, parts: [stale] }]), failed.promise, retried.promise) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| await store.sync("child") | |
| const loading = store.sync("child", { force: true }) | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| store.optimistic.add({ sessionID: "child", message, parts: [optimistic] }) | |
| failed.reject(new Error("failed to fetch")) | |
| await client.requested(3) | |
| retried.resolve(response([{ info: message, parts: [stale] }])) | |
| await loading | |
| expect(store.data.message.child).toEqual([message]) | |
| expect(store.data.part[message.id]).toEqual([optimistic]) | |
| }) | |
| test("accepts part omission from a successful retry after an earlier delta", async () => { | |
| const failed = Promise.withResolvers<MessageResponse>() | |
| const retried = Promise.withResolvers<MessageResponse>() | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const client = messageClient(response([{ info: message, parts: [part] }]), failed.promise, retried.promise) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| await store.sync("child") | |
| const loading = store.sync("child", { force: true }) | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, | |
| }) | |
| failed.reject(new Error("failed to fetch")) | |
| await client.requested(3) | |
| retried.resolve(response([{ info: message, parts: [] }])) | |
| await loading | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() | |
| }) | |
| test("clears load-owned orphan parts when all retries fail", async () => { | |
| const first = Promise.withResolvers<MessageResponse>() | |
| const second = Promise.withResolvers<MessageResponse>() | |
| const third = Promise.withResolvers<MessageResponse>() | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const client = messageClient(first.promise, second.promise, third.promise) | |
| const store = createServerSession(client, { retry: retryImmediately }) | |
| const loading = store.sync("child").catch((error) => error) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) | |
| first.reject(new Error("failed to fetch")) | |
| await client.requested(2) | |
| second.reject(new Error("failed to fetch")) | |
| await client.requested(3) | |
| third.reject(new Error("failed to fetch")) | |
| await loading | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("preserves live updates during a forced refresh", async () => { | |
| const pending = deferredResponse() | |
| const stale = userMessage("message") | |
| const stalePart = textPart(stale.id, { text: "stale" }) | |
| const store = createServerSession(messageClient(response([{ info: stale, parts: [stalePart] }]), pending.promise)) | |
| await store.sync("child") | |
| const refreshing = store.sync("child", { force: true }) | |
| const live = { ...stale, time: { created: 2 } } | |
| store.apply({ type: "message.updated", properties: { info: live } }) | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: stale.id, partID: stalePart.id, field: "text", delta: " live" }, | |
| }) | |
| pending.resolve(response([{ info: stale, parts: [stalePart] }])) | |
| await refreshing | |
| expect(store.data.message.child).toEqual([live]) | |
| expect(store.data.part[stale.id]).toEqual([{ ...stalePart, text: "stale live" }]) | |
| }) | |
| test("keeps fetched message metadata when only a part changes", async () => { | |
| const pending = deferredResponse() | |
| const stale = userMessage("message") | |
| const fetched = { ...stale, time: { created: 2 } } | |
| const part = textPart(stale.id, { text: "stale" }) | |
| const store = createServerSession(messageClient(response([{ info: stale, parts: [part] }]), pending.promise)) | |
| await store.sync("child") | |
| const refreshing = store.sync("child", { force: true }) | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: stale.id, partID: part.id, field: "text", delta: " live" }, | |
| }) | |
| pending.resolve(response([{ info: fetched, parts: [part] }])) | |
| await refreshing | |
| expect(store.data.message.child).toEqual([fetched]) | |
| expect(store.data.part[stale.id]).toEqual([{ ...part, text: "stale live" }]) | |
| }) | |
| test("preserves a part update when a forced refresh omits its message", async () => { | |
| const pending = deferredResponse() | |
| const message = userMessage("message") | |
| const stale = textPart(message.id, { text: "stale" }) | |
| const live = { ...stale, text: "live" } | |
| const store = createServerSession(messageClient(response([{ info: message, parts: [stale] }]), pending.promise)) | |
| await store.sync("child") | |
| const refreshing = store.sync("child", { force: true }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: live, time: 2 } }) | |
| pending.resolve(response()) | |
| await refreshing | |
| expect(store.data.message.child).toEqual([message]) | |
| expect(store.data.part[message.id]).toEqual([live]) | |
| }) | |
| test("ignores a late part update after its message is removed", async () => { | |
| const pending = deferredResponse() | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const store = createServerSession(messageClient(pending.promise)) | |
| const loading = store.sync("child") | |
| store.apply({ type: "message.updated", properties: { info: message } }) | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) | |
| pending.resolve(response([{ info: message, parts: [part] }])) | |
| await loading | |
| expect(store.data.message.child).toEqual([]) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("ignores a late part update after a completed message removal", () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const store = setup({ child: session("child") }).store | |
| store.apply({ type: "message.updated", properties: { info: message } }) | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("does not restore a completed message removal from a stale refresh", async () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [part] }])), | |
| ) | |
| await store.sync("child") | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: message.id } }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.message.child).toEqual([]) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("does not restore a completed part removal from a stale refresh", async () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id) | |
| const store = createServerSession( | |
| messageClient(response([{ info: message, parts: [part] }]), response([{ info: message, parts: [part] }])), | |
| ) | |
| await store.sync("child") | |
| store.apply({ | |
| type: "message.part.removed", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id }, | |
| }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| }) | |
| test("does not cache skipped optimistic parts", () => { | |
| const message = userMessage("message") | |
| const part = { id: "part", sessionID: "child", messageID: message.id, type: "step-start" as const } | |
| const store = setup({ child: session("child") }).store | |
| store.optimistic.add({ sessionID: "child", message, parts: [part] }) | |
| expect(store.data.part[message.id]).toEqual([]) | |
| }) | |
| test("clears stale delta buffers when replacing optimistic parts", () => { | |
| const message = userMessage("message") | |
| const stale = textPart(message.id, { id: "stale", text: "stale" }) | |
| const optimistic = textPart(message.id, { id: "optimistic", text: "optimistic" }) | |
| const store = setup({ child: session("child") }).store | |
| store.optimistic.add({ sessionID: "child", message, parts: [stale] }) | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: stale.id, field: "text", delta: " delta" }, | |
| }) | |
| store.optimistic.add({ sessionID: "child", message, parts: [optimistic] }) | |
| expect(store.data.part_text_accum_delta[stale.id]).toBeUndefined() | |
| expect(store.data.part_text_accum_delta[optimistic.id]).toBeUndefined() | |
| }) | |
| test("preserves removals during history prepend", async () => { | |
| const pending = deferredResponse() | |
| const latest = userMessage("message-2", { time: { created: 2 } }) | |
| const older = { ...latest, id: "message-1", time: { created: 1 } } | |
| const store = createServerSession(messageClient(response([{ info: latest, parts: [] }], "older"), pending.promise)) | |
| await store.sync("child") | |
| const loading = store.history.loadMore("child") | |
| store.apply({ type: "message.removed", properties: { sessionID: "child", messageID: older.id } }) | |
| pending.resolve(response([{ info: older, parts: [] }])) | |
| await loading | |
| expect(store.data.message.child).toEqual([latest]) | |
| }) | |
| test("does not scan cached messages for user roots during history prepend", async () => { | |
| const guard = { active: false } | |
| const latest = new Proxy(userMessage("message-2", { time: { created: 2 } }), { | |
| get(target, property, receiver) { | |
| if (guard.active && property === "role") throw new Error("cached role accessed") | |
| return Reflect.get(target, property, receiver) | |
| }, | |
| }) | |
| const older = userMessage("message-1") | |
| const store = createServerSession( | |
| messageClient(response([{ info: latest, parts: [] }], "older"), response([{ info: older, parts: [] }])), | |
| ) | |
| await store.sync("child") | |
| guard.active = true | |
| await store.history.loadMore("child") | |
| expect(store.data.message.child).toEqual([older, latest]) | |
| }) | |
| test("preserves loaded history during an incomplete refresh", async () => { | |
| const older = userMessage("message-1") | |
| const latest = userMessage("message-2", { time: { created: 2 } }) | |
| const fresh = userMessage("message-3", { time: { created: 3 } }) | |
| const store = createServerSession( | |
| messageClient( | |
| response( | |
| [ | |
| { info: older, parts: [] }, | |
| { info: latest, parts: [] }, | |
| ], | |
| "older", | |
| ), | |
| response( | |
| [ | |
| { info: latest, parts: [] }, | |
| { info: fresh, parts: [] }, | |
| ], | |
| "older", | |
| ), | |
| ), | |
| ) | |
| await store.sync("child") | |
| await store.sync("child", { force: true }) | |
| expect(store.data.message.child).toEqual([older, latest, fresh]) | |
| }) | |
| test("drops stale recent messages omitted by an incomplete refresh", async () => { | |
| const third = userMessage("message-3", { time: { created: 3 } }) | |
| const fourth = userMessage("message-4", { time: { created: 4 } }) | |
| const stale = userMessage("message-5", { time: { created: 5 } }) | |
| const store = createServerSession( | |
| messageClient( | |
| response( | |
| [ | |
| { info: fourth, parts: [] }, | |
| { info: stale, parts: [] }, | |
| ], | |
| "older", | |
| ), | |
| response( | |
| [ | |
| { info: third, parts: [] }, | |
| { info: fourth, parts: [] }, | |
| ], | |
| "older", | |
| ), | |
| ), | |
| ) | |
| await store.sync("child") | |
| await store.sync("child", { force: true }) | |
| expect(store.data.message.child).toEqual([third, fourth]) | |
| }) | |
| test("uses message creation time for incomplete refresh boundaries", async () => { | |
| const older = userMessage("msg_z", { time: { created: 1 } }) | |
| const boundary = userMessage("msg_m", { time: { created: 2 } }) | |
| const stale = userMessage("msg_a", { time: { created: 3 } }) | |
| const store = createServerSession( | |
| messageClient( | |
| response( | |
| [ | |
| { info: older, parts: [] }, | |
| { info: stale, parts: [] }, | |
| ], | |
| "older", | |
| ), | |
| response([{ info: boundary, parts: [] }], "older"), | |
| ), | |
| ) | |
| await store.sync("child") | |
| await store.sync("child", { force: true }) | |
| expect(store.data.message.child).toEqual([older, boundary]) | |
| }) | |
| test("preserves a part update for a message being loaded from history", async () => { | |
| const pending = deferredResponse() | |
| const latest = userMessage("message-2", { time: { created: 2 } }) | |
| const older = userMessage("message-1") | |
| const stale = textPart(older.id, { text: "stale" }) | |
| const live = { ...stale, text: "live" } | |
| const store = createServerSession(messageClient(response([{ info: latest, parts: [] }], "older"), pending.promise)) | |
| await store.sync("child") | |
| const loading = store.history.loadMore("child") | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part: live, time: 2 } }) | |
| pending.resolve(response([{ info: older, parts: [stale] }])) | |
| await loading | |
| expect(store.data.part[older.id]).toEqual([live]) | |
| }) | |
| test("does not clear newer orphan parts after terminal history prepend", async () => { | |
| const pending = deferredResponse() | |
| const latest = userMessage("message-2", { time: { created: 2 } }) | |
| const older = userMessage("message-1") | |
| const newer = userMessage("message-3", { time: { created: 3 } }) | |
| const part = textPart(newer.id, { text: "live" }) | |
| const store = createServerSession(messageClient(response([{ info: latest, parts: [] }], "older"), pending.promise)) | |
| await store.sync("child") | |
| const loading = store.history.loadMore("child") | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 3 } }) | |
| pending.resolve(response([{ info: older, parts: [] }])) | |
| await loading | |
| store.apply({ type: "message.updated", properties: { sessionID: "child", info: newer } }) | |
| expect(store.data.part[newer.id]).toEqual([part]) | |
| }) | |
| test("accepts an authoritative history part after an earlier unknown-parent update", async () => { | |
| const pending = deferredResponse() | |
| const history = deferredResponse() | |
| const latest = userMessage("message-2", { time: { created: 2 } }) | |
| const older = userMessage("message-1") | |
| const part = textPart(older.id, { text: "live" }) | |
| const store = createServerSession(messageClient(pending.promise, history.promise)) | |
| const loading = store.sync("child") | |
| store.apply({ type: "message.part.updated", properties: { sessionID: "child", part, time: 2 } }) | |
| pending.resolve(response([{ info: latest, parts: [] }], "older")) | |
| await loading | |
| expect(store.data.part[older.id]).toEqual([part]) | |
| const loadingHistory = store.history.loadMore("child") | |
| history.resolve(response([{ info: older, parts: [{ ...part, text: "stale" }] }])) | |
| await loadingHistory | |
| expect(store.data.part[older.id]).toEqual([{ ...part, text: "stale" }]) | |
| }) | |
| test("preserves an unknown-parent part removal across pages", async () => { | |
| const initial = deferredResponse() | |
| const history = deferredResponse() | |
| const latest = userMessage("message-2", { time: { created: 2 } }) | |
| const older = userMessage("message-1") | |
| const part = textPart(older.id) | |
| const store = createServerSession(messageClient(initial.promise, history.promise)) | |
| const loading = store.sync("child") | |
| store.apply({ | |
| type: "message.part.removed", | |
| properties: { sessionID: "child", messageID: older.id, partID: part.id }, | |
| }) | |
| initial.resolve(response([{ info: latest, parts: [] }], "older")) | |
| await loading | |
| const loadingHistory = store.history.loadMore("child") | |
| history.resolve(response([{ info: older, parts: [part] }])) | |
| await loadingHistory | |
| expect(store.data.part[older.id]).toBeUndefined() | |
| }) | |
| test("clears orphaned parts when a refresh drops a message", async () => { | |
| const message = userMessage("message") | |
| const part = textPart(message.id, { text: "stale" }) | |
| const store = createServerSession(messageClient(response([{ info: message, parts: [part] }]), response())) | |
| await store.sync("child") | |
| store.apply({ | |
| type: "message.part.delta", | |
| properties: { sessionID: "child", messageID: message.id, partID: part.id, field: "text", delta: " delta" }, | |
| }) | |
| await store.sync("child", { force: true }) | |
| expect(store.data.message.child).toEqual([]) | |
| expect(store.data.part[message.id]).toBeUndefined() | |
| expect(store.data.part_text_accum_delta[part.id]).toBeUndefined() | |
| }) | |
| test("applies events without a directory store", () => { | |
| const ctx = setup({}) | |
| ctx.store.apply({ type: "session.created", properties: { sessionID: "root", info: session("root") } }) | |
| ctx.store.apply({ type: "session.status", properties: { sessionID: "root", status: { type: "busy" } } }) | |
| expect(ctx.store.get("root")?.directory).toBe("/repo") | |
| expect(ctx.store.data.session_working("root")).toBe(true) | |
| expect(ctx.get).toEqual([]) | |
| }) | |
| test("preserves pinned session content under server-wide cache pressure", () => { | |
| const ctx = setup({}) | |
| ctx.store.pin("active") | |
| ctx.store.optimistic.add({ | |
| sessionID: "active", | |
| message: { | |
| id: "message", | |
| sessionID: "active", | |
| role: "assistant", | |
| time: { created: 1 }, | |
| parentID: "parent", | |
| modelID: "model", | |
| providerID: "provider", | |
| mode: "build", | |
| agent: "agent", | |
| path: { cwd: "/repo", root: "/repo" }, | |
| cost: 0, | |
| tokens: { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }, | |
| }, | |
| parts: [], | |
| }) | |
| for (let index = 0; index < 50; index++) { | |
| ctx.store.remember(session(`session-${index}`)) | |
| ctx.store.apply({ | |
| type: "session.status", | |
| properties: { sessionID: `session-${index}`, status: { type: "idle" } }, | |
| }) | |
| } | |
| expect(ctx.store.data.message.active?.map((message) => message.id)).toEqual(["message"]) | |
| expect(ctx.store.data.session_status["session-0"]).toBeUndefined() | |
| }) | |
| }) | |