Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72300,7 +72300,7 @@ import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key", 1, "member");
await redis.zadd("key", { score: 1, member: "member" });
const value = await redis.zincrby("key", 2, "member");
console.log(value); // 3
```
Expand Down Expand Up @@ -73032,7 +73032,7 @@ await redis.zadd("key",
{ score: 1, member: "one"},
{ score: 2, member: "two" },
);
const elements = await redis.zlexcount("key", "two", "+");
const elements = await redis.zlexcount("key", "[two", "+");
console.log(elements); // 1
```

Expand Down Expand Up @@ -108266,7 +108266,7 @@ The Search SDK supports defining your content and metadata types at the index le

```typescript {4,5,6}
import { Search } from "@upstash/search";
const client = new Search();
const client = Search.fromEnv();

type Content = { title: string, genre: string };
type Metadata = { year: number };
Expand Down Expand Up @@ -118019,7 +118019,7 @@ This flexibility allows you to tailor the tools to your specific needs while lev
<CodeGroup>

```ts WorkflowTool (custom)
import { WorkflowTool } from '@upstash/workflow'
import { WorkflowTool } from '@upstash/workflow-agents'
import { z } from 'zod'
import * as mathjs from 'mathjs'

Expand Down Expand Up @@ -120949,7 +120949,7 @@ const { workflowRunId } = await client.trigger({
workflowRunId: "my-workflow", // optional workflow run id
retries: 3, // optional retries
retryDelay: "1000 * (1 + retried)", // optional delay between retries
delay: "10s" // optional delay value
delay: "10s", // optional delay value
failureUrl: "https://<YOUR_FAILURE_URL>", // optional failure url
flowControl: { // optional flow control
key: "USER_GIVEN_KEY",
Expand Down Expand Up @@ -121930,7 +121930,7 @@ You can delete entries individually, in bulk by IDs, or by using filters such as
await client.dlq.delete(["dlq-12345", "dlq-67890"]);

// delete by filters
await client.dlq.delete({ label: "my-label" });
await client.dlq.delete({ filter: { label: "my-label" } });

// delete all
await client.dlq.delete({ all: true });
Expand Down Expand Up @@ -122120,7 +122120,7 @@ If you think this advanced option fits your need, you can configure it by passin
const client = new Client({ token: "<QSTASH_TOKEN>" })

const { workflowRunId } = await client.trigger({
url: "https://<YOUR_WORKFLOW_URL>/workflow"
url: "https://<YOUR_WORKFLOW_URL>/workflow",
failureUrl: "https://<YOUR_FAILURE_URL>/workflow-failure"
})
```
Expand Down Expand Up @@ -122252,7 +122252,7 @@ export const { POST } = serve(async (context) => {

await context.run(
"charge customer",
() => chargeCustomer(),
() => chargeCustomer("customer-id"),
{
flowControl: { key: "payment-provider", parallelism: 3 },
}
Expand Down Expand Up @@ -122433,7 +122433,7 @@ const {
{
workflow: analyzeContent,
body: "test",
header: {...}, // headers to pass to anotherWorkflow (optional)
headers: {...}, // headers to pass to anotherWorkflow (optional)
retries, // number of retries (optional, default: 3)
flowControl, // flow control settings (optional)
workflowRunId // workflowRunId to set (optional)
Expand Down Expand Up @@ -122488,7 +122488,7 @@ This way, you can invoke a workflow simply by passing the object to `context.inv
workflow: workflowOne,
body: "user-1"
}
),
);
});
```
</Step>
Expand Down Expand Up @@ -122530,7 +122530,7 @@ This way, you can invoke a workflow simply by passing the object to `context.inv
// 👇 optionally label the invoked run; single string or array
label: ["team-a", "high-priority"],
}
),
);
});
```

Expand Down Expand Up @@ -122740,7 +122740,7 @@ By default, the retry count is set to **3**, and an **exponential backoff** dela

```javascript Default Backoff Algorithm
// n = how many times this request has been retried
delay = min(86400, e ** (2.5*n)) // in seconds
delay = Math.min(86400, Math.E ** (2.5*n)) // in seconds
```

| Retry Attempt | Algorithm | Delay |
Expand Down
2 changes: 1 addition & 1 deletion qstash/sdks/ts/examples/dlq.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { Client } from "@upstash/qstash";

const client = new Client({ token: "<QSTASH_TOKEN>" });
const all_messages = [];
let cursor = null;
let cursor: string | undefined;
while (true) {
const res = await client.dlq.listMessages({ cursor });
all_messages.push(...res.messages);
Expand Down
2 changes: 1 addition & 1 deletion redis/commands/sorted-set/zincrby.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

await redis.zadd("key", 1, "member");
await redis.zadd("key", { score: 1, member: "member" });
const value = await redis.zincrby("key", 2, "member");
console.log(value); // 3
```
Expand Down
2 changes: 1 addition & 1 deletion redis/commands/sorted-set/zlexcount.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ await redis.zadd("key",
{ score: 1, member: "one"},
{ score: 2, member: "two" },
);
const elements = await redis.zlexcount("key", "two", "+");
const elements = await redis.zlexcount("key", "[two", "+");
console.log(elements); // 1
```

Expand Down
2 changes: 1 addition & 1 deletion redis/sdks/py/commands/list/lpush.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ description: Push an element at the head of the list.
```py Example
assert redis.lpush("mylist", "one", "two", "three") == 3

