0
# Key-Value Store
1
2
The StoreClient provides distributed key-value store operations with namespace support, TTL management, and search capabilities. It enables persistent data storage and retrieval across LangGraph applications, perfect for maintaining application state, caching data, and sharing information between different components and runs.
3
4
## Core Functionality
5
6
The StoreClient supports:
7
8
- **Item Operations**: Store, retrieve, and delete key-value items with rich metadata
9
- **Namespace Management**: Organize data using hierarchical namespaces
10
- **Search Capabilities**: Find items using flexible search criteria and filters
11
- **TTL Support**: Automatic expiration of stored items with refresh capabilities
12
- **Indexing**: Custom indexing for efficient search operations
13
- **Pagination**: Handle large datasets with built-in pagination support
14
15
## StoreClient API
16
17
```typescript { .api }
18
class StoreClient extends BaseClient {
19
/**
20
* Store an item in the key-value store with optional indexing and TTL
21
* @param namespace - Hierarchical namespace array for organization
22
* @param key - Unique key within the namespace
23
* @param value - Data to store (must be JSON-serializable object)
24
* @param options - Storage options including indexing and TTL
25
* @returns Promise resolving when storage completes
26
*/
27
putItem(
28
namespace: string[],
29
key: string,
30
value: Record<string, unknown>,
31
options?: PutItemOptions
32
): Promise<void>;
33
34
/**
35
* Retrieve an item from the key-value store
36
* @param namespace - Hierarchical namespace array
37
* @param key - Unique key within the namespace
38
* @param options - Retrieval options including TTL refresh
39
* @returns Promise resolving to item data or null if not found
40
*/
41
getItem(
42
namespace: string[],
43
key: string,
44
options?: GetItemOptions
45
): Promise<Item | null>;
46
47
/**
48
* Delete an item from the key-value store
49
* @param namespace - Hierarchical namespace array
50
* @param key - Unique key within the namespace
51
* @returns Promise resolving when deletion completes
52
*/
53
deleteItem(namespace: string[], key: string): Promise<void>;
54
55
/**
56
* Search for items within a namespace with flexible filtering and pagination
57
* @param namespacePrefix - Namespace prefix to search within
58
* @param options - Search criteria, filters, and pagination options
59
* @returns Promise resolving to search results with items and metadata
60
*/
61
searchItems(
62
namespacePrefix: string[],
63
options?: SearchItemsOptions
64
): Promise<SearchItemsResponse>;
65
66
/**
67
* List all available namespaces with optional filtering and pagination
68
* @param options - Listing options including filters and pagination
69
* @returns Promise resolving to namespace information
70
*/
71
listNamespaces(options?: ListNamespacesOptions): Promise<ListNamespaceResponse>;
72
}
73
```
74
75
## Core Types
76
77
### Item Interface
78
79
```typescript { .api }
80
interface Item {
81
/** Hierarchical namespace array */
82
namespace: string[];
83
/** Unique key within namespace */
84
key: string;
85
/** Stored value data */
86
value: Record<string, unknown>;
87
/** Item creation timestamp */
88
created_at: string;
89
/** Item last update timestamp */
90
updated_at: string;
91
/** TTL expiration timestamp (if applicable) */
92
expires_at?: string;
93
/** Custom index fields for search */
94
index?: string[] | null;
95
}
96
97
interface SearchItem extends Item {
98
/** Search relevance score */
99
score?: number;
100
/** Highlighted search matches */
101
highlights?: Record<string, string[]>;
102
}
103
```
104
105
### Response Types
106
107
```typescript { .api }
108
interface SearchItemsResponse {
109
/** Array of matching items */
110
items: SearchItem[];
111
/** Total number of matching items */
112
total: number;
113
/** Current page offset */
114
offset: number;
115
/** Items per page limit */
116
limit: number;
117
/** Whether more results are available */
118
hasMore: boolean;
119
/** Search query metadata */
120
query: {
121
namespace_prefix: string[];
122
filter?: Record<string, any>;
123
query?: string;
124
};
125
}
126
127
interface ListNamespaceResponse {
128
/** Array of namespace information */
129
namespaces: Array<{
130
/** Namespace path */
131
namespace: string[];
132
/** Number of items in namespace */
133
item_count: number;
134
/** Total size in bytes */
135
size_bytes: number;
136
/** Last activity timestamp */
137
last_activity: string;
138
}>;
139
/** Total number of namespaces */
140
total: number;
141
/** Current page offset */
142
offset: number;
143
/** Namespaces per page limit */
144
limit: number;
145
}
146
```
147
148
## Options Types
149
150
### Storage Options
151
152
```typescript { .api }
153
interface PutItemOptions {
154
/**
155
* Time-to-live in seconds (null for no expiration)
156
* Item will be automatically deleted after this duration
157
*/
158
ttl?: number | null;
159
160
/**
161
* Custom index fields for search optimization
162
* - false: No indexing
163
* - string[]: Index specific fields
164
* - null: Index all fields (default)
165
*/
166
index?: false | string[] | null;
167
168
/**
169
* Whether to overwrite existing item
170
* @default true
171
*/
172
overwrite?: boolean;
173
174
/**
175
* Custom metadata for the item
176
*/
177
metadata?: Record<string, any>;
178
}
179
180
interface GetItemOptions {
181
/**
182
* Whether to refresh TTL on access
183
* Extends the expiration time by the original TTL duration
184
*/
185
refreshTtl?: boolean | null;
186
187
/**
188
* Include item metadata in response
189
* @default false
190
*/
191
includeMetadata?: boolean;
192
}
193
```
194
195
### Search Options
196
197
```typescript { .api }
198
interface SearchItemsOptions {
199
/** Text query for full-text search across item values */
200
query?: string;
201
202
/** Filter criteria for item values */
203
filter?: Record<string, any>;
204
205
/** Maximum number of results to return */
206
limit?: number;
207
208
/** Pagination offset */
209
offset?: number;
210
211
/** Sort configuration */
212
sort?: {
213
/** Field to sort by */
214
field: string;
215
/** Sort direction */
216
direction: "asc" | "desc";
217
}[];
218
219
/** Include expired items in results */
220
includeExpired?: boolean;
221
222
/** Search only within specific namespaces */
223
exactNamespaceMatch?: boolean;
224
225
/** Minimum relevance score for results */
226
minScore?: number;
227
228
/** Fields to include in highlights */
229
highlightFields?: string[];
230
231
/** Include item metadata in results */
232
includeMetadata?: boolean;
233
}
234
235
interface ListNamespacesOptions {
236
/** Filter namespaces by prefix */
237
prefix?: string[];
238
239
/** Maximum number of namespaces to return */
240
limit?: number;
241
242
/** Pagination offset */
243
offset?: number;
244
245
/** Include item count statistics */
246
includeStats?: boolean;
247
248
/** Sort by field */
249
sortBy?: "namespace" | "item_count" | "size_bytes" | "last_activity";
250
251
/** Sort direction */
252
sortDirection?: "asc" | "desc";
253
}
254
```
255
256
## Usage Examples
257
258
### Basic Item Operations
259
260
```typescript
261
import { Client } from "@langchain/langgraph-sdk";
262
263
const client = new Client({
264
apiUrl: "https://api.langgraph.example.com",
265
apiKey: "your-api-key"
266
});
267
268
// Store user profile data
269
await client.store.putItem(
270
["users", "profiles"],
271
"user_123",
272
{
273
name: "Alice Johnson",
274
email: "alice@example.com",
275
preferences: {
276
theme: "dark",
277
notifications: true,
278
language: "en"
279
},
280
lastLogin: new Date().toISOString()
281
},
282
{
283
ttl: 86400, // Expire after 24 hours
284
index: ["name", "email", "preferences.theme"] // Index specific fields
285
}
286
);
287
288
// Retrieve user profile
289
const userProfile = await client.store.getItem(
290
["users", "profiles"],
291
"user_123",
292
{ refreshTtl: true } // Extend TTL on access
293
);
294
295
if (userProfile) {
296
console.log("User:", userProfile.value.name);
297
console.log("Theme:", userProfile.value.preferences?.theme);
298
console.log("Expires at:", userProfile.expires_at);
299
} else {
300
console.log("User profile not found or expired");
301
}
302
303
// Update user profile
304
await client.store.putItem(
305
["users", "profiles"],
306
"user_123",
307
{
308
...userProfile?.value,
309
lastLogin: new Date().toISOString(),
310
preferences: {
311
...userProfile?.value.preferences,
312
theme: "light" // User changed theme
313
}
314
},
315
{ ttl: 86400, index: ["name", "email", "preferences.theme"] }
316
);
317
```
318
319
### Session and Cache Management
320
321
```typescript
322
// Store session data with automatic expiration
323
await client.store.putItem(
324
["sessions", "user_sessions"],
325
"session_abc123",
326
{
327
userId: "user_456",
328
startTime: new Date().toISOString(),
329
permissions: ["read", "write"],
330
metadata: {
331
userAgent: "Mozilla/5.0...",
332
ipAddress: "192.168.1.1"
333
}
334
},
335
{
336
ttl: 3600, // 1 hour session
337
index: ["userId", "permissions"]
338
}
339
);
340
341
// Cache expensive computation results
342
await client.store.putItem(
343
["cache", "computations"],
344
"analysis_result_789",
345
{
346
input: { dataset: "monthly_sales", parameters: { aggregation: "sum" } },
347
result: {
348
total: 150000,
349
breakdown: { "Q1": 35000, "Q2": 42000, "Q3": 38000, "Q4": 35000 },
350
computedAt: new Date().toISOString()
351
},
352
executionTime: 2.5 // seconds
353
},
354
{
355
ttl: 1800, // 30 minutes cache
356
index: ["input.dataset", "result.total"],
357
metadata: { cacheVersion: "1.0" }
358
}
359
);
360
361
// Store conversation context
362
await client.store.putItem(
363
["conversations", "contexts"],
364
"conv_456",
365
{
366
participants: ["user_123", "assistant_ai"],
367
messages: [
368
{ role: "user", content: "Hello", timestamp: "2024-01-01T10:00:00Z" },
369
{ role: "assistant", content: "Hi! How can I help?", timestamp: "2024-01-01T10:00:01Z" }
370
],
371
context: {
372
topic: "general_chat",
373
mood: "friendly",
374
preferences: { verbosity: "normal" }
375
}
376
},
377
{
378
ttl: 86400 * 7, // 1 week
379
index: ["participants", "context.topic"]
380
}
381
);
382
```
383
384
### Advanced Search Operations
385
386
```typescript
387
// Search for users by name
388
const userSearchResults = await client.store.searchItems(
389
["users", "profiles"],
390
{
391
query: "alice johnson",
392
limit: 10,
393
highlightFields: ["name", "email"],
394
includeMetadata: true
395
}
396
);
397
398
console.log(`Found ${userSearchResults.total} users`);
399
userSearchResults.items.forEach(item => {
400
console.log(`User: ${item.value.name}`);
401
if (item.highlights?.name) {
402
console.log(`Matched: ${item.highlights.name.join(", ")}`);
403
}
404
});
405
406
// Filter-based search for active sessions
407
const activeSessions = await client.store.searchItems(
408
["sessions"],
409
{
410
filter: {
411
permissions: { $contains: "write" },
412
metadata: {
413
ipAddress: { $regex: "192\\.168\\.*" }
414
}
415
},
416
sort: [
417
{ field: "startTime", direction: "desc" }
418
],
419
limit: 50,
420
includeExpired: false
421
}
422
);
423
424
// Complex search across multiple namespaces
425
const analyticsData = await client.store.searchItems(
426
["analytics"],
427
{
428
filter: {
429
result: {
430
total: { $gte: 100000 }, // Greater than or equal to 100k
431
computedAt: {
432
$gte: "2024-01-01T00:00:00Z",
433
$lt: "2024-02-01T00:00:00Z"
434
}
435
}
436
},
437
sort: [
438
{ field: "result.total", direction: "desc" },
439
{ field: "executionTime", direction: "asc" }
440
],
441
minScore: 0.5
442
}
443
);
444
445
console.log(`Found ${analyticsData.total} analytics records`);
446
```
447
448
### Namespace Management and Organization
449
450
```typescript
451
// List all namespaces to understand data organization
452
const namespaces = await client.store.listNamespaces({
453
includeStats: true,
454
sortBy: "item_count",
455
sortDirection: "desc",
456
limit: 20
457
});
458
459
console.log("Top namespaces by item count:");
460
namespaces.namespaces.forEach(ns => {
461
console.log(`${ns.namespace.join('/')}: ${ns.item_count} items (${ns.size_bytes} bytes)`);
462
console.log(`Last activity: ${ns.last_activity}`);
463
});
464
465
// Organize data by application domains
466
const domains = [
467
{ namespace: ["app", "users"], description: "User data and profiles" },
468
{ namespace: ["app", "sessions"], description: "Session management" },
469
{ namespace: ["app", "cache"], description: "Application cache" },
470
{ namespace: ["analytics", "reports"], description: "Generated reports" },
471
{ namespace: ["temp", "uploads"], description: "Temporary file data" }
472
];
473
474
// Store domain configuration
475
for (const domain of domains) {
476
await client.store.putItem(
477
["meta", "domains"],
478
domain.namespace.join("_"),
479
{
480
namespace: domain.namespace,
481
description: domain.description,
482
createdAt: new Date().toISOString(),
483
access: "internal"
484
},
485
{ index: ["namespace", "description"] }
486
);
487
}
488
```
489
490
### TTL and Expiration Management
491
492
```typescript
493
// Store temporary data with different TTL patterns
494
await client.store.putItem(
495
["temp", "quick_cache"],
496
"result_123",
497
{ data: "quick computation result" },
498
{ ttl: 300 } // 5 minutes
499
);
500
501
await client.store.putItem(
502
["temp", "medium_cache"],
503
"analysis_456",
504
{ analysis: "detailed analysis results" },
505
{ ttl: 3600 } // 1 hour
506
);
507
508
await client.store.putItem(
509
["persistent", "config"],
510
"app_settings",
511
{ version: "1.0", settings: { theme: "auto" } }
512
// No TTL - permanent storage
513
);
514
515
// Refresh TTL for frequently accessed items
516
const frequentlyUsedItem = await client.store.getItem(
517
["cache", "popular"],
518
"trending_data",
519
{ refreshTtl: true } // Extends expiration
520
);
521
522
// Clean up expired items (handled automatically, but can check)
523
const expiredItems = await client.store.searchItems(
524
["temp"],
525
{
526
filter: {
527
expires_at: { $lt: new Date().toISOString() }
528
},
529
includeExpired: true,
530
limit: 100
531
}
532
);
533
534
console.log(`Found ${expiredItems.total} expired items for cleanup`);
535
```
536
537
### Bulk Operations and Data Migration
538
539
```typescript
540
// Bulk data insertion for migration
541
const bulkUserData = [
542
{ id: "user_001", name: "John Doe", email: "john@example.com" },
543
{ id: "user_002", name: "Jane Smith", email: "jane@example.com" },
544
{ id: "user_003", name: "Bob Wilson", email: "bob@example.com" }
545
];
546
547
// Store multiple items (sequential for simplicity, can be parallelized)
548
for (const user of bulkUserData) {
549
await client.store.putItem(
550
["migration", "users"],
551
user.id,
552
{
553
...user,
554
migratedAt: new Date().toISOString(),
555
source: "legacy_system"
556
},
557
{
558
index: ["name", "email", "source"],
559
metadata: { batch: "migration_2024_01" }
560
}
561
);
562
}
563
564
// Parallel bulk operations for better performance
565
const migrationPromises = bulkUserData.map(user =>
566
client.store.putItem(
567
["migration", "users_parallel"],
568
user.id,
569
{ ...user, migratedAt: new Date().toISOString() },
570
{ index: ["name", "email"] }
571
)
572
);
573
574
await Promise.all(migrationPromises);
575
console.log("Bulk migration completed");
576
577
// Archive old data
578
const oldData = await client.store.searchItems(
579
["app", "users"],
580
{
581
filter: {
582
lastLogin: { $lt: "2023-01-01T00:00:00Z" } // Older than 1 year
583
},
584
limit: 1000
585
}
586
);
587
588
for (const item of oldData.items) {
589
// Archive to different namespace
590
await client.store.putItem(
591
["archive", "users"],
592
item.key,
593
{
594
...item.value,
595
archivedAt: new Date().toISOString(),
596
originalNamespace: item.namespace
597
},
598
{ ttl: 86400 * 365 * 2 } // Keep archives for 2 years
599
);
600
601
// Delete from active namespace
602
await client.store.deleteItem(item.namespace, item.key);
603
}
604
```
605
606
### Error Handling and Data Integrity
607
608
```typescript
609
async function robustStoreOperations() {
610
try {
611
// Attempt to store critical data with validation
612
const criticalData = {
613
id: "critical_001",
614
value: 1000000,
615
timestamp: new Date().toISOString(),
616
checksum: "abc123" // Simple integrity check
617
};
618
619
await client.store.putItem(
620
["critical", "financial"],
621
criticalData.id,
622
criticalData,
623
{
624
index: ["id", "timestamp"],
625
overwrite: false, // Fail if exists
626
metadata: { integrity: "validated" }
627
}
628
);
629
630
// Verify storage
631
const stored = await client.store.getItem(
632
["critical", "financial"],
633
criticalData.id
634
);
635
636
if (!stored || stored.value.checksum !== criticalData.checksum) {
637
throw new Error("Data integrity check failed");
638
}
639
640
console.log("Critical data stored and verified successfully");
641
642
} catch (error) {
643
console.error("Critical operation failed:", error);
644
645
// Attempt cleanup or recovery
646
try {
647
await client.store.deleteItem(["critical", "financial"], "critical_001");
648
console.log("Cleaned up partial data");
649
} catch (cleanupError) {
650
console.error("Cleanup also failed:", cleanupError);
651
}
652
653
throw error;
654
}
655
}
656
657
// Transactional-like operations (manual implementation)
658
async function pseudoTransactionalUpdate() {
659
const backupKey = `backup_${Date.now()}`;
660
661
try {
662
// Create backup before update
663
const original = await client.store.getItem(["data", "important"], "record_123");
664
if (original) {
665
await client.store.putItem(
666
["backups", "important"],
667
backupKey,
668
original.value,
669
{ ttl: 86400 } // 24h backup retention
670
);
671
}
672
673
// Perform update
674
await client.store.putItem(
675
["data", "important"],
676
"record_123",
677
{
678
...original?.value,
679
updated_at: new Date().toISOString(),
680
version: (original?.value.version || 0) + 1
681
}
682
);
683
684
console.log("Update completed successfully");
685
686
} catch (error) {
687
console.error("Update failed, attempting rollback:", error);
688
689
// Rollback using backup
690
const backup = await client.store.getItem(["backups", "important"], backupKey);
691
if (backup) {
692
await client.store.putItem(["data", "important"], "record_123", backup.value);
693
console.log("Rollback completed");
694
}
695
696
throw error;
697
} finally {
698
// Cleanup backup
699
await client.store.deleteItem(["backups", "important"], backupKey);
700
}
701
}
702
```
703
704
The StoreClient provides comprehensive key-value store capabilities with hierarchical organization, flexible search, TTL management, and robust indexing, making it ideal for persistent data storage, caching, and state management in LangGraph applications.