@farukada/aws-langgraph-dynamodb-ts 0.1.1 → 0.2.2
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 +131 -5
- package/dist/checkpointer/actions/delete-thread.d.ts.map +1 -1
- package/dist/checkpointer/actions/delete-thread.js +17 -9
- package/dist/checkpointer/actions/delete-thread.js.map +1 -1
- package/dist/checkpointer/actions/get-tuple.d.ts.map +1 -1
- package/dist/checkpointer/actions/get-tuple.js +70 -32
- package/dist/checkpointer/actions/get-tuple.js.map +1 -1
- package/dist/checkpointer/actions/put-writes.d.ts.map +1 -1
- package/dist/checkpointer/actions/put-writes.js +36 -19
- package/dist/checkpointer/actions/put-writes.js.map +1 -1
- package/dist/checkpointer/actions/put.d.ts.map +1 -1
- package/dist/checkpointer/actions/put.js +75 -18
- package/dist/checkpointer/actions/put.js.map +1 -1
- package/dist/checkpointer/actions/writer.d.ts +6 -4
- package/dist/checkpointer/actions/writer.d.ts.map +1 -1
- package/dist/checkpointer/actions/writer.js +12 -12
- package/dist/checkpointer/actions/writer.js.map +1 -1
- package/dist/checkpointer/index.d.ts +11 -4
- package/dist/checkpointer/index.d.ts.map +1 -1
- package/dist/checkpointer/index.js +97 -72
- package/dist/checkpointer/index.js.map +1 -1
- package/dist/checkpointer/types/index.d.ts +8 -0
- package/dist/checkpointer/types/index.d.ts.map +1 -1
- package/dist/checkpointer/utils/deserialization.d.ts.map +1 -1
- package/dist/checkpointer/utils/deserialization.js +28 -4
- package/dist/checkpointer/utils/deserialization.js.map +1 -1
- package/dist/checkpointer/utils/validation.d.ts +8 -8
- package/dist/checkpointer/utils/validation.d.ts.map +1 -1
- package/dist/checkpointer/utils/validation.js +45 -1
- package/dist/checkpointer/utils/validation.js.map +1 -1
- package/dist/factory.d.ts +14 -0
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +39 -20
- package/dist/factory.js.map +1 -1
- package/dist/history/actions/add-message.d.ts +5 -9
- package/dist/history/actions/add-message.d.ts.map +1 -1
- package/dist/history/actions/add-message.js +14 -42
- package/dist/history/actions/add-message.js.map +1 -1
- package/dist/history/actions/add-messages.d.ts +6 -10
- package/dist/history/actions/add-messages.d.ts.map +1 -1
- package/dist/history/actions/add-messages.js +33 -38
- package/dist/history/actions/add-messages.js.map +1 -1
- package/dist/history/actions/clear.d.ts.map +1 -1
- package/dist/history/actions/clear.js +3 -3
- package/dist/history/actions/clear.js.map +1 -1
- package/dist/history/actions/get-messages.d.ts.map +1 -1
- package/dist/history/actions/get-messages.js +2 -2
- package/dist/history/actions/get-messages.js.map +1 -1
- package/dist/history/actions/list-sessions.d.ts.map +1 -1
- package/dist/history/actions/list-sessions.js +6 -3
- package/dist/history/actions/list-sessions.js.map +1 -1
- package/dist/history/index.d.ts +41 -5
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/index.js +52 -18
- package/dist/history/index.js.map +1 -1
- package/dist/history/session-adapter.d.ts +39 -0
- package/dist/history/session-adapter.d.ts.map +1 -0
- package/dist/history/session-adapter.js +74 -0
- package/dist/history/session-adapter.js.map +1 -0
- package/dist/history/types/index.d.ts +32 -1
- package/dist/history/types/index.d.ts.map +1 -1
- package/dist/history/utils/index.d.ts +1 -0
- package/dist/history/utils/index.d.ts.map +1 -1
- package/dist/history/utils/index.js +1 -0
- package/dist/history/utils/index.js.map +1 -1
- package/dist/history/utils/optimistic-retry.d.ts +31 -0
- package/dist/history/utils/optimistic-retry.d.ts.map +1 -0
- package/dist/history/utils/optimistic-retry.js +74 -0
- package/dist/history/utils/optimistic-retry.js.map +1 -0
- package/dist/history/utils/title-generator.d.ts.map +1 -1
- package/dist/history/utils/title-generator.js +8 -3
- package/dist/history/utils/title-generator.js.map +1 -1
- package/dist/history/utils/update-expression-builder.d.ts +18 -21
- package/dist/history/utils/update-expression-builder.d.ts.map +1 -1
- package/dist/history/utils/update-expression-builder.js +31 -46
- package/dist/history/utils/update-expression-builder.js.map +1 -1
- package/dist/history/utils/validation.d.ts.map +1 -1
- package/dist/history/utils/validation.js +23 -3
- package/dist/history/utils/validation.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/utils/batch-write.d.ts +44 -6
- package/dist/shared/utils/batch-write.d.ts.map +1 -1
- package/dist/shared/utils/batch-write.js +76 -28
- package/dist/shared/utils/batch-write.js.map +1 -1
- package/dist/shared/utils/client-factory.d.ts +25 -0
- package/dist/shared/utils/client-factory.d.ts.map +1 -0
- package/dist/shared/utils/client-factory.js +23 -0
- package/dist/shared/utils/client-factory.js.map +1 -0
- package/dist/shared/utils/compressor.d.ts +21 -3
- package/dist/shared/utils/compressor.d.ts.map +1 -1
- package/dist/shared/utils/compressor.js +75 -10
- package/dist/shared/utils/compressor.js.map +1 -1
- package/dist/shared/utils/constants.d.ts +1 -1
- package/dist/shared/utils/constants.d.ts.map +1 -1
- package/dist/shared/utils/constants.js +5 -1
- package/dist/shared/utils/constants.js.map +1 -1
- package/dist/shared/utils/index.d.ts +3 -0
- package/dist/shared/utils/index.d.ts.map +1 -1
- package/dist/shared/utils/index.js +3 -0
- package/dist/shared/utils/index.js.map +1 -1
- package/dist/shared/utils/logger.d.ts +27 -0
- package/dist/shared/utils/logger.d.ts.map +1 -1
- package/dist/shared/utils/logger.js +86 -0
- package/dist/shared/utils/logger.js.map +1 -1
- package/dist/shared/utils/retry.d.ts +7 -0
- package/dist/shared/utils/retry.d.ts.map +1 -1
- package/dist/shared/utils/retry.js +75 -17
- package/dist/shared/utils/retry.js.map +1 -1
- package/dist/shared/utils/s3-offloader.d.ts +10 -3
- package/dist/shared/utils/s3-offloader.d.ts.map +1 -1
- package/dist/shared/utils/s3-offloader.js +19 -9
- package/dist/shared/utils/s3-offloader.js.map +1 -1
- package/dist/shared/utils/s3-orphans.d.ts +16 -0
- package/dist/shared/utils/s3-orphans.d.ts.map +1 -0
- package/dist/shared/utils/s3-orphans.js +79 -0
- package/dist/shared/utils/s3-orphans.js.map +1 -0
- package/dist/shared/utils/sleep.d.ts +48 -0
- package/dist/shared/utils/sleep.d.ts.map +1 -0
- package/dist/shared/utils/sleep.js +82 -0
- package/dist/shared/utils/sleep.js.map +1 -0
- package/dist/shared/utils/ttl.d.ts +6 -2
- package/dist/shared/utils/ttl.d.ts.map +1 -1
- package/dist/shared/utils/ttl.js +17 -2
- package/dist/shared/utils/ttl.js.map +1 -1
- package/dist/store/actions/get-operation.d.ts.map +1 -1
- package/dist/store/actions/get-operation.js +2 -2
- package/dist/store/actions/get-operation.js.map +1 -1
- package/dist/store/actions/list-namespaces-operation.d.ts.map +1 -1
- package/dist/store/actions/list-namespaces-operation.js +44 -29
- package/dist/store/actions/list-namespaces-operation.js.map +1 -1
- package/dist/store/actions/put-operation.d.ts.map +1 -1
- package/dist/store/actions/put-operation.js +19 -5
- package/dist/store/actions/put-operation.js.map +1 -1
- package/dist/store/actions/search-operation.d.ts.map +1 -1
- package/dist/store/actions/search-operation.js +54 -21
- package/dist/store/actions/search-operation.js.map +1 -1
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +24 -20
- package/dist/store/index.js.map +1 -1
- package/dist/store/types/index.d.ts +33 -1
- package/dist/store/types/index.d.ts.map +1 -1
- package/dist/store/utils/filter.d.ts.map +1 -1
- package/dist/store/utils/filter.js +63 -1
- package/dist/store/utils/filter.js.map +1 -1
- package/dist/store/utils/validation.d.ts +6 -2
- package/dist/store/utils/validation.d.ts.map +1 -1
- package/dist/store/utils/validation.js +21 -5
- package/dist/store/utils/validation.js.map +1 -1
- package/package.json +39 -25
package/README.md
CHANGED
|
@@ -171,6 +171,14 @@ await history.addMessages('user-123', 'session-456', [
|
|
|
171
171
|
const sessions = await history.listSessions('user-123');
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
+
> **TTL semantics.** The session metadata item's TTL is refreshed on every
|
|
175
|
+
> `addMessage`/`addMessages` call, so `listSessions()` keeps reporting the
|
|
176
|
+
> session as live while activity continues. Individual message items get their
|
|
177
|
+
> TTL stamped at write time and expire independently — a long-lived session can
|
|
178
|
+
> develop gaps where older messages drop out while recent ones persist. If that
|
|
179
|
+
> isn't acceptable, set `ttlDays` well above your expected session lifetime or
|
|
180
|
+
> manage deletion explicitly via `clear()`.
|
|
181
|
+
|
|
174
182
|
### Factory (One-Liner)
|
|
175
183
|
|
|
176
184
|
```typescript
|
|
@@ -327,13 +335,16 @@ When TTL and S3 offloading are both enabled, the library automatically configure
|
|
|
327
335
|
### Store Filters
|
|
328
336
|
|
|
329
337
|
```typescript
|
|
330
|
-
//
|
|
338
|
+
// Supported operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin.
|
|
339
|
+
// Filter keys are fields inside the stored `value` — the library wraps each
|
|
340
|
+
// key as `value.<key>` when building the DynamoDB FilterExpression.
|
|
331
341
|
const [results] = await store.batch([
|
|
332
342
|
{
|
|
333
343
|
namespacePrefix: ['products'],
|
|
334
344
|
filter: {
|
|
335
|
-
|
|
336
|
-
|
|
345
|
+
price: { $gte: 10, $lte: 100 },
|
|
346
|
+
category: { $in: ['electronics', 'books'] },
|
|
347
|
+
status: { $ne: 'archived' },
|
|
337
348
|
},
|
|
338
349
|
limit: 10,
|
|
339
350
|
},
|
|
@@ -349,6 +360,89 @@ const [results] = await store.batch([
|
|
|
349
360
|
['documents', 'category', 'subcategory']
|
|
350
361
|
```
|
|
351
362
|
|
|
363
|
+
### LangChain `RunnableWithMessageHistory` integration
|
|
364
|
+
|
|
365
|
+
`DynamoDBChatMessageHistory` is a multi-session store; to hand a single
|
|
366
|
+
`(userId, sessionId)` pair to LangChain's `RunnableWithMessageHistory`, bind it
|
|
367
|
+
with `forSession()` which returns a `BaseListChatMessageHistory` compatible
|
|
368
|
+
instance:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
import { RunnableWithMessageHistory } from '@langchain/core/runnables';
|
|
372
|
+
import { DynamoDBChatMessageHistory } from '@farukada/aws-langgraph-dynamodb-ts';
|
|
373
|
+
|
|
374
|
+
const store = new DynamoDBChatMessageHistory({ tableName: 'chat-sessions' });
|
|
375
|
+
|
|
376
|
+
const chain = new RunnableWithMessageHistory({
|
|
377
|
+
runnable,
|
|
378
|
+
getMessageHistory: (sessionId) => store.forSession(userId, sessionId),
|
|
379
|
+
inputMessagesKey: 'input',
|
|
380
|
+
historyMessagesKey: 'history',
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Atomicity contract
|
|
385
|
+
|
|
386
|
+
| Operation | Atomicity scope | Failure mode |
|
|
387
|
+
|---|---|---|
|
|
388
|
+
| `DynamoDBSaver.put()` | Single `TransactWrite` on the **checkpoints** table. Both the metadata item and the payload item either both persist or neither does. | Throws; any S3 objects already uploaded are best-effort cleaned, otherwise swept by the S3 lifecycle rule. |
|
|
389
|
+
| `DynamoDBSaver.putWrites()` | `BatchWriteItem` against the **writes** table, chunked in groups of 25. Each 25-item batch is independently atomic at the DynamoDB level; batches are *not* transactional with each other. | On retry exhaustion, throws `BatchWriteIncompleteError` with `.succeededCount` and `.unprocessed` so callers can drive reconciliation. Items already acked by DynamoDB remain persisted. |
|
|
390
|
+
| `put()` ↔ `putWrites()` | **Not** atomic across tables. LangGraph calls them as distinct steps; a crash between them leaves a checkpoint without its writes (or vice versa). | LangGraph's node-retry loop will re-invoke both; the idempotency guard on `put()` makes re-submission safe. |
|
|
391
|
+
| `DynamoDBChatMessageHistory.addMessage(s)` | `TransactWriteItems` — up to 99 message items plus the metadata counter update, guarded by an optimistic `messageCount` condition. All-or-nothing per call. | Throws on conflict; the helper retries up to 5× on `ConditionalCheckFailed` sub-reasons. |
|
|
392
|
+
| `DynamoDBStore.batch()` (Put) | Per-item `UpdateItem` / `PutItem`. Batch failure is per-operation. | Each op's error propagates via `Promise.all`. |
|
|
393
|
+
|
|
394
|
+
### `put()` optimistic-concurrency guard
|
|
395
|
+
|
|
396
|
+
Every `put()` writes metadata with a `ConditionExpression` that rejects the
|
|
397
|
+
transaction when a pre-existing row for the same `(thread_id, checkpoint_id)`
|
|
398
|
+
disagrees on `parent_checkpoint_id` or serializer `type`. Effect:
|
|
399
|
+
|
|
400
|
+
- A fresh checkpoint always succeeds.
|
|
401
|
+
- A retry after a transient error (network blip, throttling) re-issues the same
|
|
402
|
+
`(parent, type)` and succeeds as if idempotent.
|
|
403
|
+
- Two concurrent workers writing the same `checkpoint_id` but with different
|
|
404
|
+
lineage surface a `ConditionalCheckFailedException` instead of silently
|
|
405
|
+
last-writer-wins. Treat it as a signal that your application is accidentally
|
|
406
|
+
racing two graphs on the same thread.
|
|
407
|
+
|
|
408
|
+
### Limits and safety caps
|
|
409
|
+
|
|
410
|
+
The library enforces a set of client-side caps to produce clean, actionable
|
|
411
|
+
errors before DynamoDB / zlib / memory blow up on pathological inputs. All of
|
|
412
|
+
them are configurable or can be raised if a legitimate workload needs more.
|
|
413
|
+
|
|
414
|
+
| Cap | Default | Raise via | Hit when |
|
|
415
|
+
|---|---|---|---|
|
|
416
|
+
| Decompressed checkpoint size | 50 MiB | `compression.maxDecompressedBytes` | Gzip payload expands beyond the cap — defends against gzip bombs. |
|
|
417
|
+
| Filter expression string | 3.5 KiB | not configurable — simplify the filter | Complex / deeply-nested `Store.batch` searches. DynamoDB's hard limit is 4 KiB. |
|
|
418
|
+
| `$in` / `$nin` array size | 50 values | not configurable — split the query | Store filter with oversized enum sets. |
|
|
419
|
+
| `list()` page iterations | 1000 pages | not configurable — narrow the filter or add a GSI | Filters that match almost nothing across a very large thread. |
|
|
420
|
+
| `deleteThread()` page iterations | 10 000 pages | not configurable — investigate the thread | Effectively "unbounded for real workloads"; only trips on extreme datasets. |
|
|
421
|
+
| Retry `cause`-chain depth | 32 | not configurable | Hostile error with an absurd nesting depth; prevents stack-overflow DoS. |
|
|
422
|
+
|
|
423
|
+
### S3 encryption
|
|
424
|
+
|
|
425
|
+
Offloaded checkpoint payloads are written with `ServerSideEncryption: AES256`
|
|
426
|
+
by default (matching S3's own default since 2023). If your bucket policy
|
|
427
|
+
enforces a different algorithm (e.g. `aws:kms`), set it explicitly:
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
s3OffloadConfig: {
|
|
431
|
+
bucketName: 'my-checkpoints-bucket',
|
|
432
|
+
serverSideEncryption: 'aws:kms',
|
|
433
|
+
sseKmsKeyId: 'alias/my-key',
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
IAM: the `s3:PutObject` permission granted in the minimum policy above is
|
|
438
|
+
sufficient. If your bucket policy denies the `AES256` algorithm, either grant
|
|
439
|
+
the key permissions required for `aws:kms` or set `serverSideEncryption`
|
|
440
|
+
explicitly to match the bucket policy.
|
|
441
|
+
|
|
442
|
+
### Checkpoint data migration (v<4)
|
|
443
|
+
|
|
444
|
+
LangGraph bumped the `Checkpoint.v` field to **4** in `@langchain/langgraph-checkpoint@1.0`. Reading data written by older versions (v<4) requires synthesizing the `TASKS` channel from parent-checkpoint writes — this library does not migrate on read. If you have pre-1.0 data, run LangGraph's reference migration before switching to this saver, or drop the older checkpoints.
|
|
445
|
+
|
|
352
446
|
<a id="configuration-reference"></a>
|
|
353
447
|
|
|
354
448
|
## Configuration Reference
|
|
@@ -473,13 +567,45 @@ const [results] = await store.batch([
|
|
|
473
567
|
|
|
474
568
|
## Testing
|
|
475
569
|
|
|
570
|
+
### Unit tests
|
|
571
|
+
|
|
476
572
|
```bash
|
|
477
|
-
npm test # Run all tests
|
|
573
|
+
npm test # Run all tests (672 unit tests, ~28s)
|
|
478
574
|
npm test -- --coverage # With coverage
|
|
479
575
|
npm run build # Type-check + compile
|
|
480
576
|
npm run lint # ESLint
|
|
481
577
|
```
|
|
482
578
|
|
|
579
|
+
### Integration tests (DynamoDB Local)
|
|
580
|
+
|
|
581
|
+
A dedicated integration tier verifies the library against a real DynamoDB API
|
|
582
|
+
(catches things `aws-sdk-client-mock` cannot, such as `ValidationException` on
|
|
583
|
+
malformed filter expressions, `ConditionExpression` enforcement, primary-key
|
|
584
|
+
attribute rules, and lex-sort assumptions on sort keys).
|
|
585
|
+
|
|
586
|
+
```bash
|
|
587
|
+
npm run test:integration:up # docker compose up (amazon/dynamodb-local)
|
|
588
|
+
npm run test:integration # 15 e2e tests against localhost:8000
|
|
589
|
+
npm run test:integration:down # docker compose down
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
CI runs the same suite via a `DynamoDB Local` GitHub Actions service container
|
|
593
|
+
on every pull request.
|
|
594
|
+
|
|
595
|
+
### Mutation testing (Stryker)
|
|
596
|
+
|
|
597
|
+
Line coverage tells you what code ran; **mutation coverage** tells you what
|
|
598
|
+
tests would *catch regressions*. Run against a hot spot:
|
|
599
|
+
|
|
600
|
+
```bash
|
|
601
|
+
npm run test:mutate:quick # retry.ts, compressor.ts, filter.ts (~15 min)
|
|
602
|
+
npm run test:mutate # full src/ (long — use incremental mode)
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Stryker runs with `incremental: true`, so re-runs only re-check files that
|
|
606
|
+
changed since the last run. Baseline kill-rate goal: **≥80%** for new code in
|
|
607
|
+
`src/shared/` and `src/checkpointer/`.
|
|
608
|
+
|
|
483
609
|
<a id="project-structure"></a>
|
|
484
610
|
|
|
485
611
|
## Project Structure
|
|
@@ -526,5 +652,5 @@ MIT © [FarukAda](https://github.com/farukada)
|
|
|
526
652
|
<p align="center">
|
|
527
653
|
Built with <a href="https://langchain-ai.github.io/langgraphjs/">LangGraph</a> · <a href="https://aws.amazon.com/sdk-for-javascript/">AWS SDK v3</a> · <a href="https://github.com/langchain-ai/langchainjs">LangChain</a>
|
|
528
654
|
<br/>
|
|
529
|
-
<a href="https://www.npmjs.com/package/@farukada/aws-langgraph-dynamodb-ts">npm</a> · <a href="https://github.com/
|
|
655
|
+
<a href="https://www.npmjs.com/package/@farukada/aws-langgraph-dynamodb-ts">npm</a> · <a href="https://github.com/FarukAda/aws-langgraph-dynamodb-ts">GitHub</a> · <a href="https://github.com/FarukAda/aws-langgraph-dynamodb-ts/issues">Issues</a>
|
|
530
656
|
</p>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete-thread.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/delete-thread.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,wBAAwB,EAG9B,MAAM,UAAU,CAAC;AAclB;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAU,QAAQ,wBAAwB,KAAG,OAAO,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"delete-thread.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/delete-thread.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,wBAAwB,EAG9B,MAAM,UAAU,CAAC;AAclB;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAU,QAAQ,wBAAwB,KAAG,OAAO,CAAC,IAAI,CAsJvF,CAAC"}
|
|
@@ -14,16 +14,23 @@ const writer_1 = require("./writer");
|
|
|
14
14
|
const deleteThreadAction = async (params) => {
|
|
15
15
|
// Validate thread ID
|
|
16
16
|
(0, utils_1.validateThreadId)(params.threadId);
|
|
17
|
-
// Query returns BOTH metadata items and PAYLOAD# items for the thread
|
|
17
|
+
// Query returns BOTH metadata items and PAYLOAD# items for the thread.
|
|
18
|
+
// We don't set a per-query Limit — DynamoDB's default 1 MB page is far more
|
|
19
|
+
// efficient than many small 100-item queries. The real safeguard is the
|
|
20
|
+
// MAX_DELETE_BATCH_SIZE total-item cap below.
|
|
18
21
|
const allItems = [];
|
|
19
22
|
let lastEvaluatedKey;
|
|
20
23
|
let iterationCount = 0;
|
|
21
|
-
|
|
22
|
-
//
|
|
24
|
+
// Deliberately higher than `MAX_LOOP_ITERATIONS` used in list()/search: deletes
|
|
25
|
+
// must walk every page of a thread (there's no early-exit when a user limit is
|
|
26
|
+
// satisfied), so the bound is set to "effectively unbounded for practical
|
|
27
|
+
// workloads". The real guard is MAX_DELETE_BATCH_SIZE total items held in memory.
|
|
28
|
+
const MAX_DELETE_PAGES = 10_000;
|
|
23
29
|
do {
|
|
24
30
|
iterationCount++;
|
|
25
|
-
if (iterationCount >
|
|
26
|
-
throw new Error(
|
|
31
|
+
if (iterationCount > MAX_DELETE_PAGES) {
|
|
32
|
+
throw new Error(`deleteThread scanned more than ${MAX_DELETE_PAGES} DynamoDB pages without completing. ` +
|
|
33
|
+
`The thread has an extreme number of checkpoints — investigate before retrying.`);
|
|
27
34
|
}
|
|
28
35
|
const result = await (0, shared_1.withDynamoDBRetry)(async () => {
|
|
29
36
|
return await params.client.query({
|
|
@@ -32,10 +39,9 @@ const deleteThreadAction = async (params) => {
|
|
|
32
39
|
ExpressionAttributeValues: {
|
|
33
40
|
':thread_id': params.threadId,
|
|
34
41
|
},
|
|
35
|
-
Limit: 100, // Fetch in smaller batches
|
|
36
42
|
ExclusiveStartKey: lastEvaluatedKey,
|
|
37
43
|
});
|
|
38
|
-
});
|
|
44
|
+
}, { signal: params.signal });
|
|
39
45
|
if (result.Items && result.Items.length > 0) {
|
|
40
46
|
allItems.push(...result.Items);
|
|
41
47
|
// Safety check: prevent deleting too many items at once
|
|
@@ -78,7 +84,7 @@ const deleteThreadAction = async (params) => {
|
|
|
78
84
|
},
|
|
79
85
|
},
|
|
80
86
|
}));
|
|
81
|
-
await (0, shared_1.batchWriteAllWithRetry)(params.client, params.checkpointsTableName, checkpointDeleteRequests);
|
|
87
|
+
await (0, shared_1.batchWriteAllWithRetry)(params.client, params.checkpointsTableName, checkpointDeleteRequests, { signal: params.signal });
|
|
82
88
|
// Fetch writes for metadata items only (payload items don't have associated writes)
|
|
83
89
|
const allWriteDeleteRequests = [];
|
|
84
90
|
const QUERY_CONCURRENCY = 10;
|
|
@@ -108,7 +114,9 @@ const deleteThreadAction = async (params) => {
|
|
|
108
114
|
}
|
|
109
115
|
}
|
|
110
116
|
// Delete all writes using shared batch write utility
|
|
111
|
-
await (0, shared_1.batchWriteAllWithRetry)(params.client, params.writesTableName, allWriteDeleteRequests
|
|
117
|
+
await (0, shared_1.batchWriteAllWithRetry)(params.client, params.writesTableName, allWriteDeleteRequests, {
|
|
118
|
+
signal: params.signal,
|
|
119
|
+
});
|
|
112
120
|
// Clean up S3 offloaded data
|
|
113
121
|
if (params.s3Offloader && s3KeysToDelete.length > 0) {
|
|
114
122
|
await params.s3Offloader.deleteBatch(s3KeysToDelete);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delete-thread.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/delete-thread.ts"],"names":[],"mappings":";;;AAEA,yCAAyE;AACzE,oCAKkB;AAClB,oCAA6E;AAC7E,qCAAkC;AAYlC;;;;;GAKG;AACI,MAAM,kBAAkB,GAAG,KAAK,EAAE,MAAgC,EAAiB,EAAE;IAC1F,qBAAqB;IACrB,IAAA,wBAAgB,EAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAElC,
|
|
1
|
+
{"version":3,"file":"delete-thread.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/delete-thread.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-tuple.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/get-tuple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA0B,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAG/F,OAAO,EAIL,KAAK,oBAAoB,EAG1B,MAAM,UAAU,CAAC;AAKlB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GACzB,QAAQ,oBAAoB,KAC3B,OAAO,CAAC,eAAe,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"get-tuple.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/get-tuple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA0B,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAG/F,OAAO,EAIL,KAAK,oBAAoB,EAG1B,MAAM,UAAU,CAAC;AAKlB;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,cAAc,GACzB,QAAQ,oBAAoB,KAC3B,OAAO,CAAC,eAAe,GAAG,SAAS,CAmLrC,CAAC"}
|
|
@@ -30,31 +30,55 @@ const getTupleAction = async (params) => {
|
|
|
30
30
|
thread_id: configurable.thread_id,
|
|
31
31
|
checkpoint_id: configurable.checkpoint_id,
|
|
32
32
|
},
|
|
33
|
+
ConsistentRead: true,
|
|
33
34
|
});
|
|
34
|
-
});
|
|
35
|
+
}, { signal: params.signal });
|
|
35
36
|
return item.Item;
|
|
36
37
|
}
|
|
37
38
|
else {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
39
|
+
// Select metadata items only. We cannot bound by `checkpoint_id` in the
|
|
40
|
+
// KeyCondition — any attempt to exclude payload items via `checkpoint_id
|
|
41
|
+
// < 'PAYLOAD#'` silently drops metadata whose IDs sort lexicographically
|
|
42
|
+
// above `PAYLOAD#` (e.g. anything starting with a lowercase letter,
|
|
43
|
+
// which includes common IDs like `ckpt-1`, `checkpoint-2`, etc.), and
|
|
44
|
+
// DynamoDB forbids primary-key attributes inside FilterExpression.
|
|
45
|
+
// Instead, filter on the non-key `type` attribute that only metadata
|
|
46
|
+
// items carry (`attribute_exists(#type)`). Payload items are never
|
|
47
|
+
// returned, regardless of user ID character set.
|
|
48
|
+
//
|
|
49
|
+
// Because Limit applies *before* FilterExpression in DynamoDB, when
|
|
50
|
+
// mixing metadata and payload pages we fetch a larger page and paginate
|
|
51
|
+
// until a metadata item is found. For high-volume threads the ordering
|
|
52
|
+
// still surfaces the newest metadata within the first 1-2 pages.
|
|
53
|
+
const hasNsFilter = !!configurable.checkpoint_ns;
|
|
54
|
+
const filterParts = ['attribute_exists(#type)'];
|
|
55
|
+
if (hasNsFilter)
|
|
56
|
+
filterParts.push('checkpoint_ns = :checkpoint_ns');
|
|
57
|
+
const expressionValues = {
|
|
58
|
+
':thread_id': configurable.thread_id,
|
|
59
|
+
...(hasNsFilter && { ':checkpoint_ns': configurable.checkpoint_ns }),
|
|
60
|
+
};
|
|
61
|
+
let lastKey;
|
|
62
|
+
do {
|
|
63
|
+
const result = await (0, shared_1.withDynamoDBRetry)(async () => {
|
|
64
|
+
return await params.client.query({
|
|
65
|
+
TableName: params.checkpointsTableName,
|
|
66
|
+
KeyConditionExpression: 'thread_id = :thread_id',
|
|
67
|
+
ExpressionAttributeValues: expressionValues,
|
|
68
|
+
ExpressionAttributeNames: { '#type': 'type' },
|
|
69
|
+
FilterExpression: filterParts.join(' AND '),
|
|
70
|
+
ConsistentRead: true,
|
|
71
|
+
ScanIndexForward: false, // Descending — newest first
|
|
72
|
+
ExclusiveStartKey: lastKey,
|
|
73
|
+
});
|
|
74
|
+
}, { signal: params.signal });
|
|
75
|
+
const items = result.Items;
|
|
76
|
+
if (items && items.length > 0) {
|
|
77
|
+
return items[0];
|
|
78
|
+
}
|
|
79
|
+
lastKey = result.LastEvaluatedKey;
|
|
80
|
+
} while (lastKey);
|
|
81
|
+
return undefined;
|
|
58
82
|
}
|
|
59
83
|
};
|
|
60
84
|
const item = await getItem((0, validate_configurable_1.validateConfigurable)(params.config.configurable));
|
|
@@ -74,7 +98,10 @@ const getTupleAction = async (params) => {
|
|
|
74
98
|
checkpointData = item.checkpoint;
|
|
75
99
|
}
|
|
76
100
|
else {
|
|
77
|
-
// New split format — fetch payload item separately
|
|
101
|
+
// New split format — fetch payload item separately.
|
|
102
|
+
// ConsistentRead must match the metadata read above: without it, a freshly-written
|
|
103
|
+
// payload can be missing from an eventually-consistent read even though the metadata
|
|
104
|
+
// item said it exists, producing spurious "payload not found" errors under write load.
|
|
78
105
|
const payloadResult = await (0, shared_1.withDynamoDBRetry)(async () => {
|
|
79
106
|
return await params.client.get({
|
|
80
107
|
TableName: params.checkpointsTableName,
|
|
@@ -82,8 +109,9 @@ const getTupleAction = async (params) => {
|
|
|
82
109
|
thread_id: item.thread_id,
|
|
83
110
|
checkpoint_id: `${types_1.PAYLOAD_SK_PREFIX}${item.checkpoint_id}`,
|
|
84
111
|
},
|
|
112
|
+
ConsistentRead: true,
|
|
85
113
|
});
|
|
86
|
-
});
|
|
114
|
+
}, { signal: params.signal });
|
|
87
115
|
const payloadItem = payloadResult.Item;
|
|
88
116
|
if (!payloadItem) {
|
|
89
117
|
throw new Error(`Checkpoint payload item not found for thread_id=${item.thread_id}, checkpoint_id=${item.checkpoint_id}`);
|
|
@@ -94,6 +122,9 @@ const getTupleAction = async (params) => {
|
|
|
94
122
|
const allWriteItems = [];
|
|
95
123
|
let writesLastEvaluatedKey;
|
|
96
124
|
do {
|
|
125
|
+
// ConsistentRead is required here: putWrites races with getTuple under concurrent
|
|
126
|
+
// workers, and an eventually-consistent Query can return a stale view that omits
|
|
127
|
+
// writes that have already been acked.
|
|
97
128
|
const writesResult = await (0, shared_1.withDynamoDBRetry)(async () => {
|
|
98
129
|
return await params.client.query({
|
|
99
130
|
TableName: params.writesTableName,
|
|
@@ -101,27 +132,34 @@ const getTupleAction = async (params) => {
|
|
|
101
132
|
ExpressionAttributeValues: {
|
|
102
133
|
':thread_id_checkpoint_id_checkpoint_ns': writer_1.Writer.getPartitionKey(item),
|
|
103
134
|
},
|
|
135
|
+
ConsistentRead: true,
|
|
104
136
|
ExclusiveStartKey: writesLastEvaluatedKey,
|
|
105
137
|
});
|
|
106
|
-
});
|
|
138
|
+
}, { signal: params.signal });
|
|
107
139
|
if (writesResult.Items) {
|
|
108
140
|
allWriteItems.push(...writesResult.Items);
|
|
109
141
|
}
|
|
110
142
|
writesLastEvaluatedKey = writesResult.LastEvaluatedKey;
|
|
111
143
|
} while (writesLastEvaluatedKey);
|
|
112
|
-
|
|
113
|
-
|
|
144
|
+
// Deserialize pending writes in parallel. Each write may involve an S3 download
|
|
145
|
+
// plus decompress + serde — sequential awaits in a hot path were measurable on
|
|
146
|
+
// checkpoints with many offloaded writes.
|
|
147
|
+
const pendingWrites = await Promise.all(allWriteItems.map(async (writeItem) => {
|
|
114
148
|
const write = writer_1.Writer.fromDynamoDBItem(writeItem);
|
|
115
|
-
// Download from S3 if offloaded
|
|
116
149
|
let rawValue = write.value;
|
|
117
|
-
if (writeItem.s3_value_key
|
|
150
|
+
if (writeItem.s3_value_key) {
|
|
151
|
+
if (!params.s3Offloader) {
|
|
152
|
+
throw new Error(`Pending write references S3 key '${writeItem.s3_value_key}' but no S3 offloader is configured. ` +
|
|
153
|
+
`Pass s3OffloadConfig when constructing DynamoDBSaver to read offloaded writes.`);
|
|
154
|
+
}
|
|
118
155
|
rawValue = await params.s3Offloader.download(writeItem.s3_value_key);
|
|
119
156
|
}
|
|
120
|
-
|
|
121
|
-
|
|
157
|
+
const writeValue = params.compressor
|
|
158
|
+
? await params.compressor.decompress(rawValue)
|
|
159
|
+
: rawValue;
|
|
122
160
|
const value = await params.serde.loadsTyped(write.type, writeValue);
|
|
123
|
-
|
|
124
|
-
}
|
|
161
|
+
return [write.task_id, write.channel, value];
|
|
162
|
+
}));
|
|
125
163
|
// Deserialize the checkpoint tuple from metadata item + payload data
|
|
126
164
|
const checkpointTuple = await (0, utils_1.deserializeCheckpointTuple)(item, checkpointData, params.serde, params.compressor, params.s3Offloader);
|
|
127
165
|
// Add pending writes to the tuple
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-tuple.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/get-tuple.ts"],"names":[],"mappings":";;;AAEA,yCAAiD;AACjD,oCAOkB;AAClB,oCAAsD;AACtD,mEAA+D;AAC/D,qCAAkC;AAElC;;;;;;;;;;;;;GAaG;AACI,MAAM,cAAc,GAAG,KAAK,EACjC,MAA4B,EACU,EAAE;IACxC,MAAM,OAAO,GAAG,KAAK,EAAE,YAAmC,EAAE,EAAE;QAC5D,IAAI,YAAY,CAAC,aAAa,IAAI,IAAI,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,MAAM,IAAA,0BAAiB,
|
|
1
|
+
{"version":3,"file":"get-tuple.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/get-tuple.ts"],"names":[],"mappings":";;;AAEA,yCAAiD;AACjD,oCAOkB;AAClB,oCAAsD;AACtD,mEAA+D;AAC/D,qCAAkC;AAElC;;;;;;;;;;;;;GAaG;AACI,MAAM,cAAc,GAAG,KAAK,EACjC,MAA4B,EACU,EAAE;IACxC,MAAM,OAAO,GAAG,KAAK,EAAE,YAAmC,EAAE,EAAE;QAC5D,IAAI,YAAY,CAAC,aAAa,IAAI,IAAI,EAAE,CAAC;YACvC,MAAM,IAAI,GAAG,MAAM,IAAA,0BAAiB,EAClC,KAAK,IAAI,EAAE;gBACT,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;oBAC7B,SAAS,EAAE,MAAM,CAAC,oBAAoB;oBACtC,GAAG,EAAE;wBACH,SAAS,EAAE,YAAY,CAAC,SAAS;wBACjC,aAAa,EAAE,YAAY,CAAC,aAAa;qBAC1C;oBACD,cAAc,EAAE,IAAI;iBACrB,CAAC,CAAC;YACL,CAAC,EACD,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAC1B,CAAC;YACF,OAAO,IAAI,CAAC,IAAkC,CAAC;QACjD,CAAC;aAAM,CAAC;YACN,wEAAwE;YACxE,yEAAyE;YACzE,yEAAyE;YACzE,oEAAoE;YACpE,sEAAsE;YACtE,mEAAmE;YACnE,qEAAqE;YACrE,mEAAmE;YACnE,iDAAiD;YACjD,EAAE;YACF,oEAAoE;YACpE,wEAAwE;YACxE,uEAAuE;YACvE,iEAAiE;YACjE,MAAM,WAAW,GAAG,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC;YACjD,MAAM,WAAW,GAAG,CAAC,yBAAyB,CAAC,CAAC;YAChD,IAAI,WAAW;gBAAE,WAAW,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;YAEpE,MAAM,gBAAgB,GAA4B;gBAChD,YAAY,EAAE,YAAY,CAAC,SAAS;gBACpC,GAAG,CAAC,WAAW,IAAI,EAAE,gBAAgB,EAAE,YAAY,CAAC,aAAa,EAAE,CAAC;aACrE,CAAC;YAEF,IAAI,OAA4C,CAAC;YACjD,GAAG,CAAC;gBACF,MAAM,MAAM,GAAG,MAAM,IAAA,0BAAiB,EACpC,KAAK,IAAI,EAAE;oBACT,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;wBAC/B,SAAS,EAAE,MAAM,CAAC,oBAAoB;wBACtC,sBAAsB,EAAE,wBAAwB;wBAChD,yBAAyB,EAAE,gBAAgB;wBAC3C,wBAAwB,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE;wBAC7C,gBAAgB,EAAE,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC;wBAC3C,cAAc,EAAE,IAAI;wBACpB,gBAAgB,EAAE,KAAK,EAAE,4BAA4B;wBACrD,iBAAiB,EAAE,OAAO;qBAC3B,CAAC,CAAC;gBACL,CAAC,EACD,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAC1B,CAAC;gBACF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAqC,CAAC;gBAC3D,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC9B,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;gBACD,OAAO,GAAG,MAAM,CAAC,gBAAuD,CAAC;YAC3E,CAAC,QAAQ,OAAO,EAAE;YAClB,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAA,4CAAoB,EAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;IAC7E,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,iDAAiD;IACjD,6EAA6E;IAC7E,kEAAkE;IAClE,IAAI,cAA0B,CAAC;IAC/B,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,yEAAyE;QACzE,cAAc,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC;SAAM,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,wDAAwD;QACxD,cAAc,GAAG,IAAI,CAAC,UAAU,CAAC;IACnC,CAAC;SAAM,CAAC;QACN,oDAAoD;QACpD,mFAAmF;QACnF,qFAAqF;QACrF,uFAAuF;QACvF,MAAM,aAAa,GAAG,MAAM,IAAA,0BAAiB,EAC3C,KAAK,IAAI,EAAE;YACT,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC;gBAC7B,SAAS,EAAE,MAAM,CAAC,oBAAoB;gBACtC,GAAG,EAAE;oBACH,SAAS,EAAE,IAAI,CAAC,SAAS;oBACzB,aAAa,EAAE,GAAG,yBAAiB,GAAG,IAAI,CAAC,aAAa,EAAE;iBAC3D;gBACD,cAAc,EAAE,IAAI;aACrB,CAAC,CAAC;QACL,CAAC,EACD,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAC1B,CAAC;QAEF,MAAM,WAAW,GAAG,aAAa,CAAC,IAAyC,CAAC;QAC5E,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,mDAAmD,IAAI,CAAC,SAAS,mBAAmB,IAAI,CAAC,aAAa,EAAE,CACzG,CAAC;QACJ,CAAC;QACD,cAAc,GAAG,WAAW,CAAC,UAAU,CAAC;IAC1C,CAAC;IAED,iFAAiF;IACjF,MAAM,aAAa,GAAwB,EAAE,CAAC;IAC9C,IAAI,sBAA2D,CAAC;IAEhE,GAAG,CAAC;QACF,kFAAkF;QAClF,iFAAiF;QACjF,uCAAuC;QACvC,MAAM,YAAY,GAAG,MAAM,IAAA,0BAAiB,EAC1C,KAAK,IAAI,EAAE;YACT,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC/B,SAAS,EAAE,MAAM,CAAC,eAAe;gBACjC,sBAAsB,EACpB,gFAAgF;gBAClF,yBAAyB,EAAE;oBACzB,wCAAwC,EAAE,eAAM,CAAC,eAAe,CAAC,IAAI,CAAC;iBACvE;gBACD,cAAc,EAAE,IAAI;gBACpB,iBAAiB,EAAE,sBAAsB;aAC1C,CAAC,CAAC;QACL,CAAC,EACD,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAC1B,CAAC;QAEF,IAAI,YAAY,CAAC,KAAK,EAAE,CAAC;YACvB,aAAa,CAAC,IAAI,CAAC,GAAI,YAAY,CAAC,KAA6B,CAAC,CAAC;QACrE,CAAC;QACD,sBAAsB,GAAG,YAAY,CAAC,gBAAgB,CAAC;IACzD,CAAC,QAAQ,sBAAsB,EAAE;IAEjC,gFAAgF;IAChF,+EAA+E;IAC/E,0CAA0C;IAC1C,MAAM,aAAa,GAA6B,MAAM,OAAO,CAAC,GAAG,CAC/D,aAAa,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAmC,EAAE;QACrE,MAAM,KAAK,GAAG,eAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,QAAQ,GAAe,KAAK,CAAC,KAAK,CAAC;QACvC,IAAI,SAAS,CAAC,YAAY,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;gBACxB,MAAM,IAAI,KAAK,CACb,oCAAoC,SAAS,CAAC,YAAY,uCAAuC;oBAC/F,gFAAgF,CACnF,CAAC;YACJ,CAAC;YACD,QAAQ,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU;YAClC,CAAC,CAAC,MAAM,MAAM,CAAC,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC;YAC9C,CAAC,CAAC,QAAQ,CAAC;QACb,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QACpE,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC/C,CAAC,CAAC,CACH,CAAC;IAEF,qEAAqE;IACrE,MAAM,eAAe,GAAG,MAAM,IAAA,kCAA0B,EACtD,IAAI,EACJ,cAAc,EACd,MAAM,CAAC,KAAK,EACZ,MAAM,CAAC,UAAU,EACjB,MAAM,CAAC,WAAW,CACnB,CAAC;IAEF,kCAAkC;IAClC,OAAO;QACL,GAAG,eAAe;QAClB,aAAa;KACd,CAAC;AACJ,CAAC,CAAC;AArLW,QAAA,cAAc,kBAqLzB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"put-writes.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/put-writes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"put-writes.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/put-writes.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAKjD;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAU,QAAQ,qBAAqB,KAAG,OAAO,CAAC,IAAI,CAqFjF,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.putWritesAction = void 0;
|
|
4
|
+
const langgraph_checkpoint_1 = require("@langchain/langgraph-checkpoint");
|
|
4
5
|
const shared_1 = require("../../shared");
|
|
5
6
|
const utils_1 = require("../utils");
|
|
6
7
|
const validate_configurable_1 = require("./validate-configurable");
|
|
@@ -20,12 +21,26 @@ const putWritesAction = async (params) => {
|
|
|
20
21
|
(0, utils_1.validateWritesCount)(params.writes.length);
|
|
21
22
|
(0, utils_1.validateTaskId)(params.taskId);
|
|
22
23
|
(0, utils_1.validateTTLDays)(params.ttlDays);
|
|
23
|
-
|
|
24
|
+
// Compute TTL once (ttlSeconds takes precedence) — not per message.
|
|
25
|
+
let ttl;
|
|
26
|
+
if (params.ttlSeconds !== undefined) {
|
|
27
|
+
ttl = (0, shared_1.calculateTTLTimestampFromSeconds)(params.ttlSeconds);
|
|
28
|
+
}
|
|
29
|
+
else if (params.ttlDays !== undefined) {
|
|
30
|
+
ttl = (0, shared_1.calculateTTLTimestamp)(params.ttlDays);
|
|
31
|
+
}
|
|
32
|
+
const writeItems = await Promise.all(params.writes.map(async (write, positionalIdx) => {
|
|
24
33
|
const [type, rawValue] = await params.serde.dumpsTyped(write[1]);
|
|
25
34
|
// Compress after serialization if compressor is provided
|
|
26
35
|
const serializedValue = params.compressor
|
|
27
36
|
? await params.compressor.compress(rawValue)
|
|
28
37
|
: rawValue;
|
|
38
|
+
// Special channels (__error__, __scheduled__, __interrupt__, __resume__) are
|
|
39
|
+
// stored at the stable negative idx from WRITES_IDX_MAP so repeated writes
|
|
40
|
+
// for the same taskId overwrite the correct dedicated slot. Regular writes
|
|
41
|
+
// use their positional index. Matches memory.js:178 from langgraph-checkpoint.
|
|
42
|
+
const channel = write[0];
|
|
43
|
+
const idx = langgraph_checkpoint_1.WRITES_IDX_MAP[channel] ?? positionalIdx;
|
|
29
44
|
// S3 offloading for large write values
|
|
30
45
|
let s3ValueKey;
|
|
31
46
|
let storedValue = serializedValue;
|
|
@@ -39,30 +54,32 @@ const putWritesAction = async (params) => {
|
|
|
39
54
|
checkpoint_id,
|
|
40
55
|
task_id: params.taskId,
|
|
41
56
|
idx,
|
|
42
|
-
channel
|
|
57
|
+
channel,
|
|
43
58
|
type,
|
|
44
59
|
value: storedValue,
|
|
45
60
|
});
|
|
46
61
|
const dynamoItem = item.toDynamoDBItem();
|
|
47
|
-
|
|
48
|
-
if (s3ValueKey) {
|
|
62
|
+
if (s3ValueKey)
|
|
49
63
|
dynamoItem.s3_value_key = s3ValueKey;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
dynamoItem.ttl = (0, shared_1.calculateTTLTimestampFromSeconds)(params.ttlSeconds);
|
|
54
|
-
}
|
|
55
|
-
else if (params.ttlDays !== undefined) {
|
|
56
|
-
dynamoItem.ttl = (0, shared_1.calculateTTLTimestamp)(params.ttlDays);
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
PutRequest: {
|
|
60
|
-
Item: dynamoItem,
|
|
61
|
-
},
|
|
62
|
-
};
|
|
64
|
+
if (ttl !== undefined)
|
|
65
|
+
dynamoItem.ttl = ttl;
|
|
66
|
+
return { PutRequest: { Item: dynamoItem } };
|
|
63
67
|
}));
|
|
64
|
-
// Batch writes using shared utility with retry logic
|
|
65
|
-
|
|
68
|
+
// Batch writes using shared utility with retry logic.
|
|
69
|
+
// On failure, best-effort-clean any S3 objects we uploaded — errors during cleanup
|
|
70
|
+
// are only logged since the lifecycle rule is the ultimate backstop.
|
|
71
|
+
try {
|
|
72
|
+
await (0, shared_1.batchWriteAllWithRetry)(params.client, params.writesTableName, writeItems, {
|
|
73
|
+
signal: params.signal,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
if (params.s3Offloader) {
|
|
78
|
+
const keys = writeItems.map((w) => w.PutRequest.Item.s3_value_key);
|
|
79
|
+
await (0, shared_1.cleanUpS3Orphans)(params.s3Offloader, keys, 'putWrites failure');
|
|
80
|
+
}
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
66
83
|
};
|
|
67
84
|
exports.putWritesAction = putWritesAction;
|
|
68
85
|
//# sourceMappingURL=put-writes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"put-writes.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/put-writes.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"put-writes.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/put-writes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"put.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/put.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"put.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/put.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQhE,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,UAAU,CAAC;AAIlB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,GAAU,QAAQ,eAAe,KAAG,OAAO,CAAC,cAAc,CAmL/E,CAAC"}
|