Skip to content

feat: split persistence into state (middleware) + delivery (transport/durable-streams) durability#923

Merged
AlemTuzlak merged 9 commits into
feat/persistencefrom
feat/persistence-v2
Jul 9, 2026
Merged

feat: split persistence into state (middleware) + delivery (transport/durable-streams) durability#923
AlemTuzlak merged 9 commits into
feat/persistencefrom
feat/persistence-v2

Conversation

@AlemTuzlak

Copy link
Copy Markdown
Contributor

Summary

Reworks the persistence feature (#785) by splitting the two concerns that were fused under one "persistence" banner, per review feedback:

  • State durability → middleware layer. withChatPersistence / withGenerationPersistence persist thread messages, run status, interrupts, and generation artifacts — nothing else.
  • Delivery durability → transport layer. A client can disconnect / reload / open a second tab and still receive the full ordered stream. This is now a thin, pluggable StreamDurability sink on the transport helpers, backed by the durable streams protocol. We own zero delivery-event storage — the home-grown event-log/cursor/resume subsystem (a database wearing a transport costume, whose DurableRunStream was a dead interface) is deleted.

This is a branch-targeted rework of the unreleased #785, so removed APIs are deleted outright (no deprecation shims).

Delivery durability (transport)

  • toServerSentEvents(stream, { durability })durability is a StreamDurability adapter; same option on every transport helper. Transport stays the user's choice; durable streams is the backing.
  • Adapters: memoryStream (zero-infra dev default) and @tanstack/ai-durable-stream's durableStream(request, { server }) (durable-streams protocol) — new package.
  • chat() is lazy: on a resume request the helper replays from the log and never iterates the stream, so the provider is never re-invoked. Batching lives in the helper.
  • Client uses plain resumable SSE — native Last-Event-ID, zero cursor machinery. chat() loses its cursor param; StreamChunk loses its in-band cursor.
  • Deleted: publicEvents/internalEvents stores + tables, DurableRunStream, cursor.ts, core resume.ts/maybeResume/ResumeSource, resume-source.ts, and the client cursor/auto-resume machinery.

State durability (middleware + backends)

  • withChatPersistence / withGenerationPersistence — state-only, sharing one store contract + run-id space (a future Gemini-nested-generation unification is deferred to its own RFC).
  • Three entry points, one AIPersistence contract: sqlPersistence({ dialect:'sqlite', url }) (batteries), drizzlePersistence(db) (BYO), prismaPersistence(prisma) (BYO). Each ORM owns its own migrations natively — no more hand-authored DDL.
  • Deleted the SQL sprawl: ai-persistence-sql + -sqlite/-postgres/-cloudflare packages, the SqlDriver abstraction, and the hand-rolled migration/CLI code — folded into the drizzle + prisma backends.
  • A shared conformance suite (@tanstack/ai-persistence/testkit) runs against memory + drizzle + prisma to keep the backends honest and the drizzle↔prisma schemas in sync (enforced by a coupling entry).

Review

Ran a full CR loop to convergence (2 fix rounds). Highlights caught and fixed: a durableStream default-batch exactly-once violation on mid-batch reconnect (now per-chunk offset tagging), an offset-codec now-sentinel throw, memoryStream in-flight-join truncation + a join-hang, thrown-provider-error not persisted, a drizzle/prisma blob-prefix wildcard bug, sqlPersistence dialect type-honesty, and removal of a mis-firing generation resume surface.

Validation

  • test:sherif, test:knip, doc-link check, and test:kiira (808 snippets) green.
  • test:eslint/test:types/test:lib/test:build green across all 12 affected library packages; test:types/build green across all example apps + testing/**.
  • Conformance suite green for memory + drizzle + prisma; delivery e2e (disconnect→reconnect exactly-once, second-tab join) green.

Known follow-ups (not blocking; tracked as notes)

  • Angular injectChat doesn't expose the resumeState/pendingInterrupts/resumeInterrupts surface the other 5 frameworks do — pre-existing, untouched by this PR.
  • The auto-injected in-memory LockStore satisfies validatePersistenceFeatures(['locks']) while being process-local — documented; a distinguishable dev-default marker could make validation warn.
  • The durable-streams "own-offset == Stream-Next-Offset, strictly-after read" assumption is faithfully modeled by the test fakes but warrants a one-time confirmation against a live durable-streams service.
  • No framework-level joinRun affordance yet (transparent SSE auto-resume covers the common case); would need a ChatClient.joinRun.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: db2f9057-f550-435e-b1c3-aba52565f1f4

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/persistence-v2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

socket-security Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedprisma@​6.19.3761009896100
Added@​prisma/​client@​6.19.3941008598100
Addeddrizzle-orm@​0.45.2881008897100
Addeddrizzle-kit@​0.31.10991009698100

View full report

@socket-security

socket-security Bot commented Jul 9, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm drizzle-orm is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: packages/ai-persistence-drizzle/package.jsonnpm/drizzle-orm@0.45.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/drizzle-orm@0.45.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm effect is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: pnpm-lock.yamlnpm/prisma@6.19.3npm/effect@3.21.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/effect@3.21.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Deprecated by its maintainer: npm @esbuild-kit/core-utils

Reason: Merged into tsx: https://tsx.is

From: pnpm-lock.yamlnpm/drizzle-kit@0.31.10npm/@esbuild-kit/core-utils@3.3.2

ℹ Read more on: This package | This alert | What is a deprecated package?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Research the state of the package and determine if there are non-deprecated versions that can be used, or if it should be replaced with a new, supported solution.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@esbuild-kit/core-utils@3.3.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn Medium
Deprecated by its maintainer: npm @esbuild-kit/esm-loader

Reason: Merged into tsx: https://tsx.is

From: pnpm-lock.yamlnpm/drizzle-kit@0.31.10npm/@esbuild-kit/esm-loader@2.6.5

ℹ Read more on: This package | This alert | What is a deprecated package?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Research the state of the package and determine if there are non-deprecated versions that can be used, or if it should be replaced with a new, supported solution.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@esbuild-kit/esm-loader@2.6.5. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

14 package(s) bumped directly, 35 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-angular 0.2.3 → 1.0.0 Changeset
@tanstack/ai-durable-stream 0.1.0 → 1.0.0 Changeset
@tanstack/ai-gemini 0.19.1 → 1.0.0 Changeset
@tanstack/ai-openai 0.16.0 → 1.0.0 Changeset
@tanstack/ai-persistence 0.1.0 → 1.0.0 Changeset
@tanstack/ai-persistence-drizzle 0.1.0 → 1.0.0 Changeset
@tanstack/ai-persistence-prisma 0.1.0 → 1.0.0 Changeset
@tanstack/ai-preact 0.10.3 → 1.0.0 Changeset
@tanstack/ai-react 0.16.4 → 1.0.0 Changeset
@tanstack/ai-solid 0.14.3 → 1.0.0 Changeset
@tanstack/ai-svelte 0.14.3 → 1.0.0 Changeset
@tanstack/ai-vue 0.14.3 → 1.0.0 Changeset
@tanstack/ai-acp 0.2.1 → 1.0.0 Dependent
@tanstack/ai-anthropic 0.16.1 → 1.0.0 Dependent
@tanstack/ai-bedrock 0.1.2 → 1.0.0 Dependent
@tanstack/ai-claude-code 0.2.1 → 1.0.0 Dependent
@tanstack/ai-code-mode 0.3.6 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.3.9 → 1.0.0 Dependent
@tanstack/ai-codex 0.2.1 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.32 → 1.0.0 Dependent
@tanstack/ai-fal 0.9.10 → 1.0.0 Dependent
@tanstack/ai-grok 0.14.7 → 1.0.0 Dependent
@tanstack/ai-grok-build 0.2.1 → 1.0.0 Dependent
@tanstack/ai-groq 0.5.1 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.45 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.45 → 1.0.0 Dependent
@tanstack/ai-mistral 0.2.1 → 1.0.0 Dependent
@tanstack/ai-ollama 0.8.14 → 1.0.0 Dependent
@tanstack/ai-opencode 0.2.1 → 1.0.0 Dependent
@tanstack/ai-openrouter 0.15.8 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.13 → 1.0.0 Dependent
@tanstack/ai-sandbox 0.2.2 → 1.0.0 Dependent
@tanstack/ai-sandbox-cloudflare 0.2.2 → 1.0.0 Dependent
@tanstack/ai-sandbox-daytona 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-docker 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-local-process 0.2.0 → 1.0.0 Dependent
@tanstack/ai-sandbox-sprites 0.2.1 → 1.0.0 Dependent
@tanstack/ai-sandbox-vercel 0.2.0 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.12 → 1.0.0 Dependent
@tanstack/openai-base 0.9.7 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.40.0 → 0.41.0 Changeset
@tanstack/ai-client 0.20.0 → 0.21.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-devtools-core 0.4.22 → 0.4.23 Dependent
@tanstack/ai-isolate-cloudflare 0.2.36 → 0.2.37 Dependent
@tanstack/ai-mcp 0.2.3 → 0.2.4 Dependent
@tanstack/ai-vue-ui 0.2.31 → 0.2.32 Dependent
@tanstack/preact-ai-devtools 0.1.65 → 0.1.66 Dependent
@tanstack/react-ai-devtools 0.2.65 → 0.2.66 Dependent
@tanstack/solid-ai-devtools 0.2.65 → 0.2.66 Dependent

@nx-cloud

nx-cloud Bot commented Jul 9, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 254ea03

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 1m 21s View ↗
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-07-09 17:51:14 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai@923

@tanstack/ai-acp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-acp@923

@tanstack/ai-angular

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-angular@923

@tanstack/ai-anthropic

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-anthropic@923

@tanstack/ai-bedrock

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-bedrock@923

@tanstack/ai-claude-code

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-claude-code@923

@tanstack/ai-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-client@923

@tanstack/ai-code-mode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode@923

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode-skills@923

@tanstack/ai-codex

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-codex@923

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-devtools-core@923

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-durable-stream@923

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-elevenlabs@923

@tanstack/ai-event-client

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-event-client@923

@tanstack/ai-fal

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-fal@923

@tanstack/ai-gemini

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-gemini@923

@tanstack/ai-grok

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok@923

@tanstack/ai-grok-build

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-grok-build@923

@tanstack/ai-groq

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-groq@923

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-cloudflare@923

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-node@923

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-isolate-quickjs@923

@tanstack/ai-mcp

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mcp@923

@tanstack/ai-mistral

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-mistral@923

@tanstack/ai-ollama

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-ollama@923

@tanstack/ai-openai

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openai@923

@tanstack/ai-opencode

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-opencode@923

@tanstack/ai-openrouter

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-openrouter@923

@tanstack/ai-persistence

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence@923

@tanstack/ai-persistence-drizzle

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence-drizzle@923

@tanstack/ai-persistence-prisma

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-persistence-prisma@923

@tanstack/ai-preact

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-preact@923

@tanstack/ai-react

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react@923

@tanstack/ai-react-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-react-ui@923

@tanstack/ai-sandbox

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox@923

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-cloudflare@923

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-daytona@923

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-docker@923

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-local-process@923

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-sprites@923

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-sandbox-vercel@923

@tanstack/ai-solid

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid@923

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-solid-ui@923

@tanstack/ai-svelte

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-svelte@923

@tanstack/ai-utils

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-utils@923

@tanstack/ai-vue

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue@923

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-vue-ui@923

@tanstack/openai-base

npm i https://pkg.pr.new/TanStack/ai/@tanstack/openai-base@923

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/preact-ai-devtools@923

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/react-ai-devtools@923

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/TanStack/ai/@tanstack/solid-ai-devtools@923

commit: 254ea03

The docs/reference/** API docs are auto-generated (pnpm generate-docs) and
regenerated in dedicated PRs; keeping that churn out of this feature PR.
Restored to the pre-PR base.
@AlemTuzlak AlemTuzlak merged commit d5e8af7 into feat/persistence Jul 9, 2026
8 of 10 checks passed
@AlemTuzlak AlemTuzlak deleted the feat/persistence-v2 branch July 9, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant