@farukada/aws-langgraph-dynamodb-ts 0.1.0 → 0.2.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 +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 +36 -25
|
@@ -85,25 +85,82 @@ const putAction = async (params) => {
|
|
|
85
85
|
if (ttl !== undefined) {
|
|
86
86
|
payloadItem.ttl = ttl;
|
|
87
87
|
}
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
88
|
+
// Optimistic-concurrency guard on the metadata Put.
|
|
89
|
+
//
|
|
90
|
+
// Two concurrent workers racing on the same (thread_id, checkpoint_id) with
|
|
91
|
+
// *different* parent/type (= divergent lineage) is a bug we want to surface,
|
|
92
|
+
// not silently resolve last-writer-wins. Two writers agreeing on parent+type
|
|
93
|
+
// (= legitimate idempotent retry after a transient error) must still succeed.
|
|
94
|
+
//
|
|
95
|
+
// attribute_not_exists(checkpoint_id) -- first write ever: OK
|
|
96
|
+
// OR (#type = :expected_type AND ( -- OR same content as prior:
|
|
97
|
+
// attribute_not_exists(parent_checkpoint_id) -- initial checkpoint
|
|
98
|
+
// OR parent_checkpoint_id = :expected_parent -- with named parent
|
|
99
|
+
// ))
|
|
100
|
+
//
|
|
101
|
+
// The payload Put is left unconditional: payloads are keyed by PAYLOAD#<id>
|
|
102
|
+
// and contain content-for-content equal data for a given checkpoint, so an
|
|
103
|
+
// overwrite on retry is always safe.
|
|
104
|
+
// Normalize `''` to `undefined` so an empty-string parent and an unset parent
|
|
105
|
+
// are treated as the same "no parent" state. This keeps the ConditionExpression
|
|
106
|
+
// stable across idempotent retries that might represent "initial checkpoint"
|
|
107
|
+
// either way (e.g. a migration or a caller that stringifies nulls).
|
|
108
|
+
const rawExpectedParent = params.config.configurable?.checkpoint_id;
|
|
109
|
+
const expectedParent = typeof rawExpectedParent === 'string' && rawExpectedParent.length > 0
|
|
110
|
+
? rawExpectedParent
|
|
111
|
+
: undefined;
|
|
112
|
+
const metadataExpressionAttributeNames = { '#type': 'type' };
|
|
113
|
+
const metadataExpressionAttributeValues = {
|
|
114
|
+
':expected_type': type1,
|
|
115
|
+
};
|
|
116
|
+
const parentClause = expectedParent !== undefined
|
|
117
|
+
? 'parent_checkpoint_id = :expected_parent'
|
|
118
|
+
: 'attribute_not_exists(parent_checkpoint_id)';
|
|
119
|
+
if (expectedParent !== undefined) {
|
|
120
|
+
metadataExpressionAttributeValues[':expected_parent'] = expectedParent;
|
|
121
|
+
}
|
|
122
|
+
const metadataConditionExpression = 'attribute_not_exists(checkpoint_id) ' + `OR (#type = :expected_type AND (${parentClause}))`;
|
|
123
|
+
// Atomic write: both items must succeed or both fail.
|
|
124
|
+
// On failure, best-effort-clean any S3 objects we uploaded so they don't linger
|
|
125
|
+
// until the lifecycle rule sweeps them. Errors during cleanup are only logged.
|
|
126
|
+
try {
|
|
127
|
+
await (0, shared_1.withDynamoDBRetry)(async () => {
|
|
128
|
+
await params.client.transactWrite({
|
|
129
|
+
TransactItems: [
|
|
130
|
+
{
|
|
131
|
+
Put: {
|
|
132
|
+
TableName: params.checkpointsTableName,
|
|
133
|
+
Item: metadataItem,
|
|
134
|
+
ConditionExpression: metadataConditionExpression,
|
|
135
|
+
ExpressionAttributeNames: metadataExpressionAttributeNames,
|
|
136
|
+
ExpressionAttributeValues: metadataExpressionAttributeValues,
|
|
137
|
+
},
|
|
102
138
|
},
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
139
|
+
{ Put: { TableName: params.checkpointsTableName, Item: payloadItem } },
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
}, { signal: params.signal });
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
// Skip S3 orphan cleanup on the optimistic-lock conflict path. S3 keys are
|
|
146
|
+
// derived deterministically from (thread_id, checkpoint_id), so a divergent
|
|
147
|
+
// put() on the same checkpoint_id uploads to the *same* keys as the
|
|
148
|
+
// canonical write already occupies — cleaning them up after a
|
|
149
|
+
// ConditionalCheckFailed would delete live data still referenced by the
|
|
150
|
+
// winning transaction. The S3 lifecycle rule is the safe backstop for the
|
|
151
|
+
// rare case where an overwrite did occur; data integrity beats a few stale
|
|
152
|
+
// bytes in S3.
|
|
153
|
+
const isConditionalFailure = err &&
|
|
154
|
+
typeof err === 'object' &&
|
|
155
|
+
('name' in err
|
|
156
|
+
? err.name === 'ConditionalCheckFailedException' ||
|
|
157
|
+
err.name === 'TransactionCanceledException'
|
|
158
|
+
: false);
|
|
159
|
+
if (params.s3Offloader && !isConditionalFailure) {
|
|
160
|
+
await (0, shared_1.cleanUpS3Orphans)(params.s3Offloader, [s3CheckpointKey, s3MetadataKey], 'put failure');
|
|
161
|
+
}
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
107
164
|
return {
|
|
108
165
|
configurable: {
|
|
109
166
|
thread_id: metadataItem.thread_id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"put.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/put.ts"],"names":[],"mappings":";;;AAEA,
|
|
1
|
+
{"version":3,"file":"put.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/put.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,7 +12,9 @@ export declare class Writer {
|
|
|
12
12
|
readonly checkpoint_id: string;
|
|
13
13
|
/** Unique identifier for the task that created this write */
|
|
14
14
|
readonly task_id: string;
|
|
15
|
-
/** Index of the write within the task
|
|
15
|
+
/** Index of the write within the task. Negative values correspond to
|
|
16
|
+
* `WRITES_IDX_MAP` special-channel slots (__error__=-1, __scheduled__=-2,
|
|
17
|
+
* __interrupt__=-3, __resume__=-4); non-negative values are positional. */
|
|
16
18
|
readonly idx: number;
|
|
17
19
|
/** Channel name for the write */
|
|
18
20
|
readonly channel: string;
|
|
@@ -42,9 +44,9 @@ export declare class Writer {
|
|
|
42
44
|
checkpoint_ns: string;
|
|
43
45
|
}): string;
|
|
44
46
|
/**
|
|
45
|
-
*
|
|
46
|
-
* Note:
|
|
47
|
+
* Separator used for composite keys.
|
|
48
|
+
* Note: input validation ensures this separator cannot appear in any component.
|
|
47
49
|
*/
|
|
48
|
-
static
|
|
50
|
+
static readonly SEPARATOR = ":::";
|
|
49
51
|
}
|
|
50
52
|
//# sourceMappingURL=writer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAS1D;;;GAGG;AACH,qBAAa,MAAM;IACjB,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,2CAA2C;IAC3C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB
|
|
1
|
+
{"version":3,"file":"writer.d.ts","sourceRoot":"","sources":["../../../src/checkpointer/actions/writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAS1D;;;GAGG;AACH,qBAAa,MAAM;IACjB,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,2CAA2C;IAC3C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,6DAA6D;IAC7D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;gFAE4E;IAC5E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;gBAEf,EACV,SAAS,EACT,aAAa,EACb,aAAa,EACb,OAAO,EACP,GAAG,EACH,OAAO,EACP,IAAI,EACJ,KAAK,GACN,EAAE,WAAW;IAsBd,cAAc,IAAI,iBAAiB;IAcnC;;;;;;OAMG;IACH,MAAM,CAAC,gBAAgB,CAAC,EACtB,qCAAqC,EACrC,WAAW,EACX,OAAO,EACP,IAAI,EACJ,KAAK,GACN,EAAE,iBAAiB,GAAG,MAAM;IA8B7B;;;;;OAKG;IACH,MAAM,CAAC,eAAe,CAAC,EACrB,SAAS,EACT,aAAa,EACb,aAAa,GACd,EAAE;QACD,SAAS,EAAE,MAAM,CAAC;QAClB,aAAa,EAAE,MAAM,CAAC;QACtB,aAAa,EAAE,MAAM,CAAC;KACvB,GAAG,MAAM;IAIV;;;OAGG;IACH,MAAM,CAAC,QAAQ,CAAC,SAAS,SAAS;CACnC"}
|
|
@@ -15,7 +15,9 @@ class Writer {
|
|
|
15
15
|
checkpoint_id;
|
|
16
16
|
/** Unique identifier for the task that created this write */
|
|
17
17
|
task_id;
|
|
18
|
-
/** Index of the write within the task
|
|
18
|
+
/** Index of the write within the task. Negative values correspond to
|
|
19
|
+
* `WRITES_IDX_MAP` special-channel slots (__error__=-1, __scheduled__=-2,
|
|
20
|
+
* __interrupt__=-3, __resume__=-4); non-negative values are positional. */
|
|
19
21
|
idx;
|
|
20
22
|
/** Channel name for the write */
|
|
21
23
|
channel;
|
|
@@ -30,8 +32,8 @@ class Writer {
|
|
|
30
32
|
(0, utils_1.validateCheckpointId)(checkpoint_id, true);
|
|
31
33
|
(0, utils_1.validateTaskId)(task_id);
|
|
32
34
|
(0, utils_1.validateChannel)(channel);
|
|
33
|
-
if (!Number.isInteger(idx)
|
|
34
|
-
throw new Error('idx must be
|
|
35
|
+
if (!Number.isInteger(idx)) {
|
|
36
|
+
throw new Error('idx must be an integer');
|
|
35
37
|
}
|
|
36
38
|
this.thread_id = thread_id;
|
|
37
39
|
this.checkpoint_ns = checkpoint_ns;
|
|
@@ -49,7 +51,7 @@ class Writer {
|
|
|
49
51
|
checkpoint_id: this.checkpoint_id,
|
|
50
52
|
checkpoint_ns: this.checkpoint_ns,
|
|
51
53
|
}),
|
|
52
|
-
task_id_idx: [this.task_id, this.idx].join(Writer.
|
|
54
|
+
task_id_idx: [this.task_id, this.idx].join(Writer.SEPARATOR),
|
|
53
55
|
channel: this.channel,
|
|
54
56
|
type: this.type,
|
|
55
57
|
value: this.value,
|
|
@@ -63,12 +65,12 @@ class Writer {
|
|
|
63
65
|
* @throws Error if parsing fails or data is invalid
|
|
64
66
|
*/
|
|
65
67
|
static fromDynamoDBItem({ thread_id_checkpoint_id_checkpoint_ns, task_id_idx, channel, type, value, }) {
|
|
66
|
-
const parts = thread_id_checkpoint_id_checkpoint_ns.split(this.
|
|
68
|
+
const parts = thread_id_checkpoint_id_checkpoint_ns.split(this.SEPARATOR);
|
|
67
69
|
if (parts.length !== 3) {
|
|
68
70
|
throw new Error(`Invalid partition key format: expected 3 parts, got ${parts.length}`);
|
|
69
71
|
}
|
|
70
72
|
const [thread_id, checkpoint_id, checkpoint_ns] = parts;
|
|
71
|
-
const idxParts = task_id_idx.split(this.
|
|
73
|
+
const idxParts = task_id_idx.split(this.SEPARATOR);
|
|
72
74
|
if (idxParts.length !== 2) {
|
|
73
75
|
throw new Error(`Invalid sort key format: expected 2 parts, got ${idxParts.length}`);
|
|
74
76
|
}
|
|
@@ -95,15 +97,13 @@ class Writer {
|
|
|
95
97
|
* @returns Composite partition key
|
|
96
98
|
*/
|
|
97
99
|
static getPartitionKey({ thread_id, checkpoint_id, checkpoint_ns, }) {
|
|
98
|
-
return [thread_id, checkpoint_id, checkpoint_ns].join(this.
|
|
100
|
+
return [thread_id, checkpoint_id, checkpoint_ns].join(this.SEPARATOR);
|
|
99
101
|
}
|
|
100
102
|
/**
|
|
101
|
-
*
|
|
102
|
-
* Note:
|
|
103
|
+
* Separator used for composite keys.
|
|
104
|
+
* Note: input validation ensures this separator cannot appear in any component.
|
|
103
105
|
*/
|
|
104
|
-
static
|
|
105
|
-
return ':::';
|
|
106
|
-
}
|
|
106
|
+
static SEPARATOR = ':::';
|
|
107
107
|
}
|
|
108
108
|
exports.Writer = Writer;
|
|
109
109
|
//# sourceMappingURL=writer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/writer.ts"],"names":[],"mappings":";;;AACA,oCAMkB;AAElB;;;GAGG;AACH,MAAa,MAAM;IACjB,oDAAoD;IAC3C,SAAS,CAAS;IAC3B,uDAAuD;IAC9C,aAAa,CAAS;IAC/B,2CAA2C;IAClC,aAAa,CAAS;IAC/B,6DAA6D;IACpD,OAAO,CAAS;IACzB
|
|
1
|
+
{"version":3,"file":"writer.js","sourceRoot":"","sources":["../../../src/checkpointer/actions/writer.ts"],"names":[],"mappings":";;;AACA,oCAMkB;AAElB;;;GAGG;AACH,MAAa,MAAM;IACjB,oDAAoD;IAC3C,SAAS,CAAS;IAC3B,uDAAuD;IAC9C,aAAa,CAAS;IAC/B,2CAA2C;IAClC,aAAa,CAAS;IAC/B,6DAA6D;IACpD,OAAO,CAAS;IACzB;;gFAE4E;IACnE,GAAG,CAAS;IACrB,iCAAiC;IACxB,OAAO,CAAS;IACzB,0CAA0C;IACjC,IAAI,CAAS;IACtB,sCAAsC;IAC7B,KAAK,CAAa;IAE3B,YAAY,EACV,SAAS,EACT,aAAa,EACb,aAAa,EACb,OAAO,EACP,GAAG,EACH,OAAO,EACP,IAAI,EACJ,KAAK,GACO;QACZ,sBAAsB;QACtB,IAAA,wBAAgB,EAAC,SAAS,CAAC,CAAC;QAC5B,IAAA,4BAAoB,EAAC,aAAa,CAAC,CAAC;QACpC,IAAA,4BAAoB,EAAC,aAAa,EAAE,IAAI,CAAC,CAAC;QAC1C,IAAA,sBAAc,EAAC,OAAO,CAAC,CAAC;QACxB,IAAA,uBAAe,EAAC,OAAO,CAAC,CAAC;QAEzB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,cAAc;QACZ,OAAO;YACL,qCAAqC,EAAE,MAAM,CAAC,eAAe,CAAC;gBAC5D,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,aAAa,EAAE,IAAI,CAAC,aAAa;aAClC,CAAC;YACF,WAAW,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC;YAC5D,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC;IACJ,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,gBAAgB,CAAC,EACtB,qCAAqC,EACrC,WAAW,EACX,OAAO,EACP,IAAI,EACJ,KAAK,GACa;QAClB,MAAM,KAAK,GAAG,qCAAqC,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC1E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,uDAAuD,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,CAAC,SAAS,EAAE,aAAa,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC;QAExD,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACnD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,kDAAkD,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAC;QAEnC,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,EAAE,CAAC,CAAC;QAClD,CAAC;QAED,OAAO,IAAI,MAAM,CAAC;YAChB,SAAS;YACT,aAAa;YACb,aAAa;YACb,OAAO;YACP,GAAG;YACH,OAAO;YACP,IAAI;YACJ,KAAK;SACN,CAAC,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,eAAe,CAAC,EACrB,SAAS,EACT,aAAa,EACb,aAAa,GAKd;QACC,OAAO,CAAC,SAAS,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACxE,CAAC;IAED;;;OAGG;IACH,MAAM,CAAU,SAAS,GAAG,KAAK,CAAC;;AAlIpC,wBAmIC"}
|
|
@@ -49,13 +49,18 @@ export declare class DynamoDBSaver extends BaseCheckpointSaver {
|
|
|
49
49
|
* Delete a thread and all its checkpoints and writes
|
|
50
50
|
*
|
|
51
51
|
* @param threadId - The thread ID to delete
|
|
52
|
+
* @param options.signal - Optional AbortSignal for cancelling in-flight retries
|
|
52
53
|
* @throws Error if validation fails or operation fails
|
|
53
54
|
*/
|
|
54
|
-
deleteThread(threadId: string
|
|
55
|
+
deleteThread(threadId: string, options?: {
|
|
56
|
+
signal?: AbortSignal;
|
|
57
|
+
}): Promise<void>;
|
|
55
58
|
/**
|
|
56
59
|
* Get a checkpoint tuple from DynamoDB
|
|
57
60
|
*
|
|
58
|
-
* @param config - Runnable configuration containing thread_id and optional checkpoint_id
|
|
61
|
+
* @param config - Runnable configuration containing thread_id and optional checkpoint_id.
|
|
62
|
+
* `config.signal` is honored as an AbortSignal: in-flight retries short-circuit
|
|
63
|
+
* and the returned promise rejects with the signal's abort reason.
|
|
59
64
|
* @returns CheckpointTuple if found, undefined otherwise
|
|
60
65
|
* @throws Error if validation fails or operation fails
|
|
61
66
|
*/
|
|
@@ -63,7 +68,8 @@ export declare class DynamoDBSaver extends BaseCheckpointSaver {
|
|
|
63
68
|
/**
|
|
64
69
|
* Save a checkpoint to DynamoDB
|
|
65
70
|
*
|
|
66
|
-
* @param config - Runnable configuration
|
|
71
|
+
* @param config - Runnable configuration. `config.signal` is honored as an
|
|
72
|
+
* AbortSignal and short-circuits retry backoff.
|
|
67
73
|
* @param checkpoint - Checkpoint to save
|
|
68
74
|
* @param metadata - Checkpoint metadata
|
|
69
75
|
* @param newVersions - Channel versions (not used in DynamoDB implementation)
|
|
@@ -74,7 +80,8 @@ export declare class DynamoDBSaver extends BaseCheckpointSaver {
|
|
|
74
80
|
/**
|
|
75
81
|
* Save pending writes to DynamoDB
|
|
76
82
|
*
|
|
77
|
-
* @param config - Runnable configuration
|
|
83
|
+
* @param config - Runnable configuration. `config.signal` is honored as an
|
|
84
|
+
* AbortSignal and short-circuits retry backoff.
|
|
78
85
|
* @param writes - Array of pending writes to save
|
|
79
86
|
* @param taskId - Task ID for the writes
|
|
80
87
|
* @throws Error if validation fails or operation fails
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checkpointer/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/checkpointer/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,iCAAiC,CAAC;AAgBzC,OAAO,KAAK,EAAyC,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAS3F;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,mBAAmB;IACpD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;IAC9C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IAErC;;;;;;;;;;;;;OAaG;gBACS,OAAO,EAAE,oBAAoB;IAgCzC;;;;OAIG;IACH,OAAO,IAAI,IAAI;IAOf;;;;;;OAMG;IACG,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAW3F;;;;;;;;OAQG;IACG,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC;IAa5E;;;;;;;;;;OAUG;IACG,GAAG,CACP,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,kBAAkB,EAE5B,WAAW,EAAE,eAAe,GAC3B,OAAO,CAAC,cAAc,CAAC;IAgB1B;;;;;;;;OAQG;IACG,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB9F;;;;;;;OAOG;IACI,IAAI,CACT,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,qBAAqB,GAAG,SAAS,GACzC,cAAc,CAAC,eAAe,CAAC;IAiIlC;;;;;;;OAOG;YACW,4BAA4B;IAqG1C;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;CAStB"}
|
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.DynamoDBSaver = void 0;
|
|
8
|
-
const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
|
|
9
|
-
const lib_dynamodb_1 = require("@aws-sdk/lib-dynamodb");
|
|
10
8
|
const langgraph_checkpoint_1 = require("@langchain/langgraph-checkpoint");
|
|
11
9
|
const shared_1 = require("../shared");
|
|
12
10
|
const actions_1 = require("./actions");
|
|
@@ -51,16 +49,11 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
51
49
|
this.writesTableName = options.writesTableName;
|
|
52
50
|
this.ttlDays = options.ttlDays;
|
|
53
51
|
this.ttlSeconds = options.ttlSeconds;
|
|
54
|
-
|
|
55
|
-
this.
|
|
56
|
-
this.
|
|
57
|
-
this.ownsClient
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
this.ddbClient = new client_dynamodb_1.DynamoDBClient(options.clientConfig || {});
|
|
61
|
-
this.client = lib_dynamodb_1.DynamoDBDocument.from(this.ddbClient);
|
|
62
|
-
this.ownsClient = true;
|
|
63
|
-
}
|
|
52
|
+
({
|
|
53
|
+
ddbClient: this.ddbClient,
|
|
54
|
+
client: this.client,
|
|
55
|
+
ownsClient: this.ownsClient,
|
|
56
|
+
} = (0, shared_1.resolveDynamoDBClient)(options));
|
|
64
57
|
if (options.compression?.enabled) {
|
|
65
58
|
this.compressor = new shared_1.Compressor(options.compression);
|
|
66
59
|
}
|
|
@@ -93,21 +86,25 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
93
86
|
* Delete a thread and all its checkpoints and writes
|
|
94
87
|
*
|
|
95
88
|
* @param threadId - The thread ID to delete
|
|
89
|
+
* @param options.signal - Optional AbortSignal for cancelling in-flight retries
|
|
96
90
|
* @throws Error if validation fails or operation fails
|
|
97
91
|
*/
|
|
98
|
-
async deleteThread(threadId) {
|
|
92
|
+
async deleteThread(threadId, options = {}) {
|
|
99
93
|
return await (0, actions_1.deleteThreadAction)({
|
|
100
94
|
client: this.client,
|
|
101
95
|
checkpointsTableName: this.checkpointsTableName,
|
|
102
96
|
writesTableName: this.writesTableName,
|
|
103
97
|
threadId,
|
|
104
98
|
s3Offloader: this.s3Offloader,
|
|
99
|
+
signal: options.signal,
|
|
105
100
|
});
|
|
106
101
|
}
|
|
107
102
|
/**
|
|
108
103
|
* Get a checkpoint tuple from DynamoDB
|
|
109
104
|
*
|
|
110
|
-
* @param config - Runnable configuration containing thread_id and optional checkpoint_id
|
|
105
|
+
* @param config - Runnable configuration containing thread_id and optional checkpoint_id.
|
|
106
|
+
* `config.signal` is honored as an AbortSignal: in-flight retries short-circuit
|
|
107
|
+
* and the returned promise rejects with the signal's abort reason.
|
|
111
108
|
* @returns CheckpointTuple if found, undefined otherwise
|
|
112
109
|
* @throws Error if validation fails or operation fails
|
|
113
110
|
*/
|
|
@@ -120,12 +117,14 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
120
117
|
config,
|
|
121
118
|
compressor: this.compressor,
|
|
122
119
|
s3Offloader: this.s3Offloader,
|
|
120
|
+
signal: config.signal,
|
|
123
121
|
});
|
|
124
122
|
}
|
|
125
123
|
/**
|
|
126
124
|
* Save a checkpoint to DynamoDB
|
|
127
125
|
*
|
|
128
|
-
* @param config - Runnable configuration
|
|
126
|
+
* @param config - Runnable configuration. `config.signal` is honored as an
|
|
127
|
+
* AbortSignal and short-circuits retry backoff.
|
|
129
128
|
* @param checkpoint - Checkpoint to save
|
|
130
129
|
* @param metadata - Checkpoint metadata
|
|
131
130
|
* @param newVersions - Channel versions (not used in DynamoDB implementation)
|
|
@@ -146,12 +145,14 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
146
145
|
ttlSeconds: this.ttlSeconds,
|
|
147
146
|
compressor: this.compressor,
|
|
148
147
|
s3Offloader: this.s3Offloader,
|
|
148
|
+
signal: config.signal,
|
|
149
149
|
});
|
|
150
150
|
}
|
|
151
151
|
/**
|
|
152
152
|
* Save pending writes to DynamoDB
|
|
153
153
|
*
|
|
154
|
-
* @param config - Runnable configuration
|
|
154
|
+
* @param config - Runnable configuration. `config.signal` is honored as an
|
|
155
|
+
* AbortSignal and short-circuits retry backoff.
|
|
155
156
|
* @param writes - Array of pending writes to save
|
|
156
157
|
* @param taskId - Task ID for the writes
|
|
157
158
|
* @throws Error if validation fails or operation fails
|
|
@@ -168,6 +169,7 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
168
169
|
ttlSeconds: this.ttlSeconds,
|
|
169
170
|
compressor: this.compressor,
|
|
170
171
|
s3Offloader: this.s3Offloader,
|
|
172
|
+
signal: config.signal,
|
|
171
173
|
});
|
|
172
174
|
}
|
|
173
175
|
/**
|
|
@@ -182,6 +184,10 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
182
184
|
const { limit, before, filter } = options ?? {};
|
|
183
185
|
const thread_id = config.configurable?.thread_id;
|
|
184
186
|
const checkpoint_ns = config.configurable?.checkpoint_ns;
|
|
187
|
+
// `config.signal` is LangChain's idiomatic AbortSignal field. We propagate
|
|
188
|
+
// it into each retry cycle AND check it before yielding, so a consumer
|
|
189
|
+
// calling `controller.abort()` during iteration stops producing items.
|
|
190
|
+
const signal = config.signal;
|
|
185
191
|
// Validate thread_id
|
|
186
192
|
if (typeof thread_id !== 'string') {
|
|
187
193
|
throw new Error('thread_id must be a string');
|
|
@@ -189,74 +195,84 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
189
195
|
(0, utils_1.validateThreadId)(thread_id);
|
|
190
196
|
// Validate limit if provided
|
|
191
197
|
(0, utils_1.validateListLimit)(limit);
|
|
198
|
+
// Validate `before` checkpoint_id — LangGraph passes this back verbatim from
|
|
199
|
+
// a previous CheckpointTuple.config, but user callers can construct it
|
|
200
|
+
// manually. Without validation a malformed before-id could slip past the
|
|
201
|
+
// KeyCondition bound (payload items would get returned under `list({before})`).
|
|
202
|
+
const beforeCheckpointId = before?.configurable?.checkpoint_id;
|
|
203
|
+
if (beforeCheckpointId !== undefined) {
|
|
204
|
+
if (typeof beforeCheckpointId !== 'string') {
|
|
205
|
+
throw new Error('before.configurable.checkpoint_id must be a string');
|
|
206
|
+
}
|
|
207
|
+
(0, utils_1.validateCheckpointId)(beforeCheckpointId, false);
|
|
208
|
+
}
|
|
192
209
|
const expressionAttributeValues = {
|
|
193
210
|
':thread_id': thread_id,
|
|
194
|
-
':payload_prefix': types_1.PAYLOAD_SK_PREFIX,
|
|
195
211
|
...(checkpoint_ns !== undefined && { ':checkpoint_ns': checkpoint_ns }),
|
|
196
212
|
};
|
|
197
|
-
// Filter
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
213
|
+
// Filter to metadata items only via the non-key `type` attribute (payload
|
|
214
|
+
// items don't carry `type`). We intentionally avoid using checkpoint_id in
|
|
215
|
+
// the KeyCondition to exclude payload items — any lex-bound approach
|
|
216
|
+
// silently drops metadata whose IDs sort above `PAYLOAD#`, which includes
|
|
217
|
+
// the very common case of lowercase-letter-prefixed IDs.
|
|
218
|
+
const filterParts = ['attribute_exists(#type)'];
|
|
219
|
+
if (checkpoint_ns !== undefined)
|
|
220
|
+
filterParts.push('checkpoint_ns = :checkpoint_ns');
|
|
221
|
+
const filterExpression = filterParts.join(' AND ');
|
|
222
|
+
// KeyCondition gates only by thread (and optional `before` sort-key bound).
|
|
223
|
+
let keyConditionExpression = 'thread_id = :thread_id';
|
|
203
224
|
if (before?.configurable?.checkpoint_id) {
|
|
204
225
|
keyConditionExpression = 'thread_id = :thread_id AND checkpoint_id < :before_checkpoint_id';
|
|
205
226
|
expressionAttributeValues[':before_checkpoint_id'] = before.configurable.checkpoint_id;
|
|
206
|
-
// No need for :payload_prefix — before_checkpoint_id is always a UUID which is < PAYLOAD#
|
|
207
227
|
}
|
|
208
|
-
// When
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
ExpressionAttributeValues: expressionAttributeValues,
|
|
217
|
-
FilterExpression: filterExpression,
|
|
218
|
-
Limit: queryLimit,
|
|
219
|
-
ScanIndexForward: false, // Descending order
|
|
220
|
-
});
|
|
221
|
-
});
|
|
228
|
+
// When any server-side OR client-side filter reduces the returned set, we
|
|
229
|
+
// drop the DynamoDB Limit: Limit is applied BEFORE FilterExpression, so a
|
|
230
|
+
// small limit with heavy filtering truncates the result prematurely. The
|
|
231
|
+
// metadata filter (from CheckpointListOptions.filter) and the checkpoint_ns
|
|
232
|
+
// filter both qualify.
|
|
233
|
+
const hasMetadataFilter = filter && Object.keys(filter).length > 0;
|
|
234
|
+
const hasNsFilter = checkpoint_ns !== undefined;
|
|
235
|
+
const hasAnyFilter = hasMetadataFilter || hasNsFilter;
|
|
222
236
|
let yieldedCount = 0;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
yieldedCount++;
|
|
235
|
-
if (limit && yieldedCount >= limit)
|
|
236
|
-
return;
|
|
237
|
+
let iterationCount = 0;
|
|
238
|
+
let lastEvaluatedKey;
|
|
239
|
+
do {
|
|
240
|
+
// Safety net against pathological filter queries: if a thread has millions
|
|
241
|
+
// of checkpoints and the filter matches almost none, the scan could loop
|
|
242
|
+
// for minutes burning RCU. Abort with a clear error rather than hanging.
|
|
243
|
+
iterationCount++;
|
|
244
|
+
if (iterationCount > shared_1.MAX_LOOP_ITERATIONS) {
|
|
245
|
+
throw new Error(`list() exceeded ${shared_1.MAX_LOOP_ITERATIONS} DynamoDB pages without reaching the requested limit. ` +
|
|
246
|
+
`This usually indicates a filter that matches very few items in a large thread — ` +
|
|
247
|
+
`narrow the filter, add a more selective namespace, or redesign with a GSI.`);
|
|
237
248
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
249
|
+
const pageLimit = hasAnyFilter
|
|
250
|
+
? undefined
|
|
251
|
+
: limit
|
|
252
|
+
? Math.max(1, limit - yieldedCount)
|
|
253
|
+
: undefined;
|
|
254
|
+
signal?.throwIfAborted();
|
|
255
|
+
const result = await (0, shared_1.withDynamoDBRetry)(async () => {
|
|
243
256
|
return await this.client.query({
|
|
244
257
|
TableName: this.checkpointsTableName,
|
|
245
258
|
KeyConditionExpression: keyConditionExpression,
|
|
246
259
|
ExpressionAttributeValues: expressionAttributeValues,
|
|
260
|
+
ExpressionAttributeNames: { '#type': 'type' },
|
|
247
261
|
FilterExpression: filterExpression,
|
|
248
|
-
Limit:
|
|
249
|
-
ScanIndexForward: false,
|
|
262
|
+
Limit: pageLimit,
|
|
263
|
+
ScanIndexForward: false, // Descending order
|
|
250
264
|
ExclusiveStartKey: lastEvaluatedKey,
|
|
251
265
|
});
|
|
252
|
-
});
|
|
253
|
-
if (
|
|
254
|
-
const items =
|
|
255
|
-
const payloadMap = await this.fetchCheckpointPayloadsBatch(items);
|
|
266
|
+
}, { signal });
|
|
267
|
+
if (result.Items) {
|
|
268
|
+
const items = result.Items;
|
|
269
|
+
const payloadMap = await this.fetchCheckpointPayloadsBatch(items, signal);
|
|
256
270
|
for (const item of items) {
|
|
271
|
+
signal?.throwIfAborted();
|
|
257
272
|
const checkpointData = payloadMap.get(item.checkpoint_id) ?? new Uint8Array(0);
|
|
258
273
|
const tuple = await (0, utils_1.deserializeCheckpointTuple)(item, checkpointData, this.serde, this.compressor, this.s3Offloader);
|
|
259
|
-
|
|
274
|
+
// Apply metadata filter: skip checkpoints that don't match all filter entries.
|
|
275
|
+
if (hasMetadataFilter && !this.matchesFilter(tuple.metadata, filter)) {
|
|
260
276
|
continue;
|
|
261
277
|
}
|
|
262
278
|
yield tuple;
|
|
@@ -265,8 +281,8 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
265
281
|
return;
|
|
266
282
|
}
|
|
267
283
|
}
|
|
268
|
-
lastEvaluatedKey =
|
|
269
|
-
}
|
|
284
|
+
lastEvaluatedKey = result.LastEvaluatedKey;
|
|
285
|
+
} while (lastEvaluatedKey);
|
|
270
286
|
}
|
|
271
287
|
/**
|
|
272
288
|
* Fetch checkpoint payloads for a batch of metadata items.
|
|
@@ -276,7 +292,7 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
276
292
|
* @param items - Checkpoint metadata items from a query result
|
|
277
293
|
* @returns Map from checkpoint_id to raw checkpoint data
|
|
278
294
|
*/
|
|
279
|
-
async fetchCheckpointPayloadsBatch(items) {
|
|
295
|
+
async fetchCheckpointPayloadsBatch(items, signal) {
|
|
280
296
|
const payloadMap = new Map();
|
|
281
297
|
// Partition items into groups
|
|
282
298
|
const splitItems = []; // Need PAYLOAD# fetch
|
|
@@ -306,7 +322,7 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
306
322
|
checkpoint_id: `${types_1.PAYLOAD_SK_PREFIX}${item.checkpoint_id}`,
|
|
307
323
|
}));
|
|
308
324
|
// Retry loop for UnprocessedKeys with exponential backoff
|
|
309
|
-
let retryDelay =
|
|
325
|
+
let retryDelay = shared_1.INITIAL_BACKOFF_DELAY_MS;
|
|
310
326
|
let retryCount = 0;
|
|
311
327
|
while (keys.length > 0) {
|
|
312
328
|
const batchResult = await (0, shared_1.withDynamoDBRetry)(async () => {
|
|
@@ -315,12 +331,19 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
315
331
|
[this.checkpointsTableName]: { Keys: keys },
|
|
316
332
|
},
|
|
317
333
|
});
|
|
318
|
-
});
|
|
334
|
+
}, { signal });
|
|
319
335
|
// Map results — BatchGetItem returns items in arbitrary order
|
|
320
336
|
const responses = batchResult.Responses?.[this.checkpointsTableName];
|
|
321
337
|
if (responses) {
|
|
322
338
|
for (const response of responses) {
|
|
323
|
-
//
|
|
339
|
+
// Sanity-check the sort key before trimming it. Without this, a corrupted
|
|
340
|
+
// or migrated row missing the PAYLOAD# prefix would silently produce a
|
|
341
|
+
// garbage `originalId` and ripple into the "payload not found" path below.
|
|
342
|
+
if (!response.checkpoint_id?.startsWith(types_1.PAYLOAD_SK_PREFIX)) {
|
|
343
|
+
throw new Error(`Unexpected payload item sort key in ${this.checkpointsTableName}: ` +
|
|
344
|
+
`expected to start with "${types_1.PAYLOAD_SK_PREFIX}" but got "${response.checkpoint_id}". ` +
|
|
345
|
+
`This indicates table corruption or a stale migration — investigate before retrying.`);
|
|
346
|
+
}
|
|
324
347
|
const originalId = response.checkpoint_id.substring(types_1.PAYLOAD_SK_PREFIX.length);
|
|
325
348
|
payloadMap.set(originalId, response.checkpoint);
|
|
326
349
|
}
|
|
@@ -333,8 +356,10 @@ class DynamoDBSaver extends langgraph_checkpoint_1.BaseCheckpointSaver {
|
|
|
333
356
|
throw new Error(`Failed to fetch all checkpoint payloads after ${shared_1.MAX_UNPROCESSED_RETRIES} retries. ` +
|
|
334
357
|
`${unprocessed.length} keys remain unprocessed.`);
|
|
335
358
|
}
|
|
336
|
-
|
|
337
|
-
|
|
359
|
+
// Full jitter so list() calls across concurrent workers don't synchronize
|
|
360
|
+
// and re-pressure the partition on every retry cycle.
|
|
361
|
+
await (0, shared_1.sleep)((0, shared_1.fullJitter)(retryDelay), signal);
|
|
362
|
+
retryDelay = (0, shared_1.nextBackoffDelay)(retryDelay);
|
|
338
363
|
keys = unprocessed;
|
|
339
364
|
}
|
|
340
365
|
else {
|