feat: split persistence into state (middleware) + delivery (transport/durable-streams) durability#923
Conversation
…sport, remove event-log/cursor/resume seam
…onformance suite; remove delivery stores
…p cursor & generation delivery-resume
…erated API reference
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
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.
|
🚀 Changeset Version Preview14 package(s) bumped directly, 35 bumped as dependents. 🟥 Major bumps
🟨 Minor bumps
🟩 Patch bumps
|
|
View your CI Pipeline Execution ↗ for commit 254ea03
☁️ Nx Cloud last updated this comment at |
@tanstack/ai
@tanstack/ai-acp
@tanstack/ai-angular
@tanstack/ai-anthropic
@tanstack/ai-bedrock
@tanstack/ai-claude-code
@tanstack/ai-client
@tanstack/ai-code-mode
@tanstack/ai-code-mode-skills
@tanstack/ai-codex
@tanstack/ai-devtools-core
@tanstack/ai-durable-stream
@tanstack/ai-elevenlabs
@tanstack/ai-event-client
@tanstack/ai-fal
@tanstack/ai-gemini
@tanstack/ai-grok
@tanstack/ai-grok-build
@tanstack/ai-groq
@tanstack/ai-isolate-cloudflare
@tanstack/ai-isolate-node
@tanstack/ai-isolate-quickjs
@tanstack/ai-mcp
@tanstack/ai-mistral
@tanstack/ai-ollama
@tanstack/ai-openai
@tanstack/ai-opencode
@tanstack/ai-openrouter
@tanstack/ai-persistence
@tanstack/ai-persistence-drizzle
@tanstack/ai-persistence-prisma
@tanstack/ai-preact
@tanstack/ai-react
@tanstack/ai-react-ui
@tanstack/ai-sandbox
@tanstack/ai-sandbox-cloudflare
@tanstack/ai-sandbox-daytona
@tanstack/ai-sandbox-docker
@tanstack/ai-sandbox-local-process
@tanstack/ai-sandbox-sprites
@tanstack/ai-sandbox-vercel
@tanstack/ai-solid
@tanstack/ai-solid-ui
@tanstack/ai-svelte
@tanstack/ai-utils
@tanstack/ai-vue
@tanstack/ai-vue-ui
@tanstack/openai-base
@tanstack/preact-ai-devtools
@tanstack/react-ai-devtools
@tanstack/solid-ai-devtools
commit: |
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.
Summary
Reworks the persistence feature (#785) by splitting the two concerns that were fused under one "persistence" banner, per review feedback:
withChatPersistence/withGenerationPersistencepersist thread messages, run status, interrupts, and generation artifacts — nothing else.StreamDurabilitysink 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, whoseDurableRunStreamwas 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 })—durabilityis aStreamDurabilityadapter; same option on every transport helper. Transport stays the user's choice; durable streams is the backing.memoryStream(zero-infra dev default) and@tanstack/ai-durable-stream'sdurableStream(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.Last-Event-ID, zero cursor machinery.chat()loses itscursorparam;StreamChunkloses its in-bandcursor.publicEvents/internalEventsstores + tables,DurableRunStream,cursor.ts, coreresume.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).AIPersistencecontract:sqlPersistence({ dialect:'sqlite', url })(batteries),drizzlePersistence(db)(BYO),prismaPersistence(prisma)(BYO). Each ORM owns its own migrations natively — no more hand-authored DDL.ai-persistence-sql+-sqlite/-postgres/-cloudflarepackages, theSqlDriverabstraction, and the hand-rolled migration/CLI code — folded into the drizzle + prisma backends.@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
durableStreamdefault-batch exactly-once violation on mid-batch reconnect (now per-chunk offset tagging), an offset-codecnow-sentinel throw,memoryStreamin-flight-join truncation + a join-hang, thrown-provider-error not persisted, adrizzle/prismablob-prefix wildcard bug,sqlPersistencedialect type-honesty, and removal of a mis-firing generation resume surface.Validation
test:sherif,test:knip, doc-link check, andtest:kiira(808 snippets) green.test:eslint/test:types/test:lib/test:buildgreen across all 12 affected library packages;test:types/buildgreen across all example apps +testing/**.Known follow-ups (not blocking; tracked as notes)
injectChatdoesn't expose theresumeState/pendingInterrupts/resumeInterruptssurface the other 5 frameworks do — pre-existing, untouched by this PR.LockStoresatisfiesvalidatePersistenceFeatures(['locks'])while being process-local — documented; a distinguishable dev-default marker could make validation warn.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.joinRunaffordance yet (transparent SSE auto-resume covers the common case); would need aChatClient.joinRun.🤖 Generated with Claude Code