@chat-adapter/state-redis 4.17.0 → 4.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,16 +3,18 @@
3
3
  [![npm version](https://img.shields.io/npm/v/@chat-adapter/state-redis)](https://www.npmjs.com/package/@chat-adapter/state-redis)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@chat-adapter/state-redis)](https://www.npmjs.com/package/@chat-adapter/state-redis)
5
5
 
6
- Redis state adapter for [Chat SDK](https://chat-sdk.dev/docs) using the official [redis](https://www.npmjs.com/package/redis) package. Recommended for production.
6
+ Production state adapter for [Chat SDK](https://chat-sdk.dev) using the official [redis](https://www.npmjs.com/package/redis) package.
7
7
 
8
8
  ## Installation
9
9
 
10
10
  ```bash
11
- npm install chat @chat-adapter/state-redis
11
+ pnpm add @chat-adapter/state-redis
12
12
  ```
13
13
 
14
14
  ## Usage
15
15
 
16
+ `createRedisState()` auto-detects the `REDIS_URL` environment variable, so you can call it with no arguments:
17
+
16
18
  ```typescript
17
19
  import { Chat } from "chat";
18
20
  import { createRedisState } from "@chat-adapter/state-redis";
@@ -20,15 +22,84 @@ import { createRedisState } from "@chat-adapter/state-redis";
20
22
  const bot = new Chat({
21
23
  userName: "mybot",
22
24
  adapters: { /* ... */ },
23
- state: createRedisState({
24
- url: process.env.REDIS_URL!,
25
- }),
25
+ state: createRedisState(),
26
+ });
27
+ ```
28
+
29
+ To provide a URL explicitly:
30
+
31
+ ```typescript
32
+ const state = createRedisState({ url: "redis://localhost:6379" });
33
+ ```
34
+
35
+ ### Using an existing client
36
+
37
+ If you already have a connected Redis client, pass it directly:
38
+
39
+ ```typescript
40
+ import { createClient } from "redis";
41
+
42
+ const client = createClient({ url: "redis://localhost:6379" });
43
+ await client.connect();
44
+
45
+ const state = createRedisState({ client });
46
+ ```
47
+
48
+ ### Key prefix
49
+
50
+ All keys are namespaced under a configurable prefix (default: `"chat-sdk"`):
51
+
52
+ ```typescript
53
+ const state = createRedisState({
54
+ url: process.env.REDIS_URL!,
55
+ keyPrefix: "my-bot",
26
56
  });
27
57
  ```
28
58
 
29
- ## Documentation
59
+ ## Configuration
60
+
61
+ | Option | Required | Description |
62
+ |--------|----------|-------------|
63
+ | `url` | No* | Redis connection URL (auto-detected from `REDIS_URL`) |
64
+ | `client` | No | Existing `redis` client instance |
65
+ | `keyPrefix` | No | Prefix for all keys (default: `"chat-sdk"`) |
66
+ | `logger` | No | Logger instance (defaults to `ConsoleLogger("info")`) |
67
+
68
+ *Either `url`, `REDIS_URL` env var, or `client` is required.
69
+
70
+ ## Environment variables
71
+
72
+ ```bash
73
+ REDIS_URL=redis://localhost:6379
74
+ ```
75
+
76
+ For serverless deployments (Vercel, AWS Lambda), use a serverless-compatible Redis provider like [Upstash](https://upstash.com).
77
+
78
+ ## Key structure
79
+
80
+ ```
81
+ {keyPrefix}:subscriptions - SET of subscribed thread IDs
82
+ {keyPrefix}:lock:{threadId} - Lock key with TTL
83
+ ```
84
+
85
+ ## Production recommendations
86
+
87
+ - Use Redis 6.0+ for best performance
88
+ - Enable Redis persistence (RDB or AOF)
89
+ - Use Redis Cluster for high availability
90
+ - Set appropriate memory limits
91
+
92
+ ## Features
30
93
 
31
- Full documentation at [chat-sdk.dev/docs/state/redis](https://chat-sdk.dev/docs/state/redis).
94
+ | Feature | Supported |
95
+ |---------|-----------|
96
+ | Persistence | Yes |
97
+ | Multi-instance | Yes |
98
+ | Subscriptions | Yes |
99
+ | Distributed locking | Yes |
100
+ | Key-value caching | Yes |
101
+ | Automatic reconnection | Yes |
102
+ | Key prefix namespacing | Yes |
32
103
 
33
104
  ## License
34
105
 
package/dist/index.d.ts CHANGED
@@ -30,6 +30,7 @@ declare class RedisStateAdapter implements StateAdapter {
30
30
  unsubscribe(threadId: string): Promise<void>;
31
31
  isSubscribed(threadId: string): Promise<boolean>;
32
32
  acquireLock(threadId: string, ttlMs: number): Promise<Lock | null>;
33
+ forceReleaseLock(threadId: string): Promise<void>;
33
34
  releaseLock(lock: Lock): Promise<void>;
34
35
  extendLock(lock: Lock, ttlMs: number): Promise<boolean>;
35
36
  get<T = unknown>(key: string): Promise<T | null>;
package/dist/index.js CHANGED
@@ -72,6 +72,11 @@ var RedisStateAdapter = class {
72
72
  }
73
73
  return null;
74
74
  }
75
+ async forceReleaseLock(threadId) {
76
+ this.ensureConnected();
77
+ const lockKey = this.key("lock", threadId);
78
+ await this.client.del(lockKey);
79
+ }
75
80
  async releaseLock(lock) {
76
81
  this.ensureConnected();
77
82
  const lockKey = this.key("lock", lock.threadId);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Lock, Logger, StateAdapter } from \"chat\";\nimport { ConsoleLogger } from \"chat\";\nimport { createClient, type RedisClientType } from \"redis\";\n\nexport interface RedisStateAdapterOptions {\n /** Key prefix for all Redis keys (default: \"chat-sdk\") */\n keyPrefix?: string;\n /** Logger instance for error reporting */\n logger: Logger;\n /** Redis connection URL (e.g., redis://localhost:6379) */\n url: string;\n}\n\n/**\n * Redis state adapter for production use.\n *\n * Provides persistent subscriptions and distributed locking\n * across multiple server instances.\n */\nexport class RedisStateAdapter implements StateAdapter {\n private readonly client: RedisClientType;\n private readonly keyPrefix: string;\n private readonly logger: Logger;\n private connected = false;\n private connectPromise: Promise<void> | null = null;\n\n constructor(options: RedisStateAdapterOptions) {\n this.client = createClient({ url: options.url });\n this.keyPrefix = options.keyPrefix || \"chat-sdk\";\n this.logger = options.logger;\n\n // Handle connection errors\n this.client.on(\"error\", (err) => {\n this.logger.error(\"Redis client error\", { error: err });\n });\n }\n\n private key(type: \"sub\" | \"lock\" | \"cache\", id: string): string {\n return `${this.keyPrefix}:${type}:${id}`;\n }\n\n private subscriptionsSetKey(): string {\n return `${this.keyPrefix}:subscriptions`;\n }\n\n async connect(): Promise<void> {\n if (this.connected) {\n return;\n }\n\n // Reuse existing connection attempt to avoid race conditions\n if (!this.connectPromise) {\n this.connectPromise = this.client.connect().then(() => {\n this.connected = true;\n });\n }\n\n await this.connectPromise;\n }\n\n async disconnect(): Promise<void> {\n if (this.connected) {\n await this.client.close();\n this.connected = false;\n this.connectPromise = null;\n }\n }\n\n async subscribe(threadId: string): Promise<void> {\n this.ensureConnected();\n await this.client.sAdd(this.subscriptionsSetKey(), threadId);\n }\n\n async unsubscribe(threadId: string): Promise<void> {\n this.ensureConnected();\n await this.client.sRem(this.subscriptionsSetKey(), threadId);\n }\n\n async isSubscribed(threadId: string): Promise<boolean> {\n this.ensureConnected();\n const result = await this.client.sIsMember(\n this.subscriptionsSetKey(),\n threadId\n );\n return result === 1;\n }\n\n async acquireLock(threadId: string, ttlMs: number): Promise<Lock | null> {\n this.ensureConnected();\n\n const token = generateToken();\n const lockKey = this.key(\"lock\", threadId);\n\n // Use SET NX EX for atomic lock acquisition\n const acquired = await this.client.set(lockKey, token, {\n NX: true,\n PX: ttlMs,\n });\n\n if (acquired) {\n return {\n threadId,\n token,\n expiresAt: Date.now() + ttlMs,\n };\n }\n\n return null;\n }\n\n async releaseLock(lock: Lock): Promise<void> {\n this.ensureConnected();\n\n const lockKey = this.key(\"lock\", lock.threadId);\n\n // Use Lua script for atomic check-and-delete\n const script = `\n if redis.call(\"get\", KEYS[1]) == ARGV[1] then\n return redis.call(\"del\", KEYS[1])\n else\n return 0\n end\n `;\n\n await this.client.eval(script, {\n keys: [lockKey],\n arguments: [lock.token],\n });\n }\n\n async extendLock(lock: Lock, ttlMs: number): Promise<boolean> {\n this.ensureConnected();\n\n const lockKey = this.key(\"lock\", lock.threadId);\n\n // Use Lua script for atomic check-and-extend\n const script = `\n if redis.call(\"get\", KEYS[1]) == ARGV[1] then\n return redis.call(\"pexpire\", KEYS[1], ARGV[2])\n else\n return 0\n end\n `;\n\n const result = await this.client.eval(script, {\n keys: [lockKey],\n arguments: [lock.token, ttlMs.toString()],\n });\n\n return result === 1;\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n this.ensureConnected();\n\n const cacheKey = this.key(\"cache\", key);\n const value = await this.client.get(cacheKey);\n\n if (value === null) {\n return null;\n }\n\n try {\n return JSON.parse(value) as T;\n } catch {\n // If parsing fails, return as string\n return value as unknown as T;\n }\n }\n\n async set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void> {\n this.ensureConnected();\n\n const cacheKey = this.key(\"cache\", key);\n const serialized = JSON.stringify(value);\n\n if (ttlMs) {\n await this.client.set(cacheKey, serialized, { PX: ttlMs });\n } else {\n await this.client.set(cacheKey, serialized);\n }\n }\n\n async setIfNotExists(\n key: string,\n value: unknown,\n ttlMs?: number\n ): Promise<boolean> {\n this.ensureConnected();\n\n const cacheKey = this.key(\"cache\", key);\n const serialized = JSON.stringify(value);\n\n const result = ttlMs\n ? await this.client.set(cacheKey, serialized, { NX: true, PX: ttlMs })\n : await this.client.set(cacheKey, serialized, { NX: true });\n\n return result !== null;\n }\n\n async delete(key: string): Promise<void> {\n this.ensureConnected();\n\n const cacheKey = this.key(\"cache\", key);\n await this.client.del(cacheKey);\n }\n\n private ensureConnected(): void {\n if (!this.connected) {\n throw new Error(\n \"RedisStateAdapter is not connected. Call connect() first.\"\n );\n }\n }\n\n /**\n * Get the underlying Redis client for advanced usage.\n */\n getClient(): RedisClientType {\n return this.client;\n }\n}\n\nfunction generateToken(): string {\n return `redis_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;\n}\n\nexport function createRedisState(\n options?: Partial<RedisStateAdapterOptions>\n): RedisStateAdapter {\n const url = options?.url ?? process.env.REDIS_URL;\n if (!url) {\n throw new Error(\n \"Redis url is required. Set REDIS_URL or provide it in options.\"\n );\n }\n const resolved: RedisStateAdapterOptions = {\n url,\n keyPrefix: options?.keyPrefix,\n logger: options?.logger ?? new ConsoleLogger(\"info\").child(\"redis\"),\n };\n return new RedisStateAdapter(resolved);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Lock, Logger, StateAdapter } from \"chat\";\nimport { ConsoleLogger } from \"chat\";\nimport { createClient, type RedisClientType } from \"redis\";\n\nexport interface RedisStateAdapterOptions {\n /** Key prefix for all Redis keys (default: \"chat-sdk\") */\n keyPrefix?: string;\n /** Logger instance for error reporting */\n logger: Logger;\n /** Redis connection URL (e.g., redis://localhost:6379) */\n url: string;\n}\n\n/**\n * Redis state adapter for production use.\n *\n * Provides persistent subscriptions and distributed locking\n * across multiple server instances.\n */\nexport class RedisStateAdapter implements StateAdapter {\n private readonly client: RedisClientType;\n private readonly keyPrefix: string;\n private readonly logger: Logger;\n private connected = false;\n private connectPromise: Promise<void> | null = null;\n\n constructor(options: RedisStateAdapterOptions) {\n this.client = createClient({ url: options.url });\n this.keyPrefix = options.keyPrefix || \"chat-sdk\";\n this.logger = options.logger;\n\n // Handle connection errors\n this.client.on(\"error\", (err) => {\n this.logger.error(\"Redis client error\", { error: err });\n });\n }\n\n private key(type: \"sub\" | \"lock\" | \"cache\", id: string): string {\n return `${this.keyPrefix}:${type}:${id}`;\n }\n\n private subscriptionsSetKey(): string {\n return `${this.keyPrefix}:subscriptions`;\n }\n\n async connect(): Promise<void> {\n if (this.connected) {\n return;\n }\n\n // Reuse existing connection attempt to avoid race conditions\n if (!this.connectPromise) {\n this.connectPromise = this.client.connect().then(() => {\n this.connected = true;\n });\n }\n\n await this.connectPromise;\n }\n\n async disconnect(): Promise<void> {\n if (this.connected) {\n await this.client.close();\n this.connected = false;\n this.connectPromise = null;\n }\n }\n\n async subscribe(threadId: string): Promise<void> {\n this.ensureConnected();\n await this.client.sAdd(this.subscriptionsSetKey(), threadId);\n }\n\n async unsubscribe(threadId: string): Promise<void> {\n this.ensureConnected();\n await this.client.sRem(this.subscriptionsSetKey(), threadId);\n }\n\n async isSubscribed(threadId: string): Promise<boolean> {\n this.ensureConnected();\n const result = await this.client.sIsMember(\n this.subscriptionsSetKey(),\n threadId\n );\n return result === 1;\n }\n\n async acquireLock(threadId: string, ttlMs: number): Promise<Lock | null> {\n this.ensureConnected();\n\n const token = generateToken();\n const lockKey = this.key(\"lock\", threadId);\n\n // Use SET NX EX for atomic lock acquisition\n const acquired = await this.client.set(lockKey, token, {\n NX: true,\n PX: ttlMs,\n });\n\n if (acquired) {\n return {\n threadId,\n token,\n expiresAt: Date.now() + ttlMs,\n };\n }\n\n return null;\n }\n\n async forceReleaseLock(threadId: string): Promise<void> {\n this.ensureConnected();\n const lockKey = this.key(\"lock\", threadId);\n await this.client.del(lockKey);\n }\n\n async releaseLock(lock: Lock): Promise<void> {\n this.ensureConnected();\n\n const lockKey = this.key(\"lock\", lock.threadId);\n\n // Use Lua script for atomic check-and-delete\n const script = `\n if redis.call(\"get\", KEYS[1]) == ARGV[1] then\n return redis.call(\"del\", KEYS[1])\n else\n return 0\n end\n `;\n\n await this.client.eval(script, {\n keys: [lockKey],\n arguments: [lock.token],\n });\n }\n\n async extendLock(lock: Lock, ttlMs: number): Promise<boolean> {\n this.ensureConnected();\n\n const lockKey = this.key(\"lock\", lock.threadId);\n\n // Use Lua script for atomic check-and-extend\n const script = `\n if redis.call(\"get\", KEYS[1]) == ARGV[1] then\n return redis.call(\"pexpire\", KEYS[1], ARGV[2])\n else\n return 0\n end\n `;\n\n const result = await this.client.eval(script, {\n keys: [lockKey],\n arguments: [lock.token, ttlMs.toString()],\n });\n\n return result === 1;\n }\n\n async get<T = unknown>(key: string): Promise<T | null> {\n this.ensureConnected();\n\n const cacheKey = this.key(\"cache\", key);\n const value = await this.client.get(cacheKey);\n\n if (value === null) {\n return null;\n }\n\n try {\n return JSON.parse(value) as T;\n } catch {\n // If parsing fails, return as string\n return value as unknown as T;\n }\n }\n\n async set<T = unknown>(key: string, value: T, ttlMs?: number): Promise<void> {\n this.ensureConnected();\n\n const cacheKey = this.key(\"cache\", key);\n const serialized = JSON.stringify(value);\n\n if (ttlMs) {\n await this.client.set(cacheKey, serialized, { PX: ttlMs });\n } else {\n await this.client.set(cacheKey, serialized);\n }\n }\n\n async setIfNotExists(\n key: string,\n value: unknown,\n ttlMs?: number\n ): Promise<boolean> {\n this.ensureConnected();\n\n const cacheKey = this.key(\"cache\", key);\n const serialized = JSON.stringify(value);\n\n const result = ttlMs\n ? await this.client.set(cacheKey, serialized, { NX: true, PX: ttlMs })\n : await this.client.set(cacheKey, serialized, { NX: true });\n\n return result !== null;\n }\n\n async delete(key: string): Promise<void> {\n this.ensureConnected();\n\n const cacheKey = this.key(\"cache\", key);\n await this.client.del(cacheKey);\n }\n\n private ensureConnected(): void {\n if (!this.connected) {\n throw new Error(\n \"RedisStateAdapter is not connected. Call connect() first.\"\n );\n }\n }\n\n /**\n * Get the underlying Redis client for advanced usage.\n */\n getClient(): RedisClientType {\n return this.client;\n }\n}\n\nfunction generateToken(): string {\n return `redis_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;\n}\n\nexport function createRedisState(\n options?: Partial<RedisStateAdapterOptions>\n): RedisStateAdapter {\n const url = options?.url ?? process.env.REDIS_URL;\n if (!url) {\n throw new Error(\n \"Redis url is required. Set REDIS_URL or provide it in options.\"\n );\n }\n const resolved: RedisStateAdapterOptions = {\n url,\n keyPrefix: options?.keyPrefix,\n logger: options?.logger ?? new ConsoleLogger(\"info\").child(\"redis\"),\n };\n return new RedisStateAdapter(resolved);\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chat-adapter/state-redis",
3
- "version": "4.17.0",
3
+ "version": "4.19.0",
4
4
  "description": "Redis state adapter for chat (production)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  "dependencies": {
19
19
  "redis": "^5.11.0",
20
- "chat": "4.17.0"
20
+ "chat": "4.19.0"
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",