assert lrange("mylist", 0, -1) == ["three", "two", "one"]
assert redis.lrange("mylist", 0, -1) == ["three", "two", "one"]
```


Expand Down
2 changes: 1 addition & 1 deletion redis/sdks/py/commands/list/lpushx.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ redis.lpush("mylist", "one")

assert redis.lpushx("mylist", "two", "three") == 3

assert lrange("mylist", 0, -1) == ["three", "two", "one"]
assert redis.lrange("mylist", 0, -1) == ["three", "two", "one"]

# Non existing key
assert redis.lpushx("non-existent-list", "one") == 0
Expand Down
6 changes: 3 additions & 3 deletions redis/sdks/py/commands/list/lset.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ assert redis.lset("mylist", 1, "Hello") == True

assert redis.lrange("mylist", 0, -1) == ["one", "Hello", "three"]

assert redis.lset("mylist", 5, "Hello") == False
assert redis.lset("mylist", 2, "World") == True

assert redis.lrange("mylist", 0, -1) == ["one", "Hello", "three"]
```
assert redis.lrange("mylist", 0, -1) == ["one", "Hello", "World"]
```
</RequestExample>
2 changes: 1 addition & 1 deletion redis/sdks/py/commands/list/ltrim.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ description: Trim a list to the specified range
```py Example
redis.rpush("mylist", "one", "two", "three")

assert redis.ltrim("mylist", 0, 1) == True
assert redis.ltrim("mylist", 0, 1) == "OK"

assert redis.lrange("mylist", 0, -1) == ["one", "two"]
```
Expand Down
2 changes: 1 addition & 1 deletion redis/sdks/py/commands/list/rpush.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ description: Push an element at the end of the list.
```py Example
assert redis.rpush("mylist", "one", "two", "three") == 3

assert lrange("mylist", 0, -1) == ["one", "two", "three"]
assert redis.lrange("mylist", 0, -1) == ["one", "two", "three"]
```


Expand Down
6 changes: 4 additions & 2 deletions redis/sdks/py/commands/list/rpushx.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ description: Push an element at the end of the list only if the list exists.

<RequestExample>
```py Example
assert redis.rpushx("mylist", "one", "two", "three") == 3
redis.rpush("mylist", "a")

assert lrange("mylist", 0, -1) == ["one", "two", "three"]
assert redis.rpushx("mylist", "one", "two", "three") == 4

assert redis.lrange("mylist", 0, -1) == ["a", "one", "two", "three"]

# Non existing key
assert redis.rpushx("non-existent-list", "one") == 0
Expand Down
2 changes: 1 addition & 1 deletion redis/sdks/py/commands/scripts/evalsha.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ description: Evaluate a cached Lua script server side.
<RequestExample>
```py Example
result = redis.evalsha("fb67a0c03b48ddbf8b4c9b011e779563bdbc28cb", args=["hello"])
assert result = "hello"
assert result == "hello"
```
</RequestExample>

2 changes: 1 addition & 1 deletion redis/sdks/py/commands/scripts/evalsha_ro.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ description: Evaluate a cached read-only Lua script server side.
<RequestExample>
```py Example
result = redis.evalsha_ro("fb67a0c03b48ddbf8b4c9b011e779563bdbc28cb", args=["hello"])
assert result = "hello"
assert result == "hello"
```
</RequestExample>

