From 849f7e9fcb99bbcd7dda5c747bda89e4e8a27961 Mon Sep 17 00:00:00 2001 From: Jace Hwang <0xd669@gmail.com> Date: Fri, 13 Mar 2026 14:06:51 +0900 Subject: [PATCH] Fix public Thread type serialization declaration --- .changeset/green-tables-lie.md | 5 +++ packages/chat/src/types.test.ts | 70 +++++++++++++++++++++++++++++++++ packages/chat/src/types.ts | 7 ++++ 3 files changed, 82 insertions(+) create mode 100644 .changeset/green-tables-lie.md create mode 100644 packages/chat/src/types.test.ts diff --git a/.changeset/green-tables-lie.md b/.changeset/green-tables-lie.md new file mode 100644 index 00000000..1b9c5842 --- /dev/null +++ b/.changeset/green-tables-lie.md @@ -0,0 +1,5 @@ +--- +"chat": patch +--- + +Expose thread.toJSON() on the public Thread type so generated declarations match the runtime API and docs. diff --git a/packages/chat/src/types.test.ts b/packages/chat/src/types.test.ts new file mode 100644 index 00000000..78b0d255 --- /dev/null +++ b/packages/chat/src/types.test.ts @@ -0,0 +1,70 @@ +import { execSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; + +function createTempProject(source: string): string { + const tempDir = mkdtempSync(join(tmpdir(), "chat-public-types-")); + + const tsconfig = { + compilerOptions: { + target: "ES2022", + module: "ESNext", + moduleResolution: "bundler", + strict: true, + skipLibCheck: true, + noEmit: true, + typeRoots: [join(import.meta.dirname, "../../../node_modules/@types")], + paths: { + chat: [join(import.meta.dirname, "index.ts")], + }, + }, + include: [join(tempDir, "index.ts")], + }; + + writeFileSync( + join(tempDir, "tsconfig.json"), + JSON.stringify(tsconfig, null, 2) + ); + writeFileSync(join(tempDir, "index.ts"), source); + + return tempDir; +} + +describe("chat public types", () => { + it("exposes Thread.toJSON() to consumers", () => { + const tempDir = createTempProject(` +import type { SerializedThread, Thread } from "chat"; + +declare const thread: Thread<{ count: number }>; + +const serialized: SerializedThread = thread.toJSON(); + +serialized satisfies SerializedThread; +`); + + try { + execSync(`pnpm exec tsc --project ${tempDir}/tsconfig.json --noEmit`, { + cwd: join(import.meta.dirname, ".."), + encoding: "utf-8", + stdio: "pipe", + }); + } catch (error) { + const execError = error as { stdout?: string; stderr?: string }; + const output = execError.stdout || execError.stderr || String(error); + rmSync(tempDir, { recursive: true, force: true }); + + expect.fail( + "Consumer Thread.toJSON() type-check failed:\n\n" + + output + + "\n\nSnippet tested:\n" + + 'import type { SerializedThread, Thread } from "chat";\n' + + "declare const thread: Thread<{ count: number }>;\n" + + "const serialized: SerializedThread = thread.toJSON();\n" + ); + } + + rmSync(tempDir, { recursive: true, force: true }); + }); +}); diff --git a/packages/chat/src/types.ts b/packages/chat/src/types.ts index 3f95dd40..f886e2e4 100644 --- a/packages/chat/src/types.ts +++ b/packages/chat/src/types.ts @@ -8,6 +8,7 @@ import type { ChatElement } from "./jsx-runtime"; import type { Logger, LogLevel } from "./logger"; import type { Message } from "./message"; import type { ModalElement } from "./modals"; +import type { SerializedThread } from "./thread"; // ============================================================================= // Re-exports from extracted modules @@ -940,6 +941,12 @@ export interface Thread, TRawMessage = unknown> */ subscribe(): Promise; + /** + * Serialize the thread to a plain JSON object. + * Use this to persist thread context or pass it across workflow boundaries. + */ + toJSON(): SerializedThread; + /** * Unsubscribe from this thread. *