[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-tanstack-ai-persistencebuild-cloudflare-adapter":3,"mdc-pz8jp2-key":54,"related-repo-tanstack-ai-persistencebuild-cloudflare-adapter":3026,"related-org-tanstack-ai-persistencebuild-cloudflare-adapter":3129},{"slug":4,"name":5,"fn":6,"description":7,"org":8,"tags":12,"stars":24,"repoUrl":25,"updatedAt":26,"license":27,"forks":28,"topics":29,"repo":49,"sourceUrl":52,"mdContent":53},"ai-persistencebuild-cloudflare-adapter","ai-persistence\u002Fbuild-cloudflare-adapter","build Cloudflare Worker adapters for TanStack AI","Use when a Cloudflare Worker needs TanStack AI chat persistence — writes a chat-persistence.ts into the app against its D1 binding (raw or via Drizzle), plus a Durable Object LockStore. Covers per-request bindings, wrangler config, D1 migrations, and lease-based locks.",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},"tanstack","TanStack","https:\u002F\u002Fpexgzepcugksgbtrxkhf.supabase.co\u002Fstorage\u002Fv1\u002Fobject\u002Fpublic\u002Forg-logos\u002Ftanstack.png",[13,17,20,23],{"name":14,"slug":15,"type":16},"Cloudflare","cloudflare","tag",{"name":18,"slug":19,"type":16},"Serverless","serverless",{"name":21,"slug":22,"type":16},"Persistence","persistence",{"name":10,"slug":9,"type":16},2884,"https:\u002F\u002Fgithub.com\u002FTanStack\u002Fai","2026-07-30T05:53:31.519866",null,269,[30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,9,45,46,47,48],"ai","ai-agents","ai-sdk","anthropic","chatbot","function-calling","gemini","generative-ai","llm","multimodal","openai","react","solidjs","streaming","svelte","tool-calling","typescript","typescript-sdk","vue",{"repoUrl":25,"stars":24,"forks":28,"topics":50,"description":51},[30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,9,45,46,47,48],"🤖 Type-safe, provider-agnostic TypeScript AI SDK for streaming chat, tool calling, agents, and multimodal apps across OpenAI, Anthropic, Gemini, React, Vue, Svelte, and Solid.","https:\u002F\u002Fgithub.com\u002FTanStack\u002Fai\u002Ftree\u002FHEAD\u002Fpackages\u002Fai-persistence\u002Fskills\u002Fai-persistence\u002Fbuild-cloudflare-adapter","---\nname: ai-persistence\u002Fbuild-cloudflare-adapter\ndescription: Use when a Cloudflare Worker needs TanStack AI chat persistence — writes a chat-persistence.ts into the app against its D1 binding (raw or via Drizzle), plus a Durable Object LockStore. Covers per-request bindings, wrangler config, D1 migrations, and lease-based locks.\n---\n\n# Cloudflare Chat Persistence\n\nThe deliverable is **one file in the Worker** — `src\u002Flib\u002Fchat-persistence.ts` —\nexporting a factory that builds a `ChatPersistence` from the request's D1\nbinding, plus (when the app needs coordination) a Durable Object lock store.\nTables go into the app's existing `migrations\u002F` directory and are applied with\n`wrangler d1 migrations apply`.\n\nDo not create a package or a migration runner. Wrangler already tracks applied\nmigrations; a second bookkeeping table only creates drift.\n\nRead the **Build Your Own Adapter** guide\n(`docs\u002Fpersistence\u002Fbuild-your-own-adapter.md`) for the store contracts, and\n**ai-persistence\u002Fstores** for the shape rules. This skill covers only\nthe Cloudflare-specific parts.\n\n## 1. Read the app before writing anything\n\n| Find                   | Where to look                                                                                    | What it decides                                   |\n| ---------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------- |\n| D1 binding name        | `wrangler.jsonc` `d1_databases[].binding`                                                        | `env.DB` vs `env.AI_STATE` in the factory         |\n| How `env` reaches code | the Worker `fetch(request, env)`, or an async-local helper (`getDb()`, `getCloudflareContext()`) | Whether the factory takes `env` or reads a helper |\n| Drizzle or raw D1      | `drizzle-orm` in `package.json`, a `src\u002Fdb\u002Fschema.ts`                                            | Which recipe below to follow                      |\n| Migrations dir         | `wrangler.jsonc` `migrations_dir`, default `migrations\u002F`                                         | Where the new `.sql` file goes                    |\n| Existing table names   | the current migrations \u002F schema                                                                  | Prefix (`chat_*`) so nothing collides             |\n\n## 2. Two independent pieces\n\n```\nD1 database      -> messages, runs, interrupts, metadata   (AIPersistence.stores)\nDurable Object   -> LockStore                              (withLocks — NOT a store)\n```\n\nThese do not compose into one object. `AIPersistence.stores` accepts exactly\nfour keys; putting `locks` in the map — or in a `composePersistence` override —\nthrows `Unknown AIPersistence store key: locks` and fails to type-check. Return\nthe state persistence from one factory and the lock store from another, then\nwire them as two middlewares.\n\nMost apps need only the first piece. Add the Durable Object when other\nmiddleware genuinely needs mutual exclusion across isolates —\n`InMemoryLockStore` gives none, because a Worker runs on many isolates at once.\n\n## 3. Bindings are per-request\n\nThis is the one rule that separates Cloudflare from every other backend. A D1\nbinding does not exist at module scope, so `chat-persistence.ts` **must export a\nfactory**, not a const:\n\n```ts ignore\nimport { defineAIPersistence } from '@tanstack\u002Fai-persistence'\nimport type { ChatPersistence } from '@tanstack\u002Fai-persistence'\n\n\u002F** Call inside a request handler — `env` is not available at module scope. *\u002F\nexport function chatPersistence(d1: D1Database): ChatPersistence {\n  return defineAIPersistence({\n    stores: {\n      messages: createMessageStore(d1),\n      runs: createRunStore(d1),\n      interrupts: createInterruptStore(d1),\n      metadata: createMetadataStore(d1),\n    },\n  })\n}\n```\n\nAnnotate `ChatPersistence` — bare `AIPersistence` is the all-optional bag and\n`withPersistence` rejects it. Building it per request is cheap: the stores hold\nno state beyond the binding.\n\n## 4. The stores\n\nTwo routes, same invariants:\n\n- **Drizzle over D1** — if the app already runs Drizzle, wrap the binding with\n  `drizzle(env.DB, { schema })` and follow\n  **ai-persistence\u002Fbuild-drizzle-adapter** verbatim (its \"if `db` is\n  per-request\" section is exactly this case). Stop reading here.\n- **Raw D1** — implement the four stores against `d1.prepare(sql).bind(...)`:\n  `.first()` for `get`, `.all()` for `list*`, `.run()` for writes. D1 speaks\n  SQLite, so this mirrors the `node:sqlite` walkthrough in the guide one-for-one;\n  everything is already async, so no `Promise.resolve` wrapping.\n\nThe invariants are the whole game, whichever route you take:\n\n| Store        | Rule                                                                                                              |\n| ------------ | ----------------------------------------------------------------------------------------------------------------- |\n| `messages`   | `saveThread` is a full replace (`INSERT … ON CONFLICT(thread_id) DO UPDATE`)                                      |\n| `runs`       | `createOrResume` reads first, else `INSERT … ON CONFLICT DO NOTHING`, then re-reads                               |\n| `runs`       | `update` on an unknown id is a silent no-op — never throws, never inserts                                         |\n| `runs`       | `findActiveRun` returns the latest `'running'` run for the thread, else null — required for reload\u002Fswitch tailing |\n| `interrupts` | `create` is insert-if-absent; never clobber a resolved interrupt back to pending                                  |\n| `interrupts` | every `list*` ends `ORDER BY requested_at ASC`                                                                    |\n| `metadata`   | reject nullish `set` with a clear `TypeError`; tell callers to use `delete`                                       |\n\nRow mappers omit absent optionals (`...(row.error != null ? { error: row.error } : {})`)\nso records compare cleanly against the reference in-memory backend. JSON columns\nare `text` — `JSON.parse` on read, `JSON.stringify` on write. Timestamps are\n`integer` epoch ms.\n\n## 5. The migration\n\nWrite the tables into the app's `migrations\u002F` directory as a new numbered file:\n\n```sql\nCREATE TABLE IF NOT EXISTS chat_threads (\n  thread_id text PRIMARY KEY NOT NULL,\n  messages_json text NOT NULL,\n  updated_at integer NOT NULL\n);\nCREATE TABLE IF NOT EXISTS chat_runs (\n  run_id text PRIMARY KEY NOT NULL,\n  thread_id text NOT NULL,\n  status text NOT NULL,\n  started_at integer NOT NULL,\n  finished_at integer,\n  error text,\n  usage_json text\n);\nCREATE INDEX IF NOT EXISTS chat_runs_thread_status ON chat_runs (thread_id, status);\nCREATE TABLE IF NOT EXISTS chat_interrupts (\n  interrupt_id text PRIMARY KEY NOT NULL,\n  run_id text NOT NULL,\n  thread_id text NOT NULL,\n  status text NOT NULL,\n  requested_at integer NOT NULL,\n  resolved_at integer,\n  payload_json text NOT NULL,\n  response_json text\n);\nCREATE INDEX IF NOT EXISTS chat_interrupts_thread ON chat_interrupts (thread_id, requested_at);\nCREATE TABLE IF NOT EXISTS chat_metadata (\n  namespace text NOT NULL,\n  key text NOT NULL,\n  value_json text NOT NULL,\n  PRIMARY KEY (namespace, key)\n);\n```\n\nApply with `wrangler d1 migrations apply \u003Cdatabase-name>` (`--local` first, then\n`--remote`). If the app also uses Drizzle, generate this file with\n`drizzle-kit generate` instead of hand-writing it — the SQL and the Drizzle\ntable definitions must agree, so let one of them own the other.\n\n## 6. Wire it into the chat route\n\n```ts ignore\nimport {\n  chat,\n  chatParamsFromRequest,\n  toServerSentEventsResponse,\n} from '@tanstack\u002Fai'\nimport { openaiText } from '@tanstack\u002Fai-openai'\nimport { withPersistence } from '@tanstack\u002Fai-persistence'\nimport { chatPersistence } from '.\u002Flib\u002Fchat-persistence'\n\nexport default {\n  async fetch(request: Request, env: Env) {\n    const params = await chatParamsFromRequest(request)\n    const stream = chat({\n      adapter: openaiText('gpt-5.5'),\n      messages: params.messages,\n      threadId: params.threadId,\n      runId: params.runId,\n      ...(params.resume ? { resume: params.resume } : {}),\n      middleware: [withPersistence(chatPersistence(env.DB))],\n    })\n    return toServerSentEventsResponse(stream)\n  },\n}\n```\n\n`threadId` is a bare string to the stores. **Authorize thread access at the\nroute** — derive the user from the session, never trust a client-supplied id.\n\n## 7. Durable Object lock store (only if needed)\n\nImplement `LockStore` from `@tanstack\u002Fai\u002Flocks`. `withLock(key, fn)`\nroutes each key to a Durable Object instance (`idFromName(key)`) that serializes\nowners. Use **leases** so a crashed owner cannot block forever: the DO grants a\nlease with an expiry, an alarm reclaims it, and the lock passes the callback an\n`AbortSignal` that fires when ownership can no longer be guaranteed. Callbacks\nmust stop starting external mutations once the signal aborts.\n\nExport the DO class from the Worker entry so wrangler can bind it:\n\n```ts ignore\nexport { ChatLockDurableObject } from '.\u002Flocks'\n```\n\nThen wire both middlewares:\n\n```ts ignore\nimport { withLocks } from '@tanstack\u002Fai\u002Flocks'\nimport { withPersistence } from '@tanstack\u002Fai-persistence'\n\nconst middleware = [\n  withPersistence(chatPersistence(env.AI_STATE)),\n  withLocks(createDurableObjectLockStore(env.AI_LOCKS)),\n]\n```\n\n## wrangler bindings\n\n```jsonc\n{\n  \"d1_databases\": [\n    {\n      \"binding\": \"AI_STATE\",\n      \"database_name\": \"tanstack-ai-state\",\n      \"database_id\": \"\u003Cid>\",\n      \"migrations_dir\": \"migrations\",\n    },\n  ],\n  \"durable_objects\": {\n    \"bindings\": [{ \"name\": \"AI_LOCKS\", \"class_name\": \"ChatLockDurableObject\" }],\n  },\n  \"migrations\": [\n    { \"tag\": \"v1\", \"new_sqlite_classes\": [\"ChatLockDurableObject\"] },\n  ],\n}\n```\n\nDurable Object locks do not use the D1 table migration set; their state is\nconfigured through the migration tags above.\n\n## Verify\n\n```ts ignore\nimport { runPersistenceConformance } from '@tanstack\u002Fai-persistence\u002Ftestkit'\nimport { env } from 'cloudflare:test'\nimport { chatPersistence } from '..\u002Fsrc\u002Flib\u002Fchat-persistence'\n\nrunPersistenceConformance('app-d1', () => chatPersistence(env.AI_STATE))\n```\n\nRun it against a Miniflare D1 binding with the migration applied, reset between\nruns. All four state stores are provided, so pass no `skip` — and `skip` never\naccepts `'locks'`: the suite covers state only.\n\nThe lock store needs its **own** tests, because nothing in the conformance suite\ntouches it. Cover at minimum: two concurrent `withLock` calls on the same key\nserialize; different keys do not block each other; a lease that expires aborts\nthe signal handed to the critical section; and a callback that throws still\nreleases the lock.\n\n## Only if you are publishing this as a package\n\nFor a reusable npm adapter rather than a file in the app: peer-dep\n`@cloudflare\u002Fworkers-types >=4.x`, and prepend\n`\u002F\u002F\u002F \u003Creference types=\"@cloudflare\u002Fworkers-types\" \u002F>` to the generated\n`index.d.ts` so consumers get the D1\u002FDurableObject types. Emit the table SQL\ninto the consumer's `migrations\u002F` directory rather than shipping a runner, and\nif you offer both raw-D1 and Drizzle paths, guard with a test that the emitted\nSQL and the Drizzle tables describe the same schema.\n",{"data":55,"body":56},{"name":5,"description":7},{"type":57,"children":58},"root",[59,68,115,120,147,154,395,401,413,450,463,469,488,876,904,910,915,1024,1029,1255,1298,1304,1316,1594,1631,1637,2289,2306,2312,2364,2369,2413,2418,2602,2608,2740,2745,2751,2930,2958,2978,2984,3020],{"type":60,"tag":61,"props":62,"children":64},"element","h1",{"id":63},"cloudflare-chat-persistence",[65],{"type":66,"value":67},"text","Cloudflare Chat Persistence",{"type":60,"tag":69,"props":70,"children":71},"p",{},[72,74,80,82,89,91,97,99,105,107,113],{"type":66,"value":73},"The deliverable is ",{"type":60,"tag":75,"props":76,"children":77},"strong",{},[78],{"type":66,"value":79},"one file in the Worker",{"type":66,"value":81}," — ",{"type":60,"tag":83,"props":84,"children":86},"code",{"className":85},[],[87],{"type":66,"value":88},"src\u002Flib\u002Fchat-persistence.ts",{"type":66,"value":90}," —\nexporting a factory that builds a ",{"type":60,"tag":83,"props":92,"children":94},{"className":93},[],[95],{"type":66,"value":96},"ChatPersistence",{"type":66,"value":98}," from the request's D1\nbinding, plus (when the app needs coordination) a Durable Object lock store.\nTables go into the app's existing ",{"type":60,"tag":83,"props":100,"children":102},{"className":101},[],[103],{"type":66,"value":104},"migrations\u002F",{"type":66,"value":106}," directory and are applied with\n",{"type":60,"tag":83,"props":108,"children":110},{"className":109},[],[111],{"type":66,"value":112},"wrangler d1 migrations apply",{"type":66,"value":114},".",{"type":60,"tag":69,"props":116,"children":117},{},[118],{"type":66,"value":119},"Do not create a package or a migration runner. Wrangler already tracks applied\nmigrations; a second bookkeeping table only creates drift.",{"type":60,"tag":69,"props":121,"children":122},{},[123,125,130,132,138,140,145],{"type":66,"value":124},"Read the ",{"type":60,"tag":75,"props":126,"children":127},{},[128],{"type":66,"value":129},"Build Your Own Adapter",{"type":66,"value":131}," guide\n(",{"type":60,"tag":83,"props":133,"children":135},{"className":134},[],[136],{"type":66,"value":137},"docs\u002Fpersistence\u002Fbuild-your-own-adapter.md",{"type":66,"value":139},") for the store contracts, and\n",{"type":60,"tag":75,"props":141,"children":142},{},[143],{"type":66,"value":144},"ai-persistence\u002Fstores",{"type":66,"value":146}," for the shape rules. This skill covers only\nthe Cloudflare-specific parts.",{"type":60,"tag":148,"props":149,"children":151},"h2",{"id":150},"_1-read-the-app-before-writing-anything",[152],{"type":66,"value":153},"1. Read the app before writing anything",{"type":60,"tag":155,"props":156,"children":157},"table",{},[158,182],{"type":60,"tag":159,"props":160,"children":161},"thead",{},[162],{"type":60,"tag":163,"props":164,"children":165},"tr",{},[166,172,177],{"type":60,"tag":167,"props":168,"children":169},"th",{},[170],{"type":66,"value":171},"Find",{"type":60,"tag":167,"props":173,"children":174},{},[175],{"type":66,"value":176},"Where to look",{"type":60,"tag":167,"props":178,"children":179},{},[180],{"type":66,"value":181},"What it decides",{"type":60,"tag":183,"props":184,"children":185},"tbody",{},[186,231,288,326,369],{"type":60,"tag":163,"props":187,"children":188},{},[189,195,212],{"type":60,"tag":190,"props":191,"children":192},"td",{},[193],{"type":66,"value":194},"D1 binding name",{"type":60,"tag":190,"props":196,"children":197},{},[198,204,206],{"type":60,"tag":83,"props":199,"children":201},{"className":200},[],[202],{"type":66,"value":203},"wrangler.jsonc",{"type":66,"value":205}," ",{"type":60,"tag":83,"props":207,"children":209},{"className":208},[],[210],{"type":66,"value":211},"d1_databases[].binding",{"type":60,"tag":190,"props":213,"children":214},{},[215,221,223,229],{"type":60,"tag":83,"props":216,"children":218},{"className":217},[],[219],{"type":66,"value":220},"env.DB",{"type":66,"value":222}," vs ",{"type":60,"tag":83,"props":224,"children":226},{"className":225},[],[227],{"type":66,"value":228},"env.AI_STATE",{"type":66,"value":230}," in the factory",{"type":60,"tag":163,"props":232,"children":233},{},[234,247,276],{"type":60,"tag":190,"props":235,"children":236},{},[237,239,245],{"type":66,"value":238},"How ",{"type":60,"tag":83,"props":240,"children":242},{"className":241},[],[243],{"type":66,"value":244},"env",{"type":66,"value":246}," reaches code",{"type":60,"tag":190,"props":248,"children":249},{},[250,252,258,260,266,268,274],{"type":66,"value":251},"the Worker ",{"type":60,"tag":83,"props":253,"children":255},{"className":254},[],[256],{"type":66,"value":257},"fetch(request, env)",{"type":66,"value":259},", or an async-local helper (",{"type":60,"tag":83,"props":261,"children":263},{"className":262},[],[264],{"type":66,"value":265},"getDb()",{"type":66,"value":267},", ",{"type":60,"tag":83,"props":269,"children":271},{"className":270},[],[272],{"type":66,"value":273},"getCloudflareContext()",{"type":66,"value":275},")",{"type":60,"tag":190,"props":277,"children":278},{},[279,281,286],{"type":66,"value":280},"Whether the factory takes ",{"type":60,"tag":83,"props":282,"children":284},{"className":283},[],[285],{"type":66,"value":244},{"type":66,"value":287}," or reads a helper",{"type":60,"tag":163,"props":289,"children":290},{},[291,296,321],{"type":60,"tag":190,"props":292,"children":293},{},[294],{"type":66,"value":295},"Drizzle or raw D1",{"type":60,"tag":190,"props":297,"children":298},{},[299,305,307,313,315],{"type":60,"tag":83,"props":300,"children":302},{"className":301},[],[303],{"type":66,"value":304},"drizzle-orm",{"type":66,"value":306}," in ",{"type":60,"tag":83,"props":308,"children":310},{"className":309},[],[311],{"type":66,"value":312},"package.json",{"type":66,"value":314},", a ",{"type":60,"tag":83,"props":316,"children":318},{"className":317},[],[319],{"type":66,"value":320},"src\u002Fdb\u002Fschema.ts",{"type":60,"tag":190,"props":322,"children":323},{},[324],{"type":66,"value":325},"Which recipe below to follow",{"type":60,"tag":163,"props":327,"children":328},{},[329,334,356],{"type":60,"tag":190,"props":330,"children":331},{},[332],{"type":66,"value":333},"Migrations dir",{"type":60,"tag":190,"props":335,"children":336},{},[337,342,343,349,351],{"type":60,"tag":83,"props":338,"children":340},{"className":339},[],[341],{"type":66,"value":203},{"type":66,"value":205},{"type":60,"tag":83,"props":344,"children":346},{"className":345},[],[347],{"type":66,"value":348},"migrations_dir",{"type":66,"value":350},", default ",{"type":60,"tag":83,"props":352,"children":354},{"className":353},[],[355],{"type":66,"value":104},{"type":60,"tag":190,"props":357,"children":358},{},[359,361,367],{"type":66,"value":360},"Where the new ",{"type":60,"tag":83,"props":362,"children":364},{"className":363},[],[365],{"type":66,"value":366},".sql",{"type":66,"value":368}," file goes",{"type":60,"tag":163,"props":370,"children":371},{},[372,377,382],{"type":60,"tag":190,"props":373,"children":374},{},[375],{"type":66,"value":376},"Existing table names",{"type":60,"tag":190,"props":378,"children":379},{},[380],{"type":66,"value":381},"the current migrations \u002F schema",{"type":60,"tag":190,"props":383,"children":384},{},[385,387,393],{"type":66,"value":386},"Prefix (",{"type":60,"tag":83,"props":388,"children":390},{"className":389},[],[391],{"type":66,"value":392},"chat_*",{"type":66,"value":394},") so nothing collides",{"type":60,"tag":148,"props":396,"children":398},{"id":397},"_2-two-independent-pieces",[399],{"type":66,"value":400},"2. Two independent pieces",{"type":60,"tag":402,"props":403,"children":407},"pre",{"className":404,"code":406,"language":66},[405],"language-text","D1 database      -> messages, runs, interrupts, metadata   (AIPersistence.stores)\nDurable Object   -> LockStore                              (withLocks — NOT a store)\n",[408],{"type":60,"tag":83,"props":409,"children":411},{"__ignoreMap":410},"",[412],{"type":66,"value":406},{"type":60,"tag":69,"props":414,"children":415},{},[416,418,424,426,432,434,440,442,448],{"type":66,"value":417},"These do not compose into one object. ",{"type":60,"tag":83,"props":419,"children":421},{"className":420},[],[422],{"type":66,"value":423},"AIPersistence.stores",{"type":66,"value":425}," accepts exactly\nfour keys; putting ",{"type":60,"tag":83,"props":427,"children":429},{"className":428},[],[430],{"type":66,"value":431},"locks",{"type":66,"value":433}," in the map — or in a ",{"type":60,"tag":83,"props":435,"children":437},{"className":436},[],[438],{"type":66,"value":439},"composePersistence",{"type":66,"value":441}," override —\nthrows ",{"type":60,"tag":83,"props":443,"children":445},{"className":444},[],[446],{"type":66,"value":447},"Unknown AIPersistence store key: locks",{"type":66,"value":449}," and fails to type-check. Return\nthe state persistence from one factory and the lock store from another, then\nwire them as two middlewares.",{"type":60,"tag":69,"props":451,"children":452},{},[453,455,461],{"type":66,"value":454},"Most apps need only the first piece. Add the Durable Object when other\nmiddleware genuinely needs mutual exclusion across isolates —\n",{"type":60,"tag":83,"props":456,"children":458},{"className":457},[],[459],{"type":66,"value":460},"InMemoryLockStore",{"type":66,"value":462}," gives none, because a Worker runs on many isolates at once.",{"type":60,"tag":148,"props":464,"children":466},{"id":465},"_3-bindings-are-per-request",[467],{"type":66,"value":468},"3. Bindings are per-request",{"type":60,"tag":69,"props":470,"children":471},{},[472,474,480,481,486],{"type":66,"value":473},"This is the one rule that separates Cloudflare from every other backend. A D1\nbinding does not exist at module scope, so ",{"type":60,"tag":83,"props":475,"children":477},{"className":476},[],[478],{"type":66,"value":479},"chat-persistence.ts",{"type":66,"value":205},{"type":60,"tag":75,"props":482,"children":483},{},[484],{"type":66,"value":485},"must export a\nfactory",{"type":66,"value":487},", not a const:",{"type":60,"tag":402,"props":489,"children":494},{"className":490,"code":491,"language":492,"meta":493,"style":410},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { defineAIPersistence } from '@tanstack\u002Fai-persistence'\nimport type { ChatPersistence } from '@tanstack\u002Fai-persistence'\n\n\u002F** Call inside a request handler — `env` is not available at module scope. *\u002F\nexport function chatPersistence(d1: D1Database): ChatPersistence {\n  return defineAIPersistence({\n    stores: {\n      messages: createMessageStore(d1),\n      runs: createRunStore(d1),\n      interrupts: createInterruptStore(d1),\n      metadata: createMetadataStore(d1),\n    },\n  })\n}\n","ts","ignore",[495],{"type":60,"tag":83,"props":496,"children":497},{"__ignoreMap":410},[498,548,590,600,610,667,690,707,742,776,810,844,853,867],{"type":60,"tag":499,"props":500,"children":503},"span",{"class":501,"line":502},"line",1,[504,510,516,522,527,532,537,543],{"type":60,"tag":499,"props":505,"children":507},{"style":506},"--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic",[508],{"type":66,"value":509},"import",{"type":60,"tag":499,"props":511,"children":513},{"style":512},"--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF",[514],{"type":66,"value":515}," {",{"type":60,"tag":499,"props":517,"children":519},{"style":518},"--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8",[520],{"type":66,"value":521}," defineAIPersistence",{"type":60,"tag":499,"props":523,"children":524},{"style":512},[525],{"type":66,"value":526}," }",{"type":60,"tag":499,"props":528,"children":529},{"style":506},[530],{"type":66,"value":531}," from",{"type":60,"tag":499,"props":533,"children":534},{"style":512},[535],{"type":66,"value":536}," '",{"type":60,"tag":499,"props":538,"children":540},{"style":539},"--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D",[541],{"type":66,"value":542},"@tanstack\u002Fai-persistence",{"type":60,"tag":499,"props":544,"children":545},{"style":512},[546],{"type":66,"value":547},"'\n",{"type":60,"tag":499,"props":549,"children":551},{"class":501,"line":550},2,[552,556,561,565,570,574,578,582,586],{"type":60,"tag":499,"props":553,"children":554},{"style":506},[555],{"type":66,"value":509},{"type":60,"tag":499,"props":557,"children":558},{"style":506},[559],{"type":66,"value":560}," type",{"type":60,"tag":499,"props":562,"children":563},{"style":512},[564],{"type":66,"value":515},{"type":60,"tag":499,"props":566,"children":567},{"style":518},[568],{"type":66,"value":569}," ChatPersistence",{"type":60,"tag":499,"props":571,"children":572},{"style":512},[573],{"type":66,"value":526},{"type":60,"tag":499,"props":575,"children":576},{"style":506},[577],{"type":66,"value":531},{"type":60,"tag":499,"props":579,"children":580},{"style":512},[581],{"type":66,"value":536},{"type":60,"tag":499,"props":583,"children":584},{"style":539},[585],{"type":66,"value":542},{"type":60,"tag":499,"props":587,"children":588},{"style":512},[589],{"type":66,"value":547},{"type":60,"tag":499,"props":591,"children":593},{"class":501,"line":592},3,[594],{"type":60,"tag":499,"props":595,"children":597},{"emptyLinePlaceholder":596},true,[598],{"type":66,"value":599},"\n",{"type":60,"tag":499,"props":601,"children":603},{"class":501,"line":602},4,[604],{"type":60,"tag":499,"props":605,"children":607},{"style":606},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic",[608],{"type":66,"value":609},"\u002F** Call inside a request handler — `env` is not available at module scope. *\u002F\n",{"type":60,"tag":499,"props":611,"children":613},{"class":501,"line":612},5,[614,619,625,631,636,642,647,653,658,662],{"type":60,"tag":499,"props":615,"children":616},{"style":506},[617],{"type":66,"value":618},"export",{"type":60,"tag":499,"props":620,"children":622},{"style":621},"--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA",[623],{"type":66,"value":624}," function",{"type":60,"tag":499,"props":626,"children":628},{"style":627},"--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF",[629],{"type":66,"value":630}," chatPersistence",{"type":60,"tag":499,"props":632,"children":633},{"style":512},[634],{"type":66,"value":635},"(",{"type":60,"tag":499,"props":637,"children":639},{"style":638},"--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic",[640],{"type":66,"value":641},"d1",{"type":60,"tag":499,"props":643,"children":644},{"style":512},[645],{"type":66,"value":646},":",{"type":60,"tag":499,"props":648,"children":650},{"style":649},"--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B",[651],{"type":66,"value":652}," D1Database",{"type":60,"tag":499,"props":654,"children":655},{"style":512},[656],{"type":66,"value":657},"):",{"type":60,"tag":499,"props":659,"children":660},{"style":649},[661],{"type":66,"value":569},{"type":60,"tag":499,"props":663,"children":664},{"style":512},[665],{"type":66,"value":666}," {\n",{"type":60,"tag":499,"props":668,"children":670},{"class":501,"line":669},6,[671,676,680,685],{"type":60,"tag":499,"props":672,"children":673},{"style":506},[674],{"type":66,"value":675},"  return",{"type":60,"tag":499,"props":677,"children":678},{"style":627},[679],{"type":66,"value":521},{"type":60,"tag":499,"props":681,"children":683},{"style":682},"--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178",[684],{"type":66,"value":635},{"type":60,"tag":499,"props":686,"children":687},{"style":512},[688],{"type":66,"value":689},"{\n",{"type":60,"tag":499,"props":691,"children":693},{"class":501,"line":692},7,[694,699,703],{"type":60,"tag":499,"props":695,"children":696},{"style":682},[697],{"type":66,"value":698},"    stores",{"type":60,"tag":499,"props":700,"children":701},{"style":512},[702],{"type":66,"value":646},{"type":60,"tag":499,"props":704,"children":705},{"style":512},[706],{"type":66,"value":666},{"type":60,"tag":499,"props":708,"children":710},{"class":501,"line":709},8,[711,716,720,725,729,733,737],{"type":60,"tag":499,"props":712,"children":713},{"style":682},[714],{"type":66,"value":715},"      messages",{"type":60,"tag":499,"props":717,"children":718},{"style":512},[719],{"type":66,"value":646},{"type":60,"tag":499,"props":721,"children":722},{"style":627},[723],{"type":66,"value":724}," createMessageStore",{"type":60,"tag":499,"props":726,"children":727},{"style":682},[728],{"type":66,"value":635},{"type":60,"tag":499,"props":730,"children":731},{"style":518},[732],{"type":66,"value":641},{"type":60,"tag":499,"props":734,"children":735},{"style":682},[736],{"type":66,"value":275},{"type":60,"tag":499,"props":738,"children":739},{"style":512},[740],{"type":66,"value":741},",\n",{"type":60,"tag":499,"props":743,"children":745},{"class":501,"line":744},9,[746,751,755,760,764,768,772],{"type":60,"tag":499,"props":747,"children":748},{"style":682},[749],{"type":66,"value":750},"      runs",{"type":60,"tag":499,"props":752,"children":753},{"style":512},[754],{"type":66,"value":646},{"type":60,"tag":499,"props":756,"children":757},{"style":627},[758],{"type":66,"value":759}," createRunStore",{"type":60,"tag":499,"props":761,"children":762},{"style":682},[763],{"type":66,"value":635},{"type":60,"tag":499,"props":765,"children":766},{"style":518},[767],{"type":66,"value":641},{"type":60,"tag":499,"props":769,"children":770},{"style":682},[771],{"type":66,"value":275},{"type":60,"tag":499,"props":773,"children":774},{"style":512},[775],{"type":66,"value":741},{"type":60,"tag":499,"props":777,"children":779},{"class":501,"line":778},10,[780,785,789,794,798,802,806],{"type":60,"tag":499,"props":781,"children":782},{"style":682},[783],{"type":66,"value":784},"      interrupts",{"type":60,"tag":499,"props":786,"children":787},{"style":512},[788],{"type":66,"value":646},{"type":60,"tag":499,"props":790,"children":791},{"style":627},[792],{"type":66,"value":793}," createInterruptStore",{"type":60,"tag":499,"props":795,"children":796},{"style":682},[797],{"type":66,"value":635},{"type":60,"tag":499,"props":799,"children":800},{"style":518},[801],{"type":66,"value":641},{"type":60,"tag":499,"props":803,"children":804},{"style":682},[805],{"type":66,"value":275},{"type":60,"tag":499,"props":807,"children":808},{"style":512},[809],{"type":66,"value":741},{"type":60,"tag":499,"props":811,"children":813},{"class":501,"line":812},11,[814,819,823,828,832,836,840],{"type":60,"tag":499,"props":815,"children":816},{"style":682},[817],{"type":66,"value":818},"      metadata",{"type":60,"tag":499,"props":820,"children":821},{"style":512},[822],{"type":66,"value":646},{"type":60,"tag":499,"props":824,"children":825},{"style":627},[826],{"type":66,"value":827}," createMetadataStore",{"type":60,"tag":499,"props":829,"children":830},{"style":682},[831],{"type":66,"value":635},{"type":60,"tag":499,"props":833,"children":834},{"style":518},[835],{"type":66,"value":641},{"type":60,"tag":499,"props":837,"children":838},{"style":682},[839],{"type":66,"value":275},{"type":60,"tag":499,"props":841,"children":842},{"style":512},[843],{"type":66,"value":741},{"type":60,"tag":499,"props":845,"children":847},{"class":501,"line":846},12,[848],{"type":60,"tag":499,"props":849,"children":850},{"style":512},[851],{"type":66,"value":852},"    },\n",{"type":60,"tag":499,"props":854,"children":856},{"class":501,"line":855},13,[857,862],{"type":60,"tag":499,"props":858,"children":859},{"style":512},[860],{"type":66,"value":861},"  }",{"type":60,"tag":499,"props":863,"children":864},{"style":682},[865],{"type":66,"value":866},")\n",{"type":60,"tag":499,"props":868,"children":870},{"class":501,"line":869},14,[871],{"type":60,"tag":499,"props":872,"children":873},{"style":512},[874],{"type":66,"value":875},"}\n",{"type":60,"tag":69,"props":877,"children":878},{},[879,881,886,888,894,896,902],{"type":66,"value":880},"Annotate ",{"type":60,"tag":83,"props":882,"children":884},{"className":883},[],[885],{"type":66,"value":96},{"type":66,"value":887}," — bare ",{"type":60,"tag":83,"props":889,"children":891},{"className":890},[],[892],{"type":66,"value":893},"AIPersistence",{"type":66,"value":895}," is the all-optional bag and\n",{"type":60,"tag":83,"props":897,"children":899},{"className":898},[],[900],{"type":66,"value":901},"withPersistence",{"type":66,"value":903}," rejects it. Building it per request is cheap: the stores hold\nno state beyond the binding.",{"type":60,"tag":148,"props":905,"children":907},{"id":906},"_4-the-stores",[908],{"type":66,"value":909},"4. The stores",{"type":60,"tag":69,"props":911,"children":912},{},[913],{"type":66,"value":914},"Two routes, same invariants:",{"type":60,"tag":916,"props":917,"children":918},"ul",{},[919,953],{"type":60,"tag":920,"props":921,"children":922},"li",{},[923,928,930,936,938,943,945,951],{"type":60,"tag":75,"props":924,"children":925},{},[926],{"type":66,"value":927},"Drizzle over D1",{"type":66,"value":929}," — if the app already runs Drizzle, wrap the binding with\n",{"type":60,"tag":83,"props":931,"children":933},{"className":932},[],[934],{"type":66,"value":935},"drizzle(env.DB, { schema })",{"type":66,"value":937}," and follow\n",{"type":60,"tag":75,"props":939,"children":940},{},[941],{"type":66,"value":942},"ai-persistence\u002Fbuild-drizzle-adapter",{"type":66,"value":944}," verbatim (its \"if ",{"type":60,"tag":83,"props":946,"children":948},{"className":947},[],[949],{"type":66,"value":950},"db",{"type":66,"value":952}," is\nper-request\" section is exactly this case). Stop reading here.",{"type":60,"tag":920,"props":954,"children":955},{},[956,961,963,969,971,977,979,985,986,992,993,999,1000,1006,1008,1014,1016,1022],{"type":60,"tag":75,"props":957,"children":958},{},[959],{"type":66,"value":960},"Raw D1",{"type":66,"value":962}," — implement the four stores against ",{"type":60,"tag":83,"props":964,"children":966},{"className":965},[],[967],{"type":66,"value":968},"d1.prepare(sql).bind(...)",{"type":66,"value":970},":\n",{"type":60,"tag":83,"props":972,"children":974},{"className":973},[],[975],{"type":66,"value":976},".first()",{"type":66,"value":978}," for ",{"type":60,"tag":83,"props":980,"children":982},{"className":981},[],[983],{"type":66,"value":984},"get",{"type":66,"value":267},{"type":60,"tag":83,"props":987,"children":989},{"className":988},[],[990],{"type":66,"value":991},".all()",{"type":66,"value":978},{"type":60,"tag":83,"props":994,"children":996},{"className":995},[],[997],{"type":66,"value":998},"list*",{"type":66,"value":267},{"type":60,"tag":83,"props":1001,"children":1003},{"className":1002},[],[1004],{"type":66,"value":1005},".run()",{"type":66,"value":1007}," for writes. D1 speaks\nSQLite, so this mirrors the ",{"type":60,"tag":83,"props":1009,"children":1011},{"className":1010},[],[1012],{"type":66,"value":1013},"node:sqlite",{"type":66,"value":1015}," walkthrough in the guide one-for-one;\neverything is already async, so no ",{"type":60,"tag":83,"props":1017,"children":1019},{"className":1018},[],[1020],{"type":66,"value":1021},"Promise.resolve",{"type":66,"value":1023}," wrapping.",{"type":60,"tag":69,"props":1025,"children":1026},{},[1027],{"type":66,"value":1028},"The invariants are the whole game, whichever route you take:",{"type":60,"tag":155,"props":1030,"children":1031},{},[1032,1048],{"type":60,"tag":159,"props":1033,"children":1034},{},[1035],{"type":60,"tag":163,"props":1036,"children":1037},{},[1038,1043],{"type":60,"tag":167,"props":1039,"children":1040},{},[1041],{"type":66,"value":1042},"Store",{"type":60,"tag":167,"props":1044,"children":1045},{},[1046],{"type":66,"value":1047},"Rule",{"type":60,"tag":183,"props":1049,"children":1050},{},[1051,1081,1112,1134,1164,1187,1216],{"type":60,"tag":163,"props":1052,"children":1053},{},[1054,1063],{"type":60,"tag":190,"props":1055,"children":1056},{},[1057],{"type":60,"tag":83,"props":1058,"children":1060},{"className":1059},[],[1061],{"type":66,"value":1062},"messages",{"type":60,"tag":190,"props":1064,"children":1065},{},[1066,1072,1074,1080],{"type":60,"tag":83,"props":1067,"children":1069},{"className":1068},[],[1070],{"type":66,"value":1071},"saveThread",{"type":66,"value":1073}," is a full replace (",{"type":60,"tag":83,"props":1075,"children":1077},{"className":1076},[],[1078],{"type":66,"value":1079},"INSERT … ON CONFLICT(thread_id) DO UPDATE",{"type":66,"value":275},{"type":60,"tag":163,"props":1082,"children":1083},{},[1084,1093],{"type":60,"tag":190,"props":1085,"children":1086},{},[1087],{"type":60,"tag":83,"props":1088,"children":1090},{"className":1089},[],[1091],{"type":66,"value":1092},"runs",{"type":60,"tag":190,"props":1094,"children":1095},{},[1096,1102,1104,1110],{"type":60,"tag":83,"props":1097,"children":1099},{"className":1098},[],[1100],{"type":66,"value":1101},"createOrResume",{"type":66,"value":1103}," reads first, else ",{"type":60,"tag":83,"props":1105,"children":1107},{"className":1106},[],[1108],{"type":66,"value":1109},"INSERT … ON CONFLICT DO NOTHING",{"type":66,"value":1111},", then re-reads",{"type":60,"tag":163,"props":1113,"children":1114},{},[1115,1123],{"type":60,"tag":190,"props":1116,"children":1117},{},[1118],{"type":60,"tag":83,"props":1119,"children":1121},{"className":1120},[],[1122],{"type":66,"value":1092},{"type":60,"tag":190,"props":1124,"children":1125},{},[1126,1132],{"type":60,"tag":83,"props":1127,"children":1129},{"className":1128},[],[1130],{"type":66,"value":1131},"update",{"type":66,"value":1133}," on an unknown id is a silent no-op — never throws, never inserts",{"type":60,"tag":163,"props":1135,"children":1136},{},[1137,1145],{"type":60,"tag":190,"props":1138,"children":1139},{},[1140],{"type":60,"tag":83,"props":1141,"children":1143},{"className":1142},[],[1144],{"type":66,"value":1092},{"type":60,"tag":190,"props":1146,"children":1147},{},[1148,1154,1156,1162],{"type":60,"tag":83,"props":1149,"children":1151},{"className":1150},[],[1152],{"type":66,"value":1153},"findActiveRun",{"type":66,"value":1155}," returns the latest ",{"type":60,"tag":83,"props":1157,"children":1159},{"className":1158},[],[1160],{"type":66,"value":1161},"'running'",{"type":66,"value":1163}," run for the thread, else null — required for reload\u002Fswitch tailing",{"type":60,"tag":163,"props":1165,"children":1166},{},[1167,1176],{"type":60,"tag":190,"props":1168,"children":1169},{},[1170],{"type":60,"tag":83,"props":1171,"children":1173},{"className":1172},[],[1174],{"type":66,"value":1175},"interrupts",{"type":60,"tag":190,"props":1177,"children":1178},{},[1179,1185],{"type":60,"tag":83,"props":1180,"children":1182},{"className":1181},[],[1183],{"type":66,"value":1184},"create",{"type":66,"value":1186}," is insert-if-absent; never clobber a resolved interrupt back to pending",{"type":60,"tag":163,"props":1188,"children":1189},{},[1190,1198],{"type":60,"tag":190,"props":1191,"children":1192},{},[1193],{"type":60,"tag":83,"props":1194,"children":1196},{"className":1195},[],[1197],{"type":66,"value":1175},{"type":60,"tag":190,"props":1199,"children":1200},{},[1201,1203,1208,1210],{"type":66,"value":1202},"every ",{"type":60,"tag":83,"props":1204,"children":1206},{"className":1205},[],[1207],{"type":66,"value":998},{"type":66,"value":1209}," ends ",{"type":60,"tag":83,"props":1211,"children":1213},{"className":1212},[],[1214],{"type":66,"value":1215},"ORDER BY requested_at ASC",{"type":60,"tag":163,"props":1217,"children":1218},{},[1219,1228],{"type":60,"tag":190,"props":1220,"children":1221},{},[1222],{"type":60,"tag":83,"props":1223,"children":1225},{"className":1224},[],[1226],{"type":66,"value":1227},"metadata",{"type":60,"tag":190,"props":1229,"children":1230},{},[1231,1233,1239,1241,1247,1249],{"type":66,"value":1232},"reject nullish ",{"type":60,"tag":83,"props":1234,"children":1236},{"className":1235},[],[1237],{"type":66,"value":1238},"set",{"type":66,"value":1240}," with a clear ",{"type":60,"tag":83,"props":1242,"children":1244},{"className":1243},[],[1245],{"type":66,"value":1246},"TypeError",{"type":66,"value":1248},"; tell callers to use ",{"type":60,"tag":83,"props":1250,"children":1252},{"className":1251},[],[1253],{"type":66,"value":1254},"delete",{"type":60,"tag":69,"props":1256,"children":1257},{},[1258,1260,1266,1268,1273,1274,1280,1282,1288,1290,1296],{"type":66,"value":1259},"Row mappers omit absent optionals (",{"type":60,"tag":83,"props":1261,"children":1263},{"className":1262},[],[1264],{"type":66,"value":1265},"...(row.error != null ? { error: row.error } : {})",{"type":66,"value":1267},")\nso records compare cleanly against the reference in-memory backend. JSON columns\nare ",{"type":60,"tag":83,"props":1269,"children":1271},{"className":1270},[],[1272],{"type":66,"value":66},{"type":66,"value":81},{"type":60,"tag":83,"props":1275,"children":1277},{"className":1276},[],[1278],{"type":66,"value":1279},"JSON.parse",{"type":66,"value":1281}," on read, ",{"type":60,"tag":83,"props":1283,"children":1285},{"className":1284},[],[1286],{"type":66,"value":1287},"JSON.stringify",{"type":66,"value":1289}," on write. Timestamps are\n",{"type":60,"tag":83,"props":1291,"children":1293},{"className":1292},[],[1294],{"type":66,"value":1295},"integer",{"type":66,"value":1297}," epoch ms.",{"type":60,"tag":148,"props":1299,"children":1301},{"id":1300},"_5-the-migration",[1302],{"type":66,"value":1303},"5. The migration",{"type":60,"tag":69,"props":1305,"children":1306},{},[1307,1309,1314],{"type":66,"value":1308},"Write the tables into the app's ",{"type":60,"tag":83,"props":1310,"children":1312},{"className":1311},[],[1313],{"type":66,"value":104},{"type":66,"value":1315}," directory as a new numbered file:",{"type":60,"tag":402,"props":1317,"children":1321},{"className":1318,"code":1319,"language":1320,"meta":410,"style":410},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","CREATE TABLE IF NOT EXISTS chat_threads (\n  thread_id text PRIMARY KEY NOT NULL,\n  messages_json text NOT NULL,\n  updated_at integer NOT NULL\n);\nCREATE TABLE IF NOT EXISTS chat_runs (\n  run_id text PRIMARY KEY NOT NULL,\n  thread_id text NOT NULL,\n  status text NOT NULL,\n  started_at integer NOT NULL,\n  finished_at integer,\n  error text,\n  usage_json text\n);\nCREATE INDEX IF NOT EXISTS chat_runs_thread_status ON chat_runs (thread_id, status);\nCREATE TABLE IF NOT EXISTS chat_interrupts (\n  interrupt_id text PRIMARY KEY NOT NULL,\n  run_id text NOT NULL,\n  thread_id text NOT NULL,\n  status text NOT NULL,\n  requested_at integer NOT NULL,\n  resolved_at integer,\n  payload_json text NOT NULL,\n  response_json text\n);\nCREATE INDEX IF NOT EXISTS chat_interrupts_thread ON chat_interrupts (thread_id, requested_at);\nCREATE TABLE IF NOT EXISTS chat_metadata (\n  namespace text NOT NULL,\n  key text NOT NULL,\n  value_json text NOT NULL,\n  PRIMARY KEY (namespace, key)\n);\n","sql",[1322],{"type":60,"tag":83,"props":1323,"children":1324},{"__ignoreMap":410},[1325,1333,1341,1349,1357,1365,1373,1381,1389,1397,1405,1413,1421,1429,1436,1445,1454,1463,1472,1480,1488,1497,1506,1515,1524,1532,1541,1550,1559,1568,1577,1586],{"type":60,"tag":499,"props":1326,"children":1327},{"class":501,"line":502},[1328],{"type":60,"tag":499,"props":1329,"children":1330},{},[1331],{"type":66,"value":1332},"CREATE TABLE IF NOT EXISTS chat_threads (\n",{"type":60,"tag":499,"props":1334,"children":1335},{"class":501,"line":550},[1336],{"type":60,"tag":499,"props":1337,"children":1338},{},[1339],{"type":66,"value":1340},"  thread_id text PRIMARY KEY NOT NULL,\n",{"type":60,"tag":499,"props":1342,"children":1343},{"class":501,"line":592},[1344],{"type":60,"tag":499,"props":1345,"children":1346},{},[1347],{"type":66,"value":1348},"  messages_json text NOT NULL,\n",{"type":60,"tag":499,"props":1350,"children":1351},{"class":501,"line":602},[1352],{"type":60,"tag":499,"props":1353,"children":1354},{},[1355],{"type":66,"value":1356},"  updated_at integer NOT NULL\n",{"type":60,"tag":499,"props":1358,"children":1359},{"class":501,"line":612},[1360],{"type":60,"tag":499,"props":1361,"children":1362},{},[1363],{"type":66,"value":1364},");\n",{"type":60,"tag":499,"props":1366,"children":1367},{"class":501,"line":669},[1368],{"type":60,"tag":499,"props":1369,"children":1370},{},[1371],{"type":66,"value":1372},"CREATE TABLE IF NOT EXISTS chat_runs (\n",{"type":60,"tag":499,"props":1374,"children":1375},{"class":501,"line":692},[1376],{"type":60,"tag":499,"props":1377,"children":1378},{},[1379],{"type":66,"value":1380},"  run_id text PRIMARY KEY NOT NULL,\n",{"type":60,"tag":499,"props":1382,"children":1383},{"class":501,"line":709},[1384],{"type":60,"tag":499,"props":1385,"children":1386},{},[1387],{"type":66,"value":1388},"  thread_id text NOT NULL,\n",{"type":60,"tag":499,"props":1390,"children":1391},{"class":501,"line":744},[1392],{"type":60,"tag":499,"props":1393,"children":1394},{},[1395],{"type":66,"value":1396},"  status text NOT NULL,\n",{"type":60,"tag":499,"props":1398,"children":1399},{"class":501,"line":778},[1400],{"type":60,"tag":499,"props":1401,"children":1402},{},[1403],{"type":66,"value":1404},"  started_at integer NOT NULL,\n",{"type":60,"tag":499,"props":1406,"children":1407},{"class":501,"line":812},[1408],{"type":60,"tag":499,"props":1409,"children":1410},{},[1411],{"type":66,"value":1412},"  finished_at integer,\n",{"type":60,"tag":499,"props":1414,"children":1415},{"class":501,"line":846},[1416],{"type":60,"tag":499,"props":1417,"children":1418},{},[1419],{"type":66,"value":1420},"  error text,\n",{"type":60,"tag":499,"props":1422,"children":1423},{"class":501,"line":855},[1424],{"type":60,"tag":499,"props":1425,"children":1426},{},[1427],{"type":66,"value":1428},"  usage_json text\n",{"type":60,"tag":499,"props":1430,"children":1431},{"class":501,"line":869},[1432],{"type":60,"tag":499,"props":1433,"children":1434},{},[1435],{"type":66,"value":1364},{"type":60,"tag":499,"props":1437,"children":1439},{"class":501,"line":1438},15,[1440],{"type":60,"tag":499,"props":1441,"children":1442},{},[1443],{"type":66,"value":1444},"CREATE INDEX IF NOT EXISTS chat_runs_thread_status ON chat_runs (thread_id, status);\n",{"type":60,"tag":499,"props":1446,"children":1448},{"class":501,"line":1447},16,[1449],{"type":60,"tag":499,"props":1450,"children":1451},{},[1452],{"type":66,"value":1453},"CREATE TABLE IF NOT EXISTS chat_interrupts (\n",{"type":60,"tag":499,"props":1455,"children":1457},{"class":501,"line":1456},17,[1458],{"type":60,"tag":499,"props":1459,"children":1460},{},[1461],{"type":66,"value":1462},"  interrupt_id text PRIMARY KEY NOT NULL,\n",{"type":60,"tag":499,"props":1464,"children":1466},{"class":501,"line":1465},18,[1467],{"type":60,"tag":499,"props":1468,"children":1469},{},[1470],{"type":66,"value":1471},"  run_id text NOT NULL,\n",{"type":60,"tag":499,"props":1473,"children":1475},{"class":501,"line":1474},19,[1476],{"type":60,"tag":499,"props":1477,"children":1478},{},[1479],{"type":66,"value":1388},{"type":60,"tag":499,"props":1481,"children":1483},{"class":501,"line":1482},20,[1484],{"type":60,"tag":499,"props":1485,"children":1486},{},[1487],{"type":66,"value":1396},{"type":60,"tag":499,"props":1489,"children":1491},{"class":501,"line":1490},21,[1492],{"type":60,"tag":499,"props":1493,"children":1494},{},[1495],{"type":66,"value":1496},"  requested_at integer NOT NULL,\n",{"type":60,"tag":499,"props":1498,"children":1500},{"class":501,"line":1499},22,[1501],{"type":60,"tag":499,"props":1502,"children":1503},{},[1504],{"type":66,"value":1505},"  resolved_at integer,\n",{"type":60,"tag":499,"props":1507,"children":1509},{"class":501,"line":1508},23,[1510],{"type":60,"tag":499,"props":1511,"children":1512},{},[1513],{"type":66,"value":1514},"  payload_json text NOT NULL,\n",{"type":60,"tag":499,"props":1516,"children":1518},{"class":501,"line":1517},24,[1519],{"type":60,"tag":499,"props":1520,"children":1521},{},[1522],{"type":66,"value":1523},"  response_json text\n",{"type":60,"tag":499,"props":1525,"children":1527},{"class":501,"line":1526},25,[1528],{"type":60,"tag":499,"props":1529,"children":1530},{},[1531],{"type":66,"value":1364},{"type":60,"tag":499,"props":1533,"children":1535},{"class":501,"line":1534},26,[1536],{"type":60,"tag":499,"props":1537,"children":1538},{},[1539],{"type":66,"value":1540},"CREATE INDEX IF NOT EXISTS chat_interrupts_thread ON chat_interrupts (thread_id, requested_at);\n",{"type":60,"tag":499,"props":1542,"children":1544},{"class":501,"line":1543},27,[1545],{"type":60,"tag":499,"props":1546,"children":1547},{},[1548],{"type":66,"value":1549},"CREATE TABLE IF NOT EXISTS chat_metadata (\n",{"type":60,"tag":499,"props":1551,"children":1553},{"class":501,"line":1552},28,[1554],{"type":60,"tag":499,"props":1555,"children":1556},{},[1557],{"type":66,"value":1558},"  namespace text NOT NULL,\n",{"type":60,"tag":499,"props":1560,"children":1562},{"class":501,"line":1561},29,[1563],{"type":60,"tag":499,"props":1564,"children":1565},{},[1566],{"type":66,"value":1567},"  key text NOT NULL,\n",{"type":60,"tag":499,"props":1569,"children":1571},{"class":501,"line":1570},30,[1572],{"type":60,"tag":499,"props":1573,"children":1574},{},[1575],{"type":66,"value":1576},"  value_json text NOT NULL,\n",{"type":60,"tag":499,"props":1578,"children":1580},{"class":501,"line":1579},31,[1581],{"type":60,"tag":499,"props":1582,"children":1583},{},[1584],{"type":66,"value":1585},"  PRIMARY KEY (namespace, key)\n",{"type":60,"tag":499,"props":1587,"children":1589},{"class":501,"line":1588},32,[1590],{"type":60,"tag":499,"props":1591,"children":1592},{},[1593],{"type":66,"value":1364},{"type":60,"tag":69,"props":1595,"children":1596},{},[1597,1599,1605,1607,1613,1615,1621,1623,1629],{"type":66,"value":1598},"Apply with ",{"type":60,"tag":83,"props":1600,"children":1602},{"className":1601},[],[1603],{"type":66,"value":1604},"wrangler d1 migrations apply \u003Cdatabase-name>",{"type":66,"value":1606}," (",{"type":60,"tag":83,"props":1608,"children":1610},{"className":1609},[],[1611],{"type":66,"value":1612},"--local",{"type":66,"value":1614}," first, then\n",{"type":60,"tag":83,"props":1616,"children":1618},{"className":1617},[],[1619],{"type":66,"value":1620},"--remote",{"type":66,"value":1622},"). If the app also uses Drizzle, generate this file with\n",{"type":60,"tag":83,"props":1624,"children":1626},{"className":1625},[],[1627],{"type":66,"value":1628},"drizzle-kit generate",{"type":66,"value":1630}," instead of hand-writing it — the SQL and the Drizzle\ntable definitions must agree, so let one of them own the other.",{"type":60,"tag":148,"props":1632,"children":1634},{"id":1633},"_6-wire-it-into-the-chat-route",[1635],{"type":66,"value":1636},"6. Wire it into the chat route",{"type":60,"tag":402,"props":1638,"children":1640},{"className":490,"code":1639,"language":492,"meta":493,"style":410},"import {\n  chat,\n  chatParamsFromRequest,\n  toServerSentEventsResponse,\n} from '@tanstack\u002Fai'\nimport { openaiText } from '@tanstack\u002Fai-openai'\nimport { withPersistence } from '@tanstack\u002Fai-persistence'\nimport { chatPersistence } from '.\u002Flib\u002Fchat-persistence'\n\nexport default {\n  async fetch(request: Request, env: Env) {\n    const params = await chatParamsFromRequest(request)\n    const stream = chat({\n      adapter: openaiText('gpt-5.5'),\n      messages: params.messages,\n      threadId: params.threadId,\n      runId: params.runId,\n      ...(params.resume ? { resume: params.resume } : {}),\n      middleware: [withPersistence(chatPersistence(env.DB))],\n    })\n    return toServerSentEventsResponse(stream)\n  },\n}\n",[1641],{"type":60,"tag":83,"props":1642,"children":1643},{"__ignoreMap":410},[1644,1655,1667,1679,1691,1716,1753,1789,1825,1832,1848,1906,1946,1975,2017,2044,2073,2102,2180,2236,2248,2274,2282],{"type":60,"tag":499,"props":1645,"children":1646},{"class":501,"line":502},[1647,1651],{"type":60,"tag":499,"props":1648,"children":1649},{"style":506},[1650],{"type":66,"value":509},{"type":60,"tag":499,"props":1652,"children":1653},{"style":512},[1654],{"type":66,"value":666},{"type":60,"tag":499,"props":1656,"children":1657},{"class":501,"line":550},[1658,1663],{"type":60,"tag":499,"props":1659,"children":1660},{"style":518},[1661],{"type":66,"value":1662},"  chat",{"type":60,"tag":499,"props":1664,"children":1665},{"style":512},[1666],{"type":66,"value":741},{"type":60,"tag":499,"props":1668,"children":1669},{"class":501,"line":592},[1670,1675],{"type":60,"tag":499,"props":1671,"children":1672},{"style":518},[1673],{"type":66,"value":1674},"  chatParamsFromRequest",{"type":60,"tag":499,"props":1676,"children":1677},{"style":512},[1678],{"type":66,"value":741},{"type":60,"tag":499,"props":1680,"children":1681},{"class":501,"line":602},[1682,1687],{"type":60,"tag":499,"props":1683,"children":1684},{"style":518},[1685],{"type":66,"value":1686},"  toServerSentEventsResponse",{"type":60,"tag":499,"props":1688,"children":1689},{"style":512},[1690],{"type":66,"value":741},{"type":60,"tag":499,"props":1692,"children":1693},{"class":501,"line":612},[1694,1699,1703,1707,1712],{"type":60,"tag":499,"props":1695,"children":1696},{"style":512},[1697],{"type":66,"value":1698},"}",{"type":60,"tag":499,"props":1700,"children":1701},{"style":506},[1702],{"type":66,"value":531},{"type":60,"tag":499,"props":1704,"children":1705},{"style":512},[1706],{"type":66,"value":536},{"type":60,"tag":499,"props":1708,"children":1709},{"style":539},[1710],{"type":66,"value":1711},"@tanstack\u002Fai",{"type":60,"tag":499,"props":1713,"children":1714},{"style":512},[1715],{"type":66,"value":547},{"type":60,"tag":499,"props":1717,"children":1718},{"class":501,"line":669},[1719,1723,1727,1732,1736,1740,1744,1749],{"type":60,"tag":499,"props":1720,"children":1721},{"style":506},[1722],{"type":66,"value":509},{"type":60,"tag":499,"props":1724,"children":1725},{"style":512},[1726],{"type":66,"value":515},{"type":60,"tag":499,"props":1728,"children":1729},{"style":518},[1730],{"type":66,"value":1731}," openaiText",{"type":60,"tag":499,"props":1733,"children":1734},{"style":512},[1735],{"type":66,"value":526},{"type":60,"tag":499,"props":1737,"children":1738},{"style":506},[1739],{"type":66,"value":531},{"type":60,"tag":499,"props":1741,"children":1742},{"style":512},[1743],{"type":66,"value":536},{"type":60,"tag":499,"props":1745,"children":1746},{"style":539},[1747],{"type":66,"value":1748},"@tanstack\u002Fai-openai",{"type":60,"tag":499,"props":1750,"children":1751},{"style":512},[1752],{"type":66,"value":547},{"type":60,"tag":499,"props":1754,"children":1755},{"class":501,"line":692},[1756,1760,1764,1769,1773,1777,1781,1785],{"type":60,"tag":499,"props":1757,"children":1758},{"style":506},[1759],{"type":66,"value":509},{"type":60,"tag":499,"props":1761,"children":1762},{"style":512},[1763],{"type":66,"value":515},{"type":60,"tag":499,"props":1765,"children":1766},{"style":518},[1767],{"type":66,"value":1768}," withPersistence",{"type":60,"tag":499,"props":1770,"children":1771},{"style":512},[1772],{"type":66,"value":526},{"type":60,"tag":499,"props":1774,"children":1775},{"style":506},[1776],{"type":66,"value":531},{"type":60,"tag":499,"props":1778,"children":1779},{"style":512},[1780],{"type":66,"value":536},{"type":60,"tag":499,"props":1782,"children":1783},{"style":539},[1784],{"type":66,"value":542},{"type":60,"tag":499,"props":1786,"children":1787},{"style":512},[1788],{"type":66,"value":547},{"type":60,"tag":499,"props":1790,"children":1791},{"class":501,"line":709},[1792,1796,1800,1804,1808,1812,1816,1821],{"type":60,"tag":499,"props":1793,"children":1794},{"style":506},[1795],{"type":66,"value":509},{"type":60,"tag":499,"props":1797,"children":1798},{"style":512},[1799],{"type":66,"value":515},{"type":60,"tag":499,"props":1801,"children":1802},{"style":518},[1803],{"type":66,"value":630},{"type":60,"tag":499,"props":1805,"children":1806},{"style":512},[1807],{"type":66,"value":526},{"type":60,"tag":499,"props":1809,"children":1810},{"style":506},[1811],{"type":66,"value":531},{"type":60,"tag":499,"props":1813,"children":1814},{"style":512},[1815],{"type":66,"value":536},{"type":60,"tag":499,"props":1817,"children":1818},{"style":539},[1819],{"type":66,"value":1820},".\u002Flib\u002Fchat-persistence",{"type":60,"tag":499,"props":1822,"children":1823},{"style":512},[1824],{"type":66,"value":547},{"type":60,"tag":499,"props":1826,"children":1827},{"class":501,"line":744},[1828],{"type":60,"tag":499,"props":1829,"children":1830},{"emptyLinePlaceholder":596},[1831],{"type":66,"value":599},{"type":60,"tag":499,"props":1833,"children":1834},{"class":501,"line":778},[1835,1839,1844],{"type":60,"tag":499,"props":1836,"children":1837},{"style":506},[1838],{"type":66,"value":618},{"type":60,"tag":499,"props":1840,"children":1841},{"style":506},[1842],{"type":66,"value":1843}," default",{"type":60,"tag":499,"props":1845,"children":1846},{"style":512},[1847],{"type":66,"value":666},{"type":60,"tag":499,"props":1849,"children":1850},{"class":501,"line":812},[1851,1856,1861,1865,1870,1874,1879,1884,1889,1893,1898,1902],{"type":60,"tag":499,"props":1852,"children":1853},{"style":621},[1854],{"type":66,"value":1855},"  async",{"type":60,"tag":499,"props":1857,"children":1858},{"style":682},[1859],{"type":66,"value":1860}," fetch",{"type":60,"tag":499,"props":1862,"children":1863},{"style":512},[1864],{"type":66,"value":635},{"type":60,"tag":499,"props":1866,"children":1867},{"style":638},[1868],{"type":66,"value":1869},"request",{"type":60,"tag":499,"props":1871,"children":1872},{"style":512},[1873],{"type":66,"value":646},{"type":60,"tag":499,"props":1875,"children":1876},{"style":649},[1877],{"type":66,"value":1878}," Request",{"type":60,"tag":499,"props":1880,"children":1881},{"style":512},[1882],{"type":66,"value":1883},",",{"type":60,"tag":499,"props":1885,"children":1886},{"style":638},[1887],{"type":66,"value":1888}," env",{"type":60,"tag":499,"props":1890,"children":1891},{"style":512},[1892],{"type":66,"value":646},{"type":60,"tag":499,"props":1894,"children":1895},{"style":649},[1896],{"type":66,"value":1897}," Env",{"type":60,"tag":499,"props":1899,"children":1900},{"style":512},[1901],{"type":66,"value":275},{"type":60,"tag":499,"props":1903,"children":1904},{"style":512},[1905],{"type":66,"value":666},{"type":60,"tag":499,"props":1907,"children":1908},{"class":501,"line":846},[1909,1914,1919,1924,1929,1934,1938,1942],{"type":60,"tag":499,"props":1910,"children":1911},{"style":621},[1912],{"type":66,"value":1913},"    const",{"type":60,"tag":499,"props":1915,"children":1916},{"style":518},[1917],{"type":66,"value":1918}," params",{"type":60,"tag":499,"props":1920,"children":1921},{"style":512},[1922],{"type":66,"value":1923}," =",{"type":60,"tag":499,"props":1925,"children":1926},{"style":506},[1927],{"type":66,"value":1928}," await",{"type":60,"tag":499,"props":1930,"children":1931},{"style":627},[1932],{"type":66,"value":1933}," chatParamsFromRequest",{"type":60,"tag":499,"props":1935,"children":1936},{"style":682},[1937],{"type":66,"value":635},{"type":60,"tag":499,"props":1939,"children":1940},{"style":518},[1941],{"type":66,"value":1869},{"type":60,"tag":499,"props":1943,"children":1944},{"style":682},[1945],{"type":66,"value":866},{"type":60,"tag":499,"props":1947,"children":1948},{"class":501,"line":855},[1949,1953,1958,1962,1967,1971],{"type":60,"tag":499,"props":1950,"children":1951},{"style":621},[1952],{"type":66,"value":1913},{"type":60,"tag":499,"props":1954,"children":1955},{"style":518},[1956],{"type":66,"value":1957}," stream",{"type":60,"tag":499,"props":1959,"children":1960},{"style":512},[1961],{"type":66,"value":1923},{"type":60,"tag":499,"props":1963,"children":1964},{"style":627},[1965],{"type":66,"value":1966}," chat",{"type":60,"tag":499,"props":1968,"children":1969},{"style":682},[1970],{"type":66,"value":635},{"type":60,"tag":499,"props":1972,"children":1973},{"style":512},[1974],{"type":66,"value":689},{"type":60,"tag":499,"props":1976,"children":1977},{"class":501,"line":869},[1978,1983,1987,1991,1995,2000,2005,2009,2013],{"type":60,"tag":499,"props":1979,"children":1980},{"style":682},[1981],{"type":66,"value":1982},"      adapter",{"type":60,"tag":499,"props":1984,"children":1985},{"style":512},[1986],{"type":66,"value":646},{"type":60,"tag":499,"props":1988,"children":1989},{"style":627},[1990],{"type":66,"value":1731},{"type":60,"tag":499,"props":1992,"children":1993},{"style":682},[1994],{"type":66,"value":635},{"type":60,"tag":499,"props":1996,"children":1997},{"style":512},[1998],{"type":66,"value":1999},"'",{"type":60,"tag":499,"props":2001,"children":2002},{"style":539},[2003],{"type":66,"value":2004},"gpt-5.5",{"type":60,"tag":499,"props":2006,"children":2007},{"style":512},[2008],{"type":66,"value":1999},{"type":60,"tag":499,"props":2010,"children":2011},{"style":682},[2012],{"type":66,"value":275},{"type":60,"tag":499,"props":2014,"children":2015},{"style":512},[2016],{"type":66,"value":741},{"type":60,"tag":499,"props":2018,"children":2019},{"class":501,"line":1438},[2020,2024,2028,2032,2036,2040],{"type":60,"tag":499,"props":2021,"children":2022},{"style":682},[2023],{"type":66,"value":715},{"type":60,"tag":499,"props":2025,"children":2026},{"style":512},[2027],{"type":66,"value":646},{"type":60,"tag":499,"props":2029,"children":2030},{"style":518},[2031],{"type":66,"value":1918},{"type":60,"tag":499,"props":2033,"children":2034},{"style":512},[2035],{"type":66,"value":114},{"type":60,"tag":499,"props":2037,"children":2038},{"style":518},[2039],{"type":66,"value":1062},{"type":60,"tag":499,"props":2041,"children":2042},{"style":512},[2043],{"type":66,"value":741},{"type":60,"tag":499,"props":2045,"children":2046},{"class":501,"line":1447},[2047,2052,2056,2060,2064,2069],{"type":60,"tag":499,"props":2048,"children":2049},{"style":682},[2050],{"type":66,"value":2051},"      threadId",{"type":60,"tag":499,"props":2053,"children":2054},{"style":512},[2055],{"type":66,"value":646},{"type":60,"tag":499,"props":2057,"children":2058},{"style":518},[2059],{"type":66,"value":1918},{"type":60,"tag":499,"props":2061,"children":2062},{"style":512},[2063],{"type":66,"value":114},{"type":60,"tag":499,"props":2065,"children":2066},{"style":518},[2067],{"type":66,"value":2068},"threadId",{"type":60,"tag":499,"props":2070,"children":2071},{"style":512},[2072],{"type":66,"value":741},{"type":60,"tag":499,"props":2074,"children":2075},{"class":501,"line":1456},[2076,2081,2085,2089,2093,2098],{"type":60,"tag":499,"props":2077,"children":2078},{"style":682},[2079],{"type":66,"value":2080},"      runId",{"type":60,"tag":499,"props":2082,"children":2083},{"style":512},[2084],{"type":66,"value":646},{"type":60,"tag":499,"props":2086,"children":2087},{"style":518},[2088],{"type":66,"value":1918},{"type":60,"tag":499,"props":2090,"children":2091},{"style":512},[2092],{"type":66,"value":114},{"type":60,"tag":499,"props":2094,"children":2095},{"style":518},[2096],{"type":66,"value":2097},"runId",{"type":60,"tag":499,"props":2099,"children":2100},{"style":512},[2101],{"type":66,"value":741},{"type":60,"tag":499,"props":2103,"children":2104},{"class":501,"line":1465},[2105,2110,2114,2119,2123,2128,2133,2137,2142,2146,2150,2154,2158,2162,2167,2172,2176],{"type":60,"tag":499,"props":2106,"children":2107},{"style":512},[2108],{"type":66,"value":2109},"      ...",{"type":60,"tag":499,"props":2111,"children":2112},{"style":682},[2113],{"type":66,"value":635},{"type":60,"tag":499,"props":2115,"children":2116},{"style":518},[2117],{"type":66,"value":2118},"params",{"type":60,"tag":499,"props":2120,"children":2121},{"style":512},[2122],{"type":66,"value":114},{"type":60,"tag":499,"props":2124,"children":2125},{"style":518},[2126],{"type":66,"value":2127},"resume",{"type":60,"tag":499,"props":2129,"children":2130},{"style":512},[2131],{"type":66,"value":2132}," ?",{"type":60,"tag":499,"props":2134,"children":2135},{"style":512},[2136],{"type":66,"value":515},{"type":60,"tag":499,"props":2138,"children":2139},{"style":682},[2140],{"type":66,"value":2141}," resume",{"type":60,"tag":499,"props":2143,"children":2144},{"style":512},[2145],{"type":66,"value":646},{"type":60,"tag":499,"props":2147,"children":2148},{"style":518},[2149],{"type":66,"value":1918},{"type":60,"tag":499,"props":2151,"children":2152},{"style":512},[2153],{"type":66,"value":114},{"type":60,"tag":499,"props":2155,"children":2156},{"style":518},[2157],{"type":66,"value":2127},{"type":60,"tag":499,"props":2159,"children":2160},{"style":512},[2161],{"type":66,"value":526},{"type":60,"tag":499,"props":2163,"children":2164},{"style":512},[2165],{"type":66,"value":2166}," :",{"type":60,"tag":499,"props":2168,"children":2169},{"style":512},[2170],{"type":66,"value":2171}," {}",{"type":60,"tag":499,"props":2173,"children":2174},{"style":682},[2175],{"type":66,"value":275},{"type":60,"tag":499,"props":2177,"children":2178},{"style":512},[2179],{"type":66,"value":741},{"type":60,"tag":499,"props":2181,"children":2182},{"class":501,"line":1474},[2183,2188,2192,2197,2201,2205,2210,2214,2218,2222,2227,2232],{"type":60,"tag":499,"props":2184,"children":2185},{"style":682},[2186],{"type":66,"value":2187},"      middleware",{"type":60,"tag":499,"props":2189,"children":2190},{"style":512},[2191],{"type":66,"value":646},{"type":60,"tag":499,"props":2193,"children":2194},{"style":682},[2195],{"type":66,"value":2196}," [",{"type":60,"tag":499,"props":2198,"children":2199},{"style":627},[2200],{"type":66,"value":901},{"type":60,"tag":499,"props":2202,"children":2203},{"style":682},[2204],{"type":66,"value":635},{"type":60,"tag":499,"props":2206,"children":2207},{"style":627},[2208],{"type":66,"value":2209},"chatPersistence",{"type":60,"tag":499,"props":2211,"children":2212},{"style":682},[2213],{"type":66,"value":635},{"type":60,"tag":499,"props":2215,"children":2216},{"style":518},[2217],{"type":66,"value":244},{"type":60,"tag":499,"props":2219,"children":2220},{"style":512},[2221],{"type":66,"value":114},{"type":60,"tag":499,"props":2223,"children":2224},{"style":518},[2225],{"type":66,"value":2226},"DB",{"type":60,"tag":499,"props":2228,"children":2229},{"style":682},[2230],{"type":66,"value":2231},"))]",{"type":60,"tag":499,"props":2233,"children":2234},{"style":512},[2235],{"type":66,"value":741},{"type":60,"tag":499,"props":2237,"children":2238},{"class":501,"line":1482},[2239,2244],{"type":60,"tag":499,"props":2240,"children":2241},{"style":512},[2242],{"type":66,"value":2243},"    }",{"type":60,"tag":499,"props":2245,"children":2246},{"style":682},[2247],{"type":66,"value":866},{"type":60,"tag":499,"props":2249,"children":2250},{"class":501,"line":1490},[2251,2256,2261,2265,2270],{"type":60,"tag":499,"props":2252,"children":2253},{"style":506},[2254],{"type":66,"value":2255},"    return",{"type":60,"tag":499,"props":2257,"children":2258},{"style":627},[2259],{"type":66,"value":2260}," toServerSentEventsResponse",{"type":60,"tag":499,"props":2262,"children":2263},{"style":682},[2264],{"type":66,"value":635},{"type":60,"tag":499,"props":2266,"children":2267},{"style":518},[2268],{"type":66,"value":2269},"stream",{"type":60,"tag":499,"props":2271,"children":2272},{"style":682},[2273],{"type":66,"value":866},{"type":60,"tag":499,"props":2275,"children":2276},{"class":501,"line":1499},[2277],{"type":60,"tag":499,"props":2278,"children":2279},{"style":512},[2280],{"type":66,"value":2281},"  },\n",{"type":60,"tag":499,"props":2283,"children":2284},{"class":501,"line":1508},[2285],{"type":60,"tag":499,"props":2286,"children":2287},{"style":512},[2288],{"type":66,"value":875},{"type":60,"tag":69,"props":2290,"children":2291},{},[2292,2297,2299,2304],{"type":60,"tag":83,"props":2293,"children":2295},{"className":2294},[],[2296],{"type":66,"value":2068},{"type":66,"value":2298}," is a bare string to the stores. ",{"type":60,"tag":75,"props":2300,"children":2301},{},[2302],{"type":66,"value":2303},"Authorize thread access at the\nroute",{"type":66,"value":2305}," — derive the user from the session, never trust a client-supplied id.",{"type":60,"tag":148,"props":2307,"children":2309},{"id":2308},"_7-durable-object-lock-store-only-if-needed",[2310],{"type":66,"value":2311},"7. Durable Object lock store (only if needed)",{"type":60,"tag":69,"props":2313,"children":2314},{},[2315,2317,2323,2325,2331,2333,2339,2341,2347,2349,2354,2356,2362],{"type":66,"value":2316},"Implement ",{"type":60,"tag":83,"props":2318,"children":2320},{"className":2319},[],[2321],{"type":66,"value":2322},"LockStore",{"type":66,"value":2324}," from ",{"type":60,"tag":83,"props":2326,"children":2328},{"className":2327},[],[2329],{"type":66,"value":2330},"@tanstack\u002Fai\u002Flocks",{"type":66,"value":2332},". ",{"type":60,"tag":83,"props":2334,"children":2336},{"className":2335},[],[2337],{"type":66,"value":2338},"withLock(key, fn)",{"type":66,"value":2340},"\nroutes each key to a Durable Object instance (",{"type":60,"tag":83,"props":2342,"children":2344},{"className":2343},[],[2345],{"type":66,"value":2346},"idFromName(key)",{"type":66,"value":2348},") that serializes\nowners. Use ",{"type":60,"tag":75,"props":2350,"children":2351},{},[2352],{"type":66,"value":2353},"leases",{"type":66,"value":2355}," so a crashed owner cannot block forever: the DO grants a\nlease with an expiry, an alarm reclaims it, and the lock passes the callback an\n",{"type":60,"tag":83,"props":2357,"children":2359},{"className":2358},[],[2360],{"type":66,"value":2361},"AbortSignal",{"type":66,"value":2363}," that fires when ownership can no longer be guaranteed. Callbacks\nmust stop starting external mutations once the signal aborts.",{"type":60,"tag":69,"props":2365,"children":2366},{},[2367],{"type":66,"value":2368},"Export the DO class from the Worker entry so wrangler can bind it:",{"type":60,"tag":402,"props":2370,"children":2372},{"className":490,"code":2371,"language":492,"meta":493,"style":410},"export { ChatLockDurableObject } from '.\u002Flocks'\n",[2373],{"type":60,"tag":83,"props":2374,"children":2375},{"__ignoreMap":410},[2376],{"type":60,"tag":499,"props":2377,"children":2378},{"class":501,"line":502},[2379,2383,2387,2392,2396,2400,2404,2409],{"type":60,"tag":499,"props":2380,"children":2381},{"style":506},[2382],{"type":66,"value":618},{"type":60,"tag":499,"props":2384,"children":2385},{"style":512},[2386],{"type":66,"value":515},{"type":60,"tag":499,"props":2388,"children":2389},{"style":518},[2390],{"type":66,"value":2391}," ChatLockDurableObject",{"type":60,"tag":499,"props":2393,"children":2394},{"style":512},[2395],{"type":66,"value":526},{"type":60,"tag":499,"props":2397,"children":2398},{"style":506},[2399],{"type":66,"value":531},{"type":60,"tag":499,"props":2401,"children":2402},{"style":512},[2403],{"type":66,"value":536},{"type":60,"tag":499,"props":2405,"children":2406},{"style":539},[2407],{"type":66,"value":2408},".\u002Flocks",{"type":60,"tag":499,"props":2410,"children":2411},{"style":512},[2412],{"type":66,"value":547},{"type":60,"tag":69,"props":2414,"children":2415},{},[2416],{"type":66,"value":2417},"Then wire both middlewares:",{"type":60,"tag":402,"props":2419,"children":2421},{"className":490,"code":2420,"language":492,"meta":493,"style":410},"import { withLocks } from '@tanstack\u002Fai\u002Flocks'\nimport { withPersistence } from '@tanstack\u002Fai-persistence'\n\nconst middleware = [\n  withPersistence(chatPersistence(env.AI_STATE)),\n  withLocks(createDurableObjectLockStore(env.AI_LOCKS)),\n]\n",[2422],{"type":60,"tag":83,"props":2423,"children":2424},{"__ignoreMap":410},[2425,2461,2496,2503,2526,2560,2594],{"type":60,"tag":499,"props":2426,"children":2427},{"class":501,"line":502},[2428,2432,2436,2441,2445,2449,2453,2457],{"type":60,"tag":499,"props":2429,"children":2430},{"style":506},[2431],{"type":66,"value":509},{"type":60,"tag":499,"props":2433,"children":2434},{"style":512},[2435],{"type":66,"value":515},{"type":60,"tag":499,"props":2437,"children":2438},{"style":518},[2439],{"type":66,"value":2440}," withLocks",{"type":60,"tag":499,"props":2442,"children":2443},{"style":512},[2444],{"type":66,"value":526},{"type":60,"tag":499,"props":2446,"children":2447},{"style":506},[2448],{"type":66,"value":531},{"type":60,"tag":499,"props":2450,"children":2451},{"style":512},[2452],{"type":66,"value":536},{"type":60,"tag":499,"props":2454,"children":2455},{"style":539},[2456],{"type":66,"value":2330},{"type":60,"tag":499,"props":2458,"children":2459},{"style":512},[2460],{"type":66,"value":547},{"type":60,"tag":499,"props":2462,"children":2463},{"class":501,"line":550},[2464,2468,2472,2476,2480,2484,2488,2492],{"type":60,"tag":499,"props":2465,"children":2466},{"style":506},[2467],{"type":66,"value":509},{"type":60,"tag":499,"props":2469,"children":2470},{"style":512},[2471],{"type":66,"value":515},{"type":60,"tag":499,"props":2473,"children":2474},{"style":518},[2475],{"type":66,"value":1768},{"type":60,"tag":499,"props":2477,"children":2478},{"style":512},[2479],{"type":66,"value":526},{"type":60,"tag":499,"props":2481,"children":2482},{"style":506},[2483],{"type":66,"value":531},{"type":60,"tag":499,"props":2485,"children":2486},{"style":512},[2487],{"type":66,"value":536},{"type":60,"tag":499,"props":2489,"children":2490},{"style":539},[2491],{"type":66,"value":542},{"type":60,"tag":499,"props":2493,"children":2494},{"style":512},[2495],{"type":66,"value":547},{"type":60,"tag":499,"props":2497,"children":2498},{"class":501,"line":592},[2499],{"type":60,"tag":499,"props":2500,"children":2501},{"emptyLinePlaceholder":596},[2502],{"type":66,"value":599},{"type":60,"tag":499,"props":2504,"children":2505},{"class":501,"line":602},[2506,2511,2516,2521],{"type":60,"tag":499,"props":2507,"children":2508},{"style":621},[2509],{"type":66,"value":2510},"const",{"type":60,"tag":499,"props":2512,"children":2513},{"style":518},[2514],{"type":66,"value":2515}," middleware ",{"type":60,"tag":499,"props":2517,"children":2518},{"style":512},[2519],{"type":66,"value":2520},"=",{"type":60,"tag":499,"props":2522,"children":2523},{"style":518},[2524],{"type":66,"value":2525}," [\n",{"type":60,"tag":499,"props":2527,"children":2528},{"class":501,"line":612},[2529,2534,2538,2542,2547,2551,2556],{"type":60,"tag":499,"props":2530,"children":2531},{"style":627},[2532],{"type":66,"value":2533},"  withPersistence",{"type":60,"tag":499,"props":2535,"children":2536},{"style":518},[2537],{"type":66,"value":635},{"type":60,"tag":499,"props":2539,"children":2540},{"style":627},[2541],{"type":66,"value":2209},{"type":60,"tag":499,"props":2543,"children":2544},{"style":518},[2545],{"type":66,"value":2546},"(env",{"type":60,"tag":499,"props":2548,"children":2549},{"style":512},[2550],{"type":66,"value":114},{"type":60,"tag":499,"props":2552,"children":2553},{"style":518},[2554],{"type":66,"value":2555},"AI_STATE))",{"type":60,"tag":499,"props":2557,"children":2558},{"style":512},[2559],{"type":66,"value":741},{"type":60,"tag":499,"props":2561,"children":2562},{"class":501,"line":669},[2563,2568,2572,2577,2581,2585,2590],{"type":60,"tag":499,"props":2564,"children":2565},{"style":627},[2566],{"type":66,"value":2567},"  withLocks",{"type":60,"tag":499,"props":2569,"children":2570},{"style":518},[2571],{"type":66,"value":635},{"type":60,"tag":499,"props":2573,"children":2574},{"style":627},[2575],{"type":66,"value":2576},"createDurableObjectLockStore",{"type":60,"tag":499,"props":2578,"children":2579},{"style":518},[2580],{"type":66,"value":2546},{"type":60,"tag":499,"props":2582,"children":2583},{"style":512},[2584],{"type":66,"value":114},{"type":60,"tag":499,"props":2586,"children":2587},{"style":518},[2588],{"type":66,"value":2589},"AI_LOCKS))",{"type":60,"tag":499,"props":2591,"children":2592},{"style":512},[2593],{"type":66,"value":741},{"type":60,"tag":499,"props":2595,"children":2596},{"class":501,"line":692},[2597],{"type":60,"tag":499,"props":2598,"children":2599},{"style":518},[2600],{"type":66,"value":2601},"]\n",{"type":60,"tag":148,"props":2603,"children":2605},{"id":2604},"wrangler-bindings",[2606],{"type":66,"value":2607},"wrangler bindings",{"type":60,"tag":402,"props":2609,"children":2613},{"className":2610,"code":2611,"language":2612,"meta":410,"style":410},"language-jsonc shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"d1_databases\": [\n    {\n      \"binding\": \"AI_STATE\",\n      \"database_name\": \"tanstack-ai-state\",\n      \"database_id\": \"\u003Cid>\",\n      \"migrations_dir\": \"migrations\",\n    },\n  ],\n  \"durable_objects\": {\n    \"bindings\": [{ \"name\": \"AI_LOCKS\", \"class_name\": \"ChatLockDurableObject\" }],\n  },\n  \"migrations\": [\n    { \"tag\": \"v1\", \"new_sqlite_classes\": [\"ChatLockDurableObject\"] },\n  ],\n}\n","jsonc",[2614],{"type":60,"tag":83,"props":2615,"children":2616},{"__ignoreMap":410},[2617,2624,2632,2640,2648,2656,2664,2672,2679,2687,2695,2703,2710,2718,2726,2733],{"type":60,"tag":499,"props":2618,"children":2619},{"class":501,"line":502},[2620],{"type":60,"tag":499,"props":2621,"children":2622},{},[2623],{"type":66,"value":689},{"type":60,"tag":499,"props":2625,"children":2626},{"class":501,"line":550},[2627],{"type":60,"tag":499,"props":2628,"children":2629},{},[2630],{"type":66,"value":2631},"  \"d1_databases\": [\n",{"type":60,"tag":499,"props":2633,"children":2634},{"class":501,"line":592},[2635],{"type":60,"tag":499,"props":2636,"children":2637},{},[2638],{"type":66,"value":2639},"    {\n",{"type":60,"tag":499,"props":2641,"children":2642},{"class":501,"line":602},[2643],{"type":60,"tag":499,"props":2644,"children":2645},{},[2646],{"type":66,"value":2647},"      \"binding\": \"AI_STATE\",\n",{"type":60,"tag":499,"props":2649,"children":2650},{"class":501,"line":612},[2651],{"type":60,"tag":499,"props":2652,"children":2653},{},[2654],{"type":66,"value":2655},"      \"database_name\": \"tanstack-ai-state\",\n",{"type":60,"tag":499,"props":2657,"children":2658},{"class":501,"line":669},[2659],{"type":60,"tag":499,"props":2660,"children":2661},{},[2662],{"type":66,"value":2663},"      \"database_id\": \"\u003Cid>\",\n",{"type":60,"tag":499,"props":2665,"children":2666},{"class":501,"line":692},[2667],{"type":60,"tag":499,"props":2668,"children":2669},{},[2670],{"type":66,"value":2671},"      \"migrations_dir\": \"migrations\",\n",{"type":60,"tag":499,"props":2673,"children":2674},{"class":501,"line":709},[2675],{"type":60,"tag":499,"props":2676,"children":2677},{},[2678],{"type":66,"value":852},{"type":60,"tag":499,"props":2680,"children":2681},{"class":501,"line":744},[2682],{"type":60,"tag":499,"props":2683,"children":2684},{},[2685],{"type":66,"value":2686},"  ],\n",{"type":60,"tag":499,"props":2688,"children":2689},{"class":501,"line":778},[2690],{"type":60,"tag":499,"props":2691,"children":2692},{},[2693],{"type":66,"value":2694},"  \"durable_objects\": {\n",{"type":60,"tag":499,"props":2696,"children":2697},{"class":501,"line":812},[2698],{"type":60,"tag":499,"props":2699,"children":2700},{},[2701],{"type":66,"value":2702},"    \"bindings\": [{ \"name\": \"AI_LOCKS\", \"class_name\": \"ChatLockDurableObject\" }],\n",{"type":60,"tag":499,"props":2704,"children":2705},{"class":501,"line":846},[2706],{"type":60,"tag":499,"props":2707,"children":2708},{},[2709],{"type":66,"value":2281},{"type":60,"tag":499,"props":2711,"children":2712},{"class":501,"line":855},[2713],{"type":60,"tag":499,"props":2714,"children":2715},{},[2716],{"type":66,"value":2717},"  \"migrations\": [\n",{"type":60,"tag":499,"props":2719,"children":2720},{"class":501,"line":869},[2721],{"type":60,"tag":499,"props":2722,"children":2723},{},[2724],{"type":66,"value":2725},"    { \"tag\": \"v1\", \"new_sqlite_classes\": [\"ChatLockDurableObject\"] },\n",{"type":60,"tag":499,"props":2727,"children":2728},{"class":501,"line":1438},[2729],{"type":60,"tag":499,"props":2730,"children":2731},{},[2732],{"type":66,"value":2686},{"type":60,"tag":499,"props":2734,"children":2735},{"class":501,"line":1447},[2736],{"type":60,"tag":499,"props":2737,"children":2738},{},[2739],{"type":66,"value":875},{"type":60,"tag":69,"props":2741,"children":2742},{},[2743],{"type":66,"value":2744},"Durable Object locks do not use the D1 table migration set; their state is\nconfigured through the migration tags above.",{"type":60,"tag":148,"props":2746,"children":2748},{"id":2747},"verify",[2749],{"type":66,"value":2750},"Verify",{"type":60,"tag":402,"props":2752,"children":2754},{"className":490,"code":2753,"language":492,"meta":493,"style":410},"import { runPersistenceConformance } from '@tanstack\u002Fai-persistence\u002Ftestkit'\nimport { env } from 'cloudflare:test'\nimport { chatPersistence } from '..\u002Fsrc\u002Flib\u002Fchat-persistence'\n\nrunPersistenceConformance('app-d1', () => chatPersistence(env.AI_STATE))\n",[2755],{"type":60,"tag":83,"props":2756,"children":2757},{"__ignoreMap":410},[2758,2795,2831,2867,2874],{"type":60,"tag":499,"props":2759,"children":2760},{"class":501,"line":502},[2761,2765,2769,2774,2778,2782,2786,2791],{"type":60,"tag":499,"props":2762,"children":2763},{"style":506},[2764],{"type":66,"value":509},{"type":60,"tag":499,"props":2766,"children":2767},{"style":512},[2768],{"type":66,"value":515},{"type":60,"tag":499,"props":2770,"children":2771},{"style":518},[2772],{"type":66,"value":2773}," runPersistenceConformance",{"type":60,"tag":499,"props":2775,"children":2776},{"style":512},[2777],{"type":66,"value":526},{"type":60,"tag":499,"props":2779,"children":2780},{"style":506},[2781],{"type":66,"value":531},{"type":60,"tag":499,"props":2783,"children":2784},{"style":512},[2785],{"type":66,"value":536},{"type":60,"tag":499,"props":2787,"children":2788},{"style":539},[2789],{"type":66,"value":2790},"@tanstack\u002Fai-persistence\u002Ftestkit",{"type":60,"tag":499,"props":2792,"children":2793},{"style":512},[2794],{"type":66,"value":547},{"type":60,"tag":499,"props":2796,"children":2797},{"class":501,"line":550},[2798,2802,2806,2810,2814,2818,2822,2827],{"type":60,"tag":499,"props":2799,"children":2800},{"style":506},[2801],{"type":66,"value":509},{"type":60,"tag":499,"props":2803,"children":2804},{"style":512},[2805],{"type":66,"value":515},{"type":60,"tag":499,"props":2807,"children":2808},{"style":518},[2809],{"type":66,"value":1888},{"type":60,"tag":499,"props":2811,"children":2812},{"style":512},[2813],{"type":66,"value":526},{"type":60,"tag":499,"props":2815,"children":2816},{"style":506},[2817],{"type":66,"value":531},{"type":60,"tag":499,"props":2819,"children":2820},{"style":512},[2821],{"type":66,"value":536},{"type":60,"tag":499,"props":2823,"children":2824},{"style":539},[2825],{"type":66,"value":2826},"cloudflare:test",{"type":60,"tag":499,"props":2828,"children":2829},{"style":512},[2830],{"type":66,"value":547},{"type":60,"tag":499,"props":2832,"children":2833},{"class":501,"line":592},[2834,2838,2842,2846,2850,2854,2858,2863],{"type":60,"tag":499,"props":2835,"children":2836},{"style":506},[2837],{"type":66,"value":509},{"type":60,"tag":499,"props":2839,"children":2840},{"style":512},[2841],{"type":66,"value":515},{"type":60,"tag":499,"props":2843,"children":2844},{"style":518},[2845],{"type":66,"value":630},{"type":60,"tag":499,"props":2847,"children":2848},{"style":512},[2849],{"type":66,"value":526},{"type":60,"tag":499,"props":2851,"children":2852},{"style":506},[2853],{"type":66,"value":531},{"type":60,"tag":499,"props":2855,"children":2856},{"style":512},[2857],{"type":66,"value":536},{"type":60,"tag":499,"props":2859,"children":2860},{"style":539},[2861],{"type":66,"value":2862},"..\u002Fsrc\u002Flib\u002Fchat-persistence",{"type":60,"tag":499,"props":2864,"children":2865},{"style":512},[2866],{"type":66,"value":547},{"type":60,"tag":499,"props":2868,"children":2869},{"class":501,"line":602},[2870],{"type":60,"tag":499,"props":2871,"children":2872},{"emptyLinePlaceholder":596},[2873],{"type":66,"value":599},{"type":60,"tag":499,"props":2875,"children":2876},{"class":501,"line":612},[2877,2882,2886,2890,2895,2899,2903,2908,2913,2917,2921,2925],{"type":60,"tag":499,"props":2878,"children":2879},{"style":627},[2880],{"type":66,"value":2881},"runPersistenceConformance",{"type":60,"tag":499,"props":2883,"children":2884},{"style":518},[2885],{"type":66,"value":635},{"type":60,"tag":499,"props":2887,"children":2888},{"style":512},[2889],{"type":66,"value":1999},{"type":60,"tag":499,"props":2891,"children":2892},{"style":539},[2893],{"type":66,"value":2894},"app-d1",{"type":60,"tag":499,"props":2896,"children":2897},{"style":512},[2898],{"type":66,"value":1999},{"type":60,"tag":499,"props":2900,"children":2901},{"style":512},[2902],{"type":66,"value":1883},{"type":60,"tag":499,"props":2904,"children":2905},{"style":512},[2906],{"type":66,"value":2907}," ()",{"type":60,"tag":499,"props":2909,"children":2910},{"style":621},[2911],{"type":66,"value":2912}," =>",{"type":60,"tag":499,"props":2914,"children":2915},{"style":627},[2916],{"type":66,"value":630},{"type":60,"tag":499,"props":2918,"children":2919},{"style":518},[2920],{"type":66,"value":2546},{"type":60,"tag":499,"props":2922,"children":2923},{"style":512},[2924],{"type":66,"value":114},{"type":60,"tag":499,"props":2926,"children":2927},{"style":518},[2928],{"type":66,"value":2929},"AI_STATE))\n",{"type":60,"tag":69,"props":2931,"children":2932},{},[2933,2935,2941,2943,2948,2950,2956],{"type":66,"value":2934},"Run it against a Miniflare D1 binding with the migration applied, reset between\nruns. All four state stores are provided, so pass no ",{"type":60,"tag":83,"props":2936,"children":2938},{"className":2937},[],[2939],{"type":66,"value":2940},"skip",{"type":66,"value":2942}," — and ",{"type":60,"tag":83,"props":2944,"children":2946},{"className":2945},[],[2947],{"type":66,"value":2940},{"type":66,"value":2949}," never\naccepts ",{"type":60,"tag":83,"props":2951,"children":2953},{"className":2952},[],[2954],{"type":66,"value":2955},"'locks'",{"type":66,"value":2957},": the suite covers state only.",{"type":60,"tag":69,"props":2959,"children":2960},{},[2961,2963,2968,2970,2976],{"type":66,"value":2962},"The lock store needs its ",{"type":60,"tag":75,"props":2964,"children":2965},{},[2966],{"type":66,"value":2967},"own",{"type":66,"value":2969}," tests, because nothing in the conformance suite\ntouches it. Cover at minimum: two concurrent ",{"type":60,"tag":83,"props":2971,"children":2973},{"className":2972},[],[2974],{"type":66,"value":2975},"withLock",{"type":66,"value":2977}," calls on the same key\nserialize; different keys do not block each other; a lease that expires aborts\nthe signal handed to the critical section; and a callback that throws still\nreleases the lock.",{"type":60,"tag":148,"props":2979,"children":2981},{"id":2980},"only-if-you-are-publishing-this-as-a-package",[2982],{"type":66,"value":2983},"Only if you are publishing this as a package",{"type":60,"tag":69,"props":2985,"children":2986},{},[2987,2989,2995,2997,3003,3005,3011,3013,3018],{"type":66,"value":2988},"For a reusable npm adapter rather than a file in the app: peer-dep\n",{"type":60,"tag":83,"props":2990,"children":2992},{"className":2991},[],[2993],{"type":66,"value":2994},"@cloudflare\u002Fworkers-types >=4.x",{"type":66,"value":2996},", and prepend\n",{"type":60,"tag":83,"props":2998,"children":3000},{"className":2999},[],[3001],{"type":66,"value":3002},"\u002F\u002F\u002F \u003Creference types=\"@cloudflare\u002Fworkers-types\" \u002F>",{"type":66,"value":3004}," to the generated\n",{"type":60,"tag":83,"props":3006,"children":3008},{"className":3007},[],[3009],{"type":66,"value":3010},"index.d.ts",{"type":66,"value":3012}," so consumers get the D1\u002FDurableObject types. Emit the table SQL\ninto the consumer's ",{"type":60,"tag":83,"props":3014,"children":3016},{"className":3015},[],[3017],{"type":66,"value":104},{"type":66,"value":3019}," directory rather than shipping a runner, and\nif you offer both raw-D1 and Drizzle paths, guard with a test that the emitted\nSQL and the Drizzle tables describe the same schema.",{"type":60,"tag":3021,"props":3022,"children":3023},"style",{},[3024],{"type":66,"value":3025},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"items":3027,"total":1543},[3028,3046,3061,3076,3089,3103,3115],{"slug":3029,"name":3029,"fn":3030,"description":3031,"org":3032,"tags":3033,"stars":24,"repoUrl":25,"updatedAt":3045},"ai-code-mode","execute sandboxed TypeScript code with LLMs","LLM-generated TypeScript execution in sandboxed environments: createCodeModeTool() with isolate drivers (createNodeIsolateDriver, createQuickJSIsolateDriver, createCloudflareIsolateDriver), codeModeWithSkills() for persistent skill libraries, trust strategies, skill storage (FileSystem, LocalStorage, InMemory, Mongo), client-side execution progress via code_mode:* custom events in useChat.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3034,3036,3039,3042,3043],{"name":3035,"slug":32,"type":16},"AI SDK",{"name":3037,"slug":3038,"type":16},"Code Execution","code-execution",{"name":3040,"slug":3041,"type":16},"Sandboxing","sandboxing",{"name":10,"slug":9,"type":16},{"name":3044,"slug":46,"type":16},"TypeScript","2026-07-16T06:04:13.597905",{"slug":3047,"name":3047,"fn":3048,"description":3049,"org":3050,"tags":3051,"stars":24,"repoUrl":25,"updatedAt":3060},"ai-core","configure TanStack AI agent features","Entry point for TanStack AI skills. Routes to chat-experience, tool-calling, media-generation, structured-outputs, adapter-configuration, ag-ui-protocol, middleware, locks, custom-backend-integration, and debug-logging, plus the skills shipped by companion packages (@tanstack\u002Fai-persistence, @tanstack\u002Fai-code-mode). Use chat() not streamText(), openaiText() not createOpenAI(), toServerSentEventsResponse() not manual SSE, middleware hooks not onEnd callbacks.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3052,3055,3057],{"name":3053,"slug":3054,"type":16},"Agents","agents",{"name":3056,"slug":30,"type":16},"AI",{"name":3058,"slug":3059,"type":16},"Middleware","middleware","2026-07-30T05:26:10.404565",{"slug":3062,"name":3063,"fn":3064,"description":3065,"org":3066,"tags":3067,"stars":24,"repoUrl":25,"updatedAt":3075},"ai-coreadapter-configuration","ai-core\u002Fadapter-configuration","configure AI provider adapters","Provider adapter selection and configuration: openaiText, anthropicText, geminiText, ollamaText, grokText, groqText, openRouterText, bedrockText, openaiCompatible. Per-model type safety with modelOptions, reasoning\u002Fthinking configuration, runtime adapter switching, extendAdapter() for custom models, createModel(). Generic OpenAI-compatible providers (DeepSeek, Together, Fireworks, etc.) via openaiCompatible({ baseURL, apiKey, models }) from @tanstack\u002Fai-openai\u002Fcompatible. API key env vars: OPENAI_API_KEY, ANTHROPIC_API_KEY, GOOGLE_API_KEY\u002FGEMINI_API_KEY, XAI_API_KEY, GROQ_API_KEY, OPENROUTER_API_KEY, OLLAMA_HOST, BEDROCK_API_KEY (or AWS_BEARER_TOKEN_BEDROCK).\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3068,3069,3072,3074],{"name":3035,"slug":32,"type":16},{"name":3070,"slug":3071,"type":16},"Configuration","configuration",{"name":3073,"slug":38,"type":16},"LLM",{"name":10,"slug":9,"type":16},"2026-07-16T06:04:17.82075",{"slug":3077,"name":3078,"fn":3079,"description":3080,"org":3081,"tags":3082,"stars":24,"repoUrl":25,"updatedAt":3088},"ai-coreag-ui-protocol","ai-core\u002Fag-ui-protocol","implement TanStack AI streaming protocol","Server-side AG-UI streaming protocol implementation: StreamChunk event types (RUN_STARTED, TEXT_MESSAGE_START\u002FCONTENT\u002FEND, TOOL_CALL_START\u002FARGS\u002FEND, RUN_FINISHED, RUN_ERROR, STEP_STARTED\u002FSTEP_FINISHED, STATE_SNAPSHOT\u002FDELTA, CUSTOM), toServerSentEventsStream() for SSE format, toHttpStream() for NDJSON format. For backends serving AG-UI events without client packages.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3083,3086,3087],{"name":3084,"slug":3085,"type":16},"API Development","api-development",{"name":3073,"slug":38,"type":16},{"name":10,"slug":9,"type":16},"2026-07-16T06:04:10.093367",{"slug":3090,"name":3091,"fn":3092,"description":3093,"org":3094,"tags":3095,"stars":24,"repoUrl":25,"updatedAt":3102},"ai-corechat-experience","ai-core\u002Fchat-experience","implement chat experiences with TanStack AI","End-to-end chat implementation: server endpoint with chat() and toServerSentEventsResponse(), client-side useChat hook with fetchServerSentEvents(), message rendering with UIMessage parts, multimodal content, thinking\u002Freasoning display. Covers streaming states, connection adapters, and message format conversions. NOT Vercel AI SDK — uses chat() not streamText().\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3096,3097,3100,3101],{"name":3084,"slug":3085,"type":16},{"name":3098,"slug":3099,"type":16},"Frontend","frontend",{"name":3073,"slug":38,"type":16},{"name":10,"slug":9,"type":16},"2026-07-30T05:26:11.505241",{"slug":3104,"name":3105,"fn":3106,"description":3107,"org":3108,"tags":3109,"stars":24,"repoUrl":25,"updatedAt":3114},"ai-coreclient-persistence","ai-core\u002Fclient-persistence","implement browser chat persistence for TanStack AI","Browser chat persistence on useChat \u002F ChatClient: localStoragePersistence, sessionStoragePersistence, indexedDBPersistence. Client-authoritative (adapter, full transcript) vs server-authoritative (persistence: true, no client cache). Reload restore, pending interrupts, mid-stream rejoin with delivery durability. Use for SPA reload durability — NOT server history alone. No extra package: the adapters ship in the framework packages.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3110,3111,3112,3113],{"name":3056,"slug":30,"type":16},{"name":3098,"slug":3099,"type":16},{"name":21,"slug":22,"type":16},{"name":10,"slug":9,"type":16},"2026-07-30T05:53:35.503176",{"slug":3116,"name":3117,"fn":3118,"description":3119,"org":3120,"tags":3121,"stars":24,"repoUrl":25,"updatedAt":3128},"ai-corecustom-backend-integration","ai-core\u002Fcustom-backend-integration","integrate custom backends with TanStack AI","Connect useChat to a non-TanStack-AI backend through custom connection adapters. ConnectConnectionAdapter (single async iterable) vs SubscribeConnectionAdapter (separate subscribe\u002Fsend). Customize fetchServerSentEvents() and fetchHttpStream() with auth headers, custom URLs, and request options. Import from framework package, not @tanstack\u002Fai-client.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3122,3123,3124,3127],{"name":3056,"slug":30,"type":16},{"name":3084,"slug":3085,"type":16},{"name":3125,"slug":3126,"type":16},"Backend","backend",{"name":10,"slug":9,"type":16},"2026-07-17T06:06:39.855351",{"items":3130,"total":3270},[3131,3145,3157,3169,3184,3196,3206,3216,3229,3239,3250,3260],{"slug":3132,"name":3132,"fn":3133,"description":3134,"org":3135,"tags":3136,"stars":3142,"repoUrl":3143,"updatedAt":3144},"aggregation","perform data aggregation in TanStack Table","Aggregate TanStack Table columns independently of grouping, including grand totals, caller-selected row totals, multiple keyed aggregations, custom context-based definitions, grouped merges, manual values, and worker constraints.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3137,3140,3141],{"name":3138,"slug":3139,"type":16},"Data Analysis","data-analysis",{"name":3098,"slug":3099,"type":16},{"name":10,"slug":9,"type":16},28175,"https:\u002F\u002Fgithub.com\u002FTanStack\u002Ftable","2026-07-30T05:25:59.429787",{"slug":3146,"name":3146,"fn":3147,"description":3148,"org":3149,"tags":3150,"stars":3142,"repoUrl":3143,"updatedAt":3156},"api-not-found","diagnose TanStack Table API errors","Diagnose missing TanStack Table v9 exports, options, state slices, and instance methods. Load before inventing an API when code sees a type error, undefined feature method, absent object key, adapter mismatch, or v8-shaped example.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3151,3154,3155],{"name":3152,"slug":3153,"type":16},"Debugging","debugging",{"name":3098,"slug":3099,"type":16},{"name":10,"slug":9,"type":16},"2026-07-30T05:26:05.418735",{"slug":3158,"name":3158,"fn":3159,"description":3160,"org":3161,"tags":3162,"stars":3142,"repoUrl":3143,"updatedAt":3168},"cell-selection","select rectangular cell ranges in tables","Select rectangular cell ranges with cellSelectionFeature: two-corner range state keyed by row and column id, mousedown\u002Fmouseenter handlers, selection edges, render-order resolution under pinning, and autoResetCellSelection. Load when ranges widen unexpectedly after sorting or column reordering, when a drag re-renders the whole table, or when building copy-to-clipboard from a selection.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3163,3164,3165],{"name":3138,"slug":3139,"type":16},{"name":10,"slug":9,"type":16},{"name":3166,"slug":3167,"type":16},"UI Components","ui-components","2026-07-30T05:25:38.403427",{"slug":3170,"name":3170,"fn":3171,"description":3172,"org":3173,"tags":3174,"stars":3142,"repoUrl":3143,"updatedAt":3183},"client-vs-server","manage TanStack Table data pipelines","Choose client or server ownership for filtering, grouping, sorting, expanding, and pagination in TanStack Table v9. Load for manual* flags, mixed pipelines, server counts, or deciding which dataset each row-model stage receives.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3175,3178,3179,3182],{"name":3176,"slug":3177,"type":16},"Data Pipeline","data-pipeline",{"name":3098,"slug":3099,"type":16},{"name":3180,"slug":3181,"type":16},"Performance","performance",{"name":10,"slug":9,"type":16},"2026-07-30T05:25:45.400104",{"slug":3185,"name":3185,"fn":3186,"description":3187,"org":3188,"tags":3189,"stars":3142,"repoUrl":3143,"updatedAt":3195},"column-faceting","build faceted filter UIs","Build faceted filter UIs with columnFacetingFeature, facetedRowModel, facetedUniqueValues, and facetedMinMaxValues. Load for facet counts, numeric ranges, own-filter exclusion, or server-page facet completeness.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3190,3193,3194],{"name":3191,"slug":3192,"type":16},"Data Visualization","data-visualization",{"name":3098,"slug":3099,"type":16},{"name":10,"slug":9,"type":16},"2026-07-30T05:25:41.397257",{"slug":3197,"name":3197,"fn":3198,"description":3199,"org":3200,"tags":3201,"stars":3142,"repoUrl":3143,"updatedAt":3205},"column-filtering","implement column filtering in TanStack Table","Filter columns with columnFilteringFeature, filteredRowModel, filterFns, filterMeta, nested-row direction, and manualFiltering. Load for accessor compatibility, controlled filter updaters, fuzzy metadata, or client\u002Fserver ownership.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3202,3203,3204],{"name":3138,"slug":3139,"type":16},{"name":3098,"slug":3099,"type":16},{"name":10,"slug":9,"type":16},"2026-07-30T05:25:53.391632",{"slug":3207,"name":3207,"fn":3208,"description":3209,"org":3210,"tags":3211,"stars":3142,"repoUrl":3143,"updatedAt":3215},"column-ordering","manage TanStack Table column ordering","Control TanStack Table v9 leaf columnOrder with stable IDs while accounting for pinning regions, visibility, and groupedColumnMode precedence. Load for drag-and-drop columns or rendered order that differs from state.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3212,3213,3214],{"name":3098,"slug":3099,"type":16},{"name":10,"slug":9,"type":16},{"name":3166,"slug":3167,"type":16},"2026-07-30T05:26:03.37801",{"slug":3217,"name":3217,"fn":3218,"description":3219,"org":3220,"tags":3221,"stars":3142,"repoUrl":3143,"updatedAt":3228},"column-pinning","configure column pinning in TanStack Table","Pin columns into logical start, center, and end regions with columnPinningFeature and renderer-owned sticky CSS. Load for RTL offsets, z-index, backgrounds, overflow, widths, gaps, or overlaps.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3222,3225,3226,3227],{"name":3223,"slug":3224,"type":16},"CSS","css",{"name":3098,"slug":3099,"type":16},{"name":10,"slug":9,"type":16},{"name":3166,"slug":3167,"type":16},"2026-07-30T05:25:55.377366",{"slug":3230,"name":3230,"fn":3231,"description":3232,"org":3233,"tags":3234,"stars":3142,"repoUrl":3143,"updatedAt":3238},"column-resizing","implement column resizing in TanStack Table","Wire columnResizingFeature, header.getResizeHandler, resize mode and direction, pointer or touch events, and performant CSS-variable updates. Load when resize state changes but widths do not, or large tables resize slowly.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3235,3236,3237],{"name":3098,"slug":3099,"type":16},{"name":10,"slug":9,"type":16},{"name":3166,"slug":3167,"type":16},"2026-07-30T05:25:51.400011",{"slug":3240,"name":3240,"fn":3241,"description":3242,"org":3243,"tags":3244,"stars":3142,"repoUrl":3143,"updatedAt":3249},"column-sizing","configure column sizing in TanStack Table","Use columnSizingFeature numeric size, minSize, maxSize, getSize, getStart, getAfter, and total-size APIs in table, grid, or flex CSS. Load for auto or percentage misconceptions and sizing\u002Fpinning layout mismatch.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3245,3246,3247,3248],{"name":3223,"slug":3224,"type":16},{"name":3098,"slug":3099,"type":16},{"name":10,"slug":9,"type":16},{"name":3166,"slug":3167,"type":16},"2026-07-30T05:25:48.703799",{"slug":3251,"name":3251,"fn":3252,"description":3253,"org":3254,"tags":3255,"stars":3142,"repoUrl":3143,"updatedAt":3259},"column-visibility","manage column visibility in TanStack Table","Hide columns with columnVisibilityFeature while rendering visibility-aware header, column, and cell collections. Load when hidden columns remain in the DOM, false-versus-absent state is confused, or enableHiding is misunderstood.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3256,3257,3258],{"name":3098,"slug":3099,"type":16},{"name":10,"slug":9,"type":16},{"name":3166,"slug":3167,"type":16},"2026-07-30T05:25:47.367943",{"slug":3261,"name":3261,"fn":3262,"description":3263,"org":3264,"tags":3265,"stars":3142,"repoUrl":3143,"updatedAt":3269},"core","build data grids with TanStack Table","Use TanStack Table v9 as a headless data-grid state and row-processing engine. Load for first-table architecture, stable data and columns, row numbering with getDisplayIndex, semantic rendering, framework adapter choice, or deciding what Table owns versus the renderer.\n",{"slug":9,"name":10,"logoUrl":11,"githubOrg":10},[3266,3267,3268],{"name":3138,"slug":3139,"type":16},{"name":3098,"slug":3099,"type":16},{"name":3166,"slug":3167,"type":16},"2026-07-30T05:25:52.366295",125]