Skip to content

Commit d4627b4

Browse files
jherrcursoragent
andcommitted
fix(transaction): satisfy kiira doc checks and eslint on clientTransaction cast
Add kiira group tags and explicit snippet types in transaction docs, and document the phantom-only double cast in clientTransaction with an eslint opt-out matching the chat activity pattern. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2039808 commit d4627b4

3 files changed

Lines changed: 28 additions & 14 deletions

File tree

docs/transaction/overview.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Verb names are *your* domain language, not a fixed set of library nouns. A blog
2121

2222
`defineTransaction()` lives on the `/transaction` subpath of `@tanstack/ai`, and `useTransaction()` on the `/transaction` subpath of `@tanstack/ai-react` (Solid and Vue follow the same pattern; Svelte exports `createTransaction` from `@tanstack/ai-svelte/transaction`) — not the package root.
2323

24-
```ts
24+
```ts group=overview-1
2525
// lib/blog-studio.ts — define once; the API route only calls `.handler`
2626
import { chat, generateImage, generateSpeech } from '@tanstack/ai'
2727
import { chatVerb, clientTransaction, defineTransaction, verb } from '@tanstack/ai/transaction'
@@ -66,17 +66,18 @@ export const blogTxnDef = clientTransaction<typeof blogTransaction>({
6666
})
6767
```
6868

69-
```ts
69+
```ts group=overview-1
7070
// routes/api.blog-studio.ts — thin server route
7171
export const POST = (request: Request) => blogTransaction.handler(request)
7272
```
7373

7474
That one route now serves conversational drafting, image generation, and narration. On the client, one hook drives all three:
7575

76-
```tsx
76+
```tsx group=overview-client
7777
// routes/blog-studio.tsx
7878
import { fetchServerSentEvents } from '@tanstack/ai-react'
7979
import { useTransaction } from '@tanstack/ai-react/transaction'
80+
import type { UIMessage } from '@tanstack/ai-react'
8081
import { blogTxnDef } from '../lib/blog-studio'
8182

8283
function BlogStudio() {
@@ -87,7 +88,7 @@ function BlogStudio() {
8788
return (
8889
<div>
8990
<button onClick={() => txn.drafting.sendMessage('Hello!')}>Send</button>
90-
{txn.drafting.messages.map((message) => (
91+
{txn.drafting.messages.map((message: UIMessage) => (
9192
<p key={message.id}>
9293
{message.parts.find((part) => part.type === 'text')?.content}
9394
</p>
@@ -120,7 +121,7 @@ Both `txn.<oneShot>.run(input)` and `txn.<chatVerb>.sendMessage(...)` **resolve
120121

121122
You can declare **several verbs of the same kind** — including several chat verbs. Each gets its own conversation state, system prompt, and even model:
122123

123-
```ts
124+
```ts group=overview-2
124125
// api/support.ts
125126
import { chat } from '@tanstack/ai'
126127
import { chatVerb, defineTransaction } from '@tanstack/ai/transaction'
@@ -162,7 +163,9 @@ On the client, `txn.primaryChat` and `txn.summaryChat` are two fully independent
162163

163164
The recommended pattern is to **`defineTransaction` once in a shared module** and export a **`blogTxnDef`** client stub beside it via `clientTransaction<typeof blogTransaction>({ kinds })`. The page imports `blogTxnDef`; the API route imports `blogTransaction` and calls `.handler`. The kinds map is checked exhaustively against the server definition, so drift fails at compile time.
164165

165-
```ts
166+
```ts group=overview-sharing
167+
import { clientTransaction, defineTransaction } from '@tanstack/ai/transaction'
168+
166169
// lib/blog-studio.ts
167170
export const blogTransaction = defineTransaction({ /**/ })
168171

docs/transaction/transactions.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ One click turns a topic into a finished post: draft the article as a typed objec
2525

2626
### Server: a `blogPost` verb that composes its siblings
2727

28-
```ts
28+
```ts group=transactions
2929
// lib/blog-studio.ts
3030
import { chat, generateImage, generateSpeech } from '@tanstack/ai'
3131
import { chatVerb, defineTransaction, verb } from '@tanstack/ai/transaction'
@@ -116,7 +116,7 @@ export const blogTransaction = defineTransaction({
116116
})
117117
```
118118

119-
```ts
119+
```ts group=transactions
120120
// routes/api.blog-studio.ts
121121
export const POST = (request: Request) => blogTransaction.handler(request)
122122
```
@@ -128,10 +128,11 @@ export const POST = (request: Request) => blogTransaction.handler(request)
128128

129129
### Client: one call, live progress, typed result
130130

131-
```tsx
131+
```tsx group=transactions
132132
// routes/blog-studio.tsx
133133
import { fetchServerSentEvents } from '@tanstack/ai-react'
134134
import { useTransaction } from '@tanstack/ai-react/transaction'
135+
import type { TransactionSubRun } from '@tanstack/ai-client/transaction'
135136
import { blogTxnDef } from '../lib/blog-studio'
136137

137138
function BlogStudio() {
@@ -141,9 +142,15 @@ function BlogStudio() {
141142

142143
const { blogPost } = txn
143144
// One entry per `ctx.call` the server made, keyed by verb name.
144-
const draftingRun = blogPost.subRuns.find((run) => run.verb === 'drafting')
145-
const heroRun = blogPost.subRuns.find((run) => run.verb === 'heroImage')
146-
const narrationRun = blogPost.subRuns.find((run) => run.verb === 'narration')
145+
const draftingRun = blogPost.subRuns.find(
146+
(run: TransactionSubRun) => run.verb === 'drafting',
147+
)
148+
const heroRun = blogPost.subRuns.find(
149+
(run: TransactionSubRun) => run.verb === 'heroImage',
150+
)
151+
const narrationRun = blogPost.subRuns.find(
152+
(run: TransactionSubRun) => run.verb === 'narration',
153+
)
147154

148155
return (
149156
<div>

packages/ai/src/activities/transaction/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ export function clientTransaction<TDef extends TransactionDefinition<any>>(
250250
kinds: ClientTransactionKinds<TDef>,
251251
): TDef {
252252
const verbs = Object.keys(kinds) as Array<keyof TDef['~verbs'] & string>
253-
return {
253+
const stub = {
254254
verbs,
255255
verbKinds: kinds,
256256
handler: async () => {
@@ -259,7 +259,11 @@ export function clientTransaction<TDef extends TransactionDefinition<any>>(
259259
)
260260
},
261261
'~verbs': {} as TDef['~verbs'],
262-
} as unknown as TDef
262+
}
263+
// Type-only stub: runtime value carries verb names/kinds; `~verbs` phantom
264+
// comes from the generic `TDef` supplied by the caller.
265+
// eslint-disable-next-line no-restricted-syntax -- phantom-only cast, see comment above
266+
return stub as unknown as TDef
263267
}
264268

265269
/**

0 commit comments

Comments
 (0)