2 changes: 1 addition & 1 deletion redis/sdks/py/commands/scripts/script_exists.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ description: Check if scripts exist in the script cache.
```py Example
# Script 1 exists
# Script 0 does not
await redis.scriptExists("<sha1>", "<sha2>") == [1, 0]
assert redis.script_exists("<sha1>", "<sha2>") == [True, False]
```
</RequestExample>

2 changes: 1 addition & 1 deletion redis/sdks/py/commands/set/sdiffstore.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ redis.sadd("key2", "c", "d", "e")
# Store the result in a new set
assert redis.sdiffstore("res", "key1", "key2") == 2

assert redis.smembers("set") == {"a", "b"}
assert set(redis.smembers("res")) == {"a", "b"}
```

</RequestExample>
2 changes: 1 addition & 1 deletion redis/sdks/py/commands/set/sinterstore.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ redis.sadd("set1", "a", "b", "c");

redis.sadd("set2", "c", "d", "e");

assert redis.sinter("destination", "set1", "set2") == 1
assert redis.sinterstore("destination", "set1", "set2") == 1
```

</RequestExample>
4 changes: 2 additions & 2 deletions redis/sdks/py/commands/set/smove.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ redis.sadd("dest", "four")

assert redis.smove("src", "dest", "three") == True

assert redis.smembers("source") == {"one", "two"}
assert set(redis.smembers("src")) == {"one", "two"}

assert redis.smembers("destination") == {"three", "four"}
assert set(redis.smembers("dest")) == {"three", "four"}
```

</RequestExample>
2 changes: 1 addition & 1 deletion redis/sdks/py/commands/set/spop.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ assert redis.spop("myset") in {"one", "two", "three"}
```py With Count
redis.sadd("myset", "one", "two", "three")

assert redis.spop("myset", 2) in {"one", "two", "three"}
assert set(redis.spop("myset", 2)) <= {"one", "two", "three"}
```

</RequestExample>
2 changes: 1 addition & 1 deletion redis/sdks/py/commands/set/srandmember.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ assert redis.srandmember("myset") in {"one", "two", "three"}
```py With Count
redis.sadd("myset", "one", "two", "three")

assert redis.srandmember("myset", 2) in {"one", "two", "three"}
assert set(redis.srandmember("myset", 2)) <= {"one", "two", "three"}
```

</RequestExample>
2 changes: 1 addition & 1 deletion redis/sdks/py/commands/set/sscan.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ description: Scan a set
# Get all members of a set.

cursor = 0
results = set()
results = []

while True:
cursor, keys = redis.sscan("myset", cursor, match="*")
Expand Down
50 changes: 27 additions & 23 deletions redis/tutorials/tanstack_chat_persistence.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ UPSTASH_REDIS_REST_TOKEN="..."

## The adapter

A persistence adapter is just an object with three methods. Each may be sync or async — the client awaits them. We store the messages array under a namespaced key and revive `createdAt` (which becomes a string through JSON) on read.
A persistence adapter is just an object with three methods. Each may be sync or async — the client awaits them. The client hands it one record per thread (the messages, plus a resume pointer while a run is in flight); we store that record under a namespaced key and revive each message's `createdAt` (which becomes a string through JSON) on read.

```typescript

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed until the SDK or service supports it: @tanstack/ai-client 0.1.x ChatClientPersistence.getItem/setItem now take a ChatPersistedState ({messages, resume?}) record, not a bare UIMessage[]; the adapter's return shape is rejected by the released types (TS2322: Property 'messages' is missing).

Restore this example after that release.

// upstash-persistence.ts
import type { Redis } from "@upstash/redis";
import type { ChatClientPersistence, UIMessage } from "@tanstack/ai-client";
import type { ChatClientPersistence, ChatPersistedState } from "@tanstack/ai-client";

type UpstashPersistenceOptions = {
redis: Redis;
Expand All @@ -41,41 +41,45 @@ type UpstashPersistenceOptions = {

export function upstashPersistence(options: UpstashPersistenceOptions): ChatClientPersistence {
const { redis, prefix = "tanstack:chat:", ttlSeconds } = options;
const key = (id: string) => `${prefix}${id}`;
const key = (threadId: string) => `${prefix}${threadId}`;

return {
async getItem(id) {
const stored = await redis.get<Array<UIMessage>>(key(id));
async getItem(threadId) {
const stored = await redis.get<ChatPersistedState>(key(threadId));
if (!stored) return null;
// createdAt round-trips as a string through JSON; revive it.
return stored.map((m) => ({
...m,
createdAt: typeof m.createdAt === "string" ? new Date(m.createdAt) : m.createdAt,
}));
return {
...stored,
messages: stored.messages.map((m) => ({
...m,
createdAt: typeof m.createdAt === "string" ? new Date(m.createdAt) : m.createdAt,
})),
};
},
async setItem(id, messages) {
await redis.set(key(id), messages, ttlSeconds ? { ex: ttlSeconds } : undefined);
async setItem(threadId, state) {
await redis.set(key(threadId), state, ttlSeconds ? { ex: ttlSeconds } : undefined);
},
async removeItem(id) {
await redis.del(key(id));
async removeItem(threadId) {
await redis.del(key(threadId));
},
};
}
```

## Use it

Pass the adapter as `persistence` and give the client a stable `id` — that `id` is the storage key, so the same `id` loads the same conversation back.
Pass the adapter as `persistence` and give the client a stable `threadId` — that `threadId` is the storage key, so the same `threadId` loads the same conversation back.

```typescript

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed until the SDK or service supports it: @tanstack/ai-client 0.1.x ChatClientOptions requires threadId: string and has no 'id' option; the block passes id (TS2353: 'id' does not exist in type 'ChatClientOptions').

Restore this example after that release.

import { Redis } from "@upstash/redis";
import { ChatClient } from "@tanstack/ai-client";
import { ChatClient, fetchServerSentEvents } from "@tanstack/ai-client";
import { upstashPersistence } from "./upstash-persistence";

const redis = Redis.fromEnv();
const connection = fetchServerSentEvents("/api/chat");

const chat = new ChatClient({
id: "conversation-123",
threadId: "conversation-123",
connection, // your OpenAI/SSE transport
persistence: upstashPersistence({ redis }), // <- that's it
});
Expand All @@ -85,22 +89,22 @@ await chat.sendMessage("In one short sentence, what is Upstash Redis?");

The client now:

- **Hydrates on construction** — calls `getItem(id)` and populates itself (overriding `initialMessages`).
- **Saves on every change** — calls `setItem(id, messages)` on each new message and streamed chunk, through an ordered write queue.
- **Clears on `clear()`** — calls `removeItem(id)`.
- **Hydrates on construction** — calls `getItem(threadId)` and populates itself (overriding `initialMessages`).
- **Saves on every change** — calls `setItem(threadId, state)` on each new message and streamed chunk, through an ordered write queue.
- **Clears on `clear()`** — calls `removeItem(threadId)`.

## Try it

Create a client, chat, then construct a **brand-new** client with the same `id` — it hydrates the full history from Redis with no `initialMessages`:
Create a client, chat, then construct a **brand-new** client with the same `threadId` — it hydrates the full history from Redis with no `initialMessages`:

```typescript

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed until the SDK or service supports it: @tanstack/ai-client 0.1.x ChatClientOptions requires threadId: string and has no 'id' option, so both new ChatClient({ id: ... }) calls are rejected (TS2353).

Restore this example after that release.

// Session 1 — persists to Redis
const a = new ChatClient({ id: "demo", connection, persistence: upstashPersistence({ redis }) });
const a = new ChatClient({ threadId: "demo", connection, persistence: upstashPersistence({ redis }) });
await a.sendMessage("In one short sentence, what is Upstash Redis?");
await a.sendMessage("And in one sentence, what is TanStack?");

// Session 2 — same id, fresh client, no initialMessages
const b = new ChatClient({ id: "demo", connection, persistence: upstashPersistence({ redis }) });
// Session 2 — same threadId, fresh client, no initialMessages
const b = new ChatClient({ threadId: "demo", connection, persistence: upstashPersistence({ redis }) });
await b.sendMessage("What did I ask you first? Quote it back.");
// -> 'You asked: "In one short sentence, what is Upstash Redis?"'
```
Expand Down
2 changes: 1 addition & 1 deletion search/sdks/ts/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ The Search SDK supports defining your content and metadata types at the index le

```typescript {4,5,6}
import { Search } from "@upstash/search";
const client = new Search();
const client = Search.fromEnv();

type Content = { title: string, genre: string };
type Metadata = { year: number };
Expand Down
Loading