@farukada/aws-langgraph-dynamodb-ts 0.6.0 → 0.8.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 +63 -9
- package/dist/checkpointer/actions/delete-thread.d.ts +3 -4
- package/dist/checkpointer/actions/delete-thread.d.ts.map +1 -1
- package/dist/checkpointer/actions/delete-thread.js +21 -41
- package/dist/checkpointer/actions/delete-thread.js.map +1 -1
- package/dist/checkpointer/actions/list.d.ts.map +1 -1
- package/dist/checkpointer/actions/list.js +31 -4
- package/dist/checkpointer/actions/list.js.map +1 -1
- package/dist/checkpointer/actions/put-writes.d.ts.map +1 -1
- package/dist/checkpointer/actions/put-writes.js +14 -24
- 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 +2 -0
- package/dist/checkpointer/actions/put.js.map +1 -1
- package/dist/checkpointer/internal/item-reader.d.ts +31 -0
- package/dist/checkpointer/internal/item-reader.d.ts.map +1 -1
- package/dist/checkpointer/internal/item-reader.js +49 -1
- package/dist/checkpointer/internal/item-reader.js.map +1 -1
- package/dist/checkpointer/internal/item-writer.d.ts +0 -8
- package/dist/checkpointer/internal/item-writer.d.ts.map +1 -1
- package/dist/checkpointer/internal/item-writer.js +15 -21
- package/dist/checkpointer/internal/item-writer.js.map +1 -1
- package/dist/checkpointer/internal/keys.d.ts +19 -3
- package/dist/checkpointer/internal/keys.d.ts.map +1 -1
- package/dist/checkpointer/internal/keys.js +41 -6
- package/dist/checkpointer/internal/keys.js.map +1 -1
- package/dist/checkpointer/internal/special-write-cleanup.d.ts.map +1 -1
- package/dist/checkpointer/internal/special-write-cleanup.js +6 -12
- package/dist/checkpointer/internal/special-write-cleanup.js.map +1 -1
- package/dist/checkpointer/internal/validation.d.ts +8 -4
- package/dist/checkpointer/internal/validation.d.ts.map +1 -1
- package/dist/checkpointer/internal/validation.js +12 -10
- package/dist/checkpointer/internal/validation.js.map +1 -1
- package/dist/checkpointer/internal/write-guard.d.ts +21 -0
- package/dist/checkpointer/internal/write-guard.d.ts.map +1 -0
- package/dist/checkpointer/internal/write-guard.js +48 -0
- package/dist/checkpointer/internal/write-guard.js.map +1 -0
- package/dist/checkpointer/internal/write-index.d.ts +29 -0
- package/dist/checkpointer/internal/write-index.d.ts.map +1 -0
- package/dist/checkpointer/internal/write-index.js +38 -0
- package/dist/checkpointer/internal/write-index.js.map +1 -0
- package/dist/checkpointer/types.d.ts +2 -0
- package/dist/checkpointer/types.d.ts.map +1 -1
- package/dist/history/actions/add-messages.d.ts.map +1 -1
- package/dist/history/actions/add-messages.js +24 -4
- package/dist/history/actions/add-messages.js.map +1 -1
- package/dist/history/actions/clear.d.ts +4 -6
- package/dist/history/actions/clear.d.ts.map +1 -1
- package/dist/history/actions/clear.js +18 -33
- package/dist/history/actions/clear.js.map +1 -1
- package/dist/history/actions/get-messages.d.ts +2 -1
- package/dist/history/actions/get-messages.d.ts.map +1 -1
- package/dist/history/actions/get-messages.js +28 -2
- package/dist/history/actions/get-messages.js.map +1 -1
- package/dist/history/actions/list-sessions.d.ts +2 -1
- package/dist/history/actions/list-sessions.d.ts.map +1 -1
- package/dist/history/actions/list-sessions.js +21 -2
- package/dist/history/actions/list-sessions.js.map +1 -1
- package/dist/history/actions/reconcile-count.d.ts.map +1 -1
- package/dist/history/actions/reconcile-count.js +2 -2
- package/dist/history/actions/reconcile-count.js.map +1 -1
- package/dist/history/internal/append-saga.d.ts.map +1 -1
- package/dist/history/internal/append-saga.js +2 -78
- package/dist/history/internal/append-saga.js.map +1 -1
- package/dist/history/internal/compensation.d.ts +21 -0
- package/dist/history/internal/compensation.d.ts.map +1 -0
- package/dist/history/internal/compensation.js +84 -0
- package/dist/history/internal/compensation.js.map +1 -0
- package/dist/history/internal/keys.d.ts +10 -2
- package/dist/history/internal/keys.d.ts.map +1 -1
- package/dist/history/internal/keys.js +32 -5
- package/dist/history/internal/keys.js.map +1 -1
- package/dist/history/internal/message-chunker.d.ts.map +1 -1
- package/dist/history/internal/message-chunker.js +13 -5
- package/dist/history/internal/message-chunker.js.map +1 -1
- package/dist/history/internal/session-count.d.ts +21 -0
- package/dist/history/internal/session-count.d.ts.map +1 -1
- package/dist/history/internal/session-count.js +51 -0
- package/dist/history/internal/session-count.js.map +1 -1
- package/dist/history/internal/session-title.d.ts +20 -0
- package/dist/history/internal/session-title.d.ts.map +1 -0
- package/dist/history/internal/session-title.js +44 -0
- package/dist/history/internal/session-title.js.map +1 -0
- package/dist/history/internal/setup.d.ts +2 -1
- package/dist/history/internal/setup.d.ts.map +1 -1
- package/dist/history/internal/setup.js +1 -0
- package/dist/history/internal/setup.js.map +1 -1
- package/dist/history/internal/title-generator.d.ts +8 -2
- package/dist/history/internal/title-generator.d.ts.map +1 -1
- package/dist/history/internal/title-generator.js +12 -3
- package/dist/history/internal/title-generator.js.map +1 -1
- package/dist/history/internal/validation.d.ts +9 -0
- package/dist/history/internal/validation.d.ts.map +1 -0
- package/dist/history/internal/validation.js +16 -0
- package/dist/history/internal/validation.js.map +1 -0
- package/dist/history/types.d.ts +12 -0
- package/dist/history/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/codec/s3/offloader.d.ts +7 -1
- package/dist/shared/codec/s3/offloader.d.ts.map +1 -1
- package/dist/shared/codec/s3/offloader.js +14 -1
- package/dist/shared/codec/s3/offloader.js.map +1 -1
- package/dist/shared/dynamodb/batch-write.d.ts.map +1 -1
- package/dist/shared/dynamodb/batch-write.js +8 -6
- package/dist/shared/dynamodb/batch-write.js.map +1 -1
- package/dist/shared/dynamodb/drain-unprocessed.d.ts +5 -1
- package/dist/shared/dynamodb/drain-unprocessed.d.ts.map +1 -1
- package/dist/shared/dynamodb/drain-unprocessed.js +20 -11
- package/dist/shared/dynamodb/drain-unprocessed.js.map +1 -1
- package/dist/shared/dynamodb/partition-delete.d.ts +33 -0
- package/dist/shared/dynamodb/partition-delete.d.ts.map +1 -0
- package/dist/shared/dynamodb/partition-delete.js +70 -0
- package/dist/shared/dynamodb/partition-delete.js.map +1 -0
- package/dist/shared/dynamodb/retry-classifier.d.ts.map +1 -1
- package/dist/shared/dynamodb/retry-classifier.js +9 -1
- package/dist/shared/dynamodb/retry-classifier.js.map +1 -1
- package/dist/shared/errors/errors.d.ts +5 -2
- package/dist/shared/errors/errors.d.ts.map +1 -1
- package/dist/shared/errors/errors.js +6 -3
- package/dist/shared/errors/errors.js.map +1 -1
- package/dist/shared/logging/redaction-walk.d.ts +23 -0
- package/dist/shared/logging/redaction-walk.d.ts.map +1 -0
- package/dist/shared/logging/redaction-walk.js +92 -0
- package/dist/shared/logging/redaction-walk.js.map +1 -0
- package/dist/shared/logging/redaction.d.ts +22 -10
- package/dist/shared/logging/redaction.d.ts.map +1 -1
- package/dist/shared/logging/redaction.js +24 -36
- package/dist/shared/logging/redaction.js.map +1 -1
- package/dist/shared/logging/secret-patterns.d.ts +41 -0
- package/dist/shared/logging/secret-patterns.d.ts.map +1 -0
- package/dist/shared/logging/secret-patterns.js +74 -0
- package/dist/shared/logging/secret-patterns.js.map +1 -0
- package/dist/shared/validation/primitives.d.ts +9 -1
- package/dist/shared/validation/primitives.d.ts.map +1 -1
- package/dist/shared/validation/primitives.js +13 -0
- package/dist/shared/validation/primitives.js.map +1 -1
- package/dist/store/actions/get.d.ts.map +1 -1
- package/dist/store/actions/get.js +15 -1
- package/dist/store/actions/get.js.map +1 -1
- package/dist/store/actions/list-namespaces.d.ts.map +1 -1
- package/dist/store/actions/list-namespaces.js +1 -0
- package/dist/store/actions/list-namespaces.js.map +1 -1
- package/dist/store/actions/put.d.ts.map +1 -1
- package/dist/store/actions/put.js +20 -2
- package/dist/store/actions/put.js.map +1 -1
- package/dist/store/actions/search.d.ts.map +1 -1
- package/dist/store/actions/search.js +4 -35
- package/dist/store/actions/search.js.map +1 -1
- package/dist/store/internal/backend-search.d.ts +5 -0
- package/dist/store/internal/backend-search.d.ts.map +1 -0
- package/dist/store/internal/backend-search.js +67 -0
- package/dist/store/internal/backend-search.js.map +1 -0
- package/dist/store/internal/filter.d.ts.map +1 -1
- package/dist/store/internal/filter.js +26 -5
- package/dist/store/internal/filter.js.map +1 -1
- package/dist/store/internal/index-reconcile.d.ts.map +1 -1
- package/dist/store/internal/index-reconcile.js +33 -3
- package/dist/store/internal/index-reconcile.js.map +1 -1
- package/dist/store/internal/keys.d.ts +1 -1
- package/dist/store/internal/keys.d.ts.map +1 -1
- package/dist/store/internal/keys.js +8 -2
- package/dist/store/internal/keys.js.map +1 -1
- package/dist/store/internal/persist.d.ts.map +1 -1
- package/dist/store/internal/persist.js +1 -5
- package/dist/store/internal/persist.js.map +1 -1
- package/dist/store/internal/search-filter.d.ts +4 -0
- package/dist/store/internal/search-filter.d.ts.map +1 -0
- package/dist/store/internal/search-filter.js +11 -0
- package/dist/store/internal/search-filter.js.map +1 -0
- package/dist/store/internal/setup.d.ts +11 -1
- package/dist/store/internal/setup.d.ts.map +1 -1
- package/dist/store/internal/setup.js +17 -1
- package/dist/store/internal/setup.js.map +1 -1
- package/dist/store/internal/validation.d.ts +8 -2
- package/dist/store/internal/validation.d.ts.map +1 -1
- package/dist/store/internal/validation.js +15 -6
- package/dist/store/internal/validation.js.map +1 -1
- package/dist/store/internal/write-verify.d.ts +12 -0
- package/dist/store/internal/write-verify.d.ts.map +1 -1
- package/dist/store/internal/write-verify.js +27 -0
- package/dist/store/internal/write-verify.js.map +1 -1
- package/dist/store/vector-backend.d.ts +17 -0
- package/dist/store/vector-backend.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1,46 +1,31 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.clearSession = clearSession;
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const constants_1 = require("../../shared/constants");
|
|
7
|
-
const batch_write_1 = require("../../shared/dynamodb/batch-write");
|
|
8
|
-
const paginate_1 = require("../../shared/dynamodb/paginate");
|
|
4
|
+
const partition_delete_1 = require("../../shared/dynamodb/partition-delete");
|
|
5
|
+
const keys_1 = require("../internal/keys");
|
|
9
6
|
const query_1 = require("../internal/query");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (context.offloader) {
|
|
15
|
-
await (0, orphans_1.cleanUpS3Orphans)(context.offloader, (0, descriptor_keys_1.collectS3Keys)(buffer.descriptors), 'history.clear', context.logger);
|
|
16
|
-
}
|
|
17
|
-
buffer.keys = [];
|
|
18
|
-
buffer.descriptors = [];
|
|
7
|
+
const validation_1 = require("../internal/validation");
|
|
8
|
+
/** The offloaded payload a chat-history row can reference. */
|
|
9
|
+
function descriptorsOf(row) {
|
|
10
|
+
return [row.message];
|
|
19
11
|
}
|
|
20
12
|
/**
|
|
21
|
-
* Delete a whole session: every message item plus the metadata item,
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* the in-memory page caps, and never discards already-flushed progress if a
|
|
26
|
-
* later batch fails.
|
|
13
|
+
* Delete a whole session: every message item plus the metadata item, best-effort
|
|
14
|
+
* deleting any offloaded S3 objects. Rows this adapter does not own are left in
|
|
15
|
+
* place and logged, so a shared-table partition holding a foreign row is never
|
|
16
|
+
* collaterally wiped.
|
|
27
17
|
*/
|
|
28
18
|
async function clearSession(context, sessionId) {
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
(0, validation_1.validateSessionId)(sessionId);
|
|
20
|
+
await (0, partition_delete_1.deletePartitionRows)({
|
|
31
21
|
client: context.client,
|
|
22
|
+
tableName: context.tableName,
|
|
32
23
|
params: (0, query_1.sessionItemsQuery)(context.tableName, sessionId, { consistent: true }),
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
logger: context.logger,
|
|
25
|
+
offloader: context.offloader,
|
|
26
|
+
operation: 'history.clear',
|
|
27
|
+
ownsSortKey: keys_1.isHistorySortKey,
|
|
28
|
+
descriptorsOf,
|
|
35
29
|
});
|
|
36
|
-
for await (const raw of pages) {
|
|
37
|
-
const item = raw;
|
|
38
|
-
buffer.keys.push({ PK: item.PK, SK: item.SK });
|
|
39
|
-
if (item.message)
|
|
40
|
-
buffer.descriptors.push(item.message);
|
|
41
|
-
if (buffer.keys.length >= constants_1.BATCH_WRITE_MAX)
|
|
42
|
-
await flushBuffer(context, buffer);
|
|
43
|
-
}
|
|
44
|
-
await flushBuffer(context, buffer);
|
|
45
30
|
}
|
|
46
31
|
//# sourceMappingURL=clear.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clear.js","sourceRoot":"","sources":["../../../src/history/actions/clear.ts"],"names":[],"mappings":";;;AACA,
|
|
1
|
+
{"version":3,"file":"clear.js","sourceRoot":"","sources":["../../../src/history/actions/clear.ts"],"names":[],"mappings":";;;AACA,6EAA6E;AAE7E,2CAAoD;AACpD,6CAAsD;AAEtD,uDAA2D;AAE3D,8DAA8D;AAC9D,SAAS,aAAa,CAAC,GAAY;IACjC,OAAO,CAAC,GAAG,CAAC,OAAwC,CAAC,CAAC;AACxD,CAAC;AAED;;;;;GAKG;AACI,KAAK,uBAAuB,OAAuB,EAAE,SAAiB;IAC3E,IAAA,8BAAiB,EAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,IAAA,sCAAmB,EAAC;QACxB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,MAAM,EAAE,IAAA,yBAAiB,EAAC,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;QAC7E,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,eAAe;QAC1B,WAAW,EAAE,uBAAgB;QAC7B,aAAa;KACd,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -3,7 +3,8 @@ import type { HistoryContext } from '../internal/setup';
|
|
|
3
3
|
/**
|
|
4
4
|
* Return a session's messages in chronological order. Items past their TTL are
|
|
5
5
|
* filtered out on read (DynamoDB's background TTL sweep can lag by up to 48h),
|
|
6
|
-
* so the returned history is never stale.
|
|
6
|
+
* so the returned history is never stale. An item that cannot be decoded is
|
|
7
|
+
* handled per `onCorruptMessage` (see {@link decodeOrSkip}).
|
|
7
8
|
*/
|
|
8
9
|
export declare function getMessages(context: HistoryContext, sessionId: string): Promise<BaseMessage[]>;
|
|
9
10
|
//# sourceMappingURL=get-messages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-messages.d.ts","sourceRoot":"","sources":["../../../src/history/actions/get-messages.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAGjB,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"get-messages.d.ts","sourceRoot":"","sources":["../../../src/history/actions/get-messages.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAGjB,MAAM,0BAA0B,CAAC;AAKlC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAgCxD;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,WAAW,EAAE,CAAC,CAgBxB"}
|
|
@@ -5,15 +5,39 @@ const messages_1 = require("@langchain/core/messages");
|
|
|
5
5
|
const paginate_1 = require("../../shared/dynamodb/paginate");
|
|
6
6
|
const item_mapper_1 = require("../internal/item-mapper");
|
|
7
7
|
const query_1 = require("../internal/query");
|
|
8
|
+
const validation_1 = require("../internal/validation");
|
|
8
9
|
function isExpired(item, nowSeconds) {
|
|
9
10
|
return item.ttl !== undefined && item.ttl <= nowSeconds;
|
|
10
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Decode one item, honouring the configured corrupt-message policy. Under the
|
|
14
|
+
* default `'skip'` an undecodable item is reported and dropped rather than
|
|
15
|
+
* taking the whole session down with it: one bad row used to throw out of
|
|
16
|
+
* `getMessages` entirely, making every other message in the session
|
|
17
|
+
* permanently unreadable with no API to remove just the bad one.
|
|
18
|
+
*/
|
|
19
|
+
async function decodeOrSkip(context, sessionId, item) {
|
|
20
|
+
try {
|
|
21
|
+
return await (0, item_mapper_1.decodeMessageItem)(context, item);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (context.onCorruptMessage === 'throw')
|
|
25
|
+
throw error;
|
|
26
|
+
context.logger.error('getMessages: skipped a corrupt message item', {
|
|
27
|
+
sessionId,
|
|
28
|
+
sortKey: item.SK,
|
|
29
|
+
});
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
11
33
|
/**
|
|
12
34
|
* Return a session's messages in chronological order. Items past their TTL are
|
|
13
35
|
* filtered out on read (DynamoDB's background TTL sweep can lag by up to 48h),
|
|
14
|
-
* so the returned history is never stale.
|
|
36
|
+
* so the returned history is never stale. An item that cannot be decoded is
|
|
37
|
+
* handled per `onCorruptMessage` (see {@link decodeOrSkip}).
|
|
15
38
|
*/
|
|
16
39
|
async function getMessages(context, sessionId) {
|
|
40
|
+
(0, validation_1.validateSessionId)(sessionId);
|
|
17
41
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
18
42
|
const stored = [];
|
|
19
43
|
for await (const raw of (0, paginate_1.paginateQuery)({
|
|
@@ -25,7 +49,9 @@ async function getMessages(context, sessionId) {
|
|
|
25
49
|
const item = raw;
|
|
26
50
|
if (isExpired(item, nowSeconds))
|
|
27
51
|
continue;
|
|
28
|
-
|
|
52
|
+
const message = await decodeOrSkip(context, sessionId, item);
|
|
53
|
+
if (message)
|
|
54
|
+
stored.push(message);
|
|
29
55
|
}
|
|
30
56
|
return (0, messages_1.mapStoredMessagesToChatMessages)(stored);
|
|
31
57
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-messages.js","sourceRoot":"","sources":["../../../src/history/actions/get-messages.ts"],"names":[],"mappings":";;;AAAA,uDAIkC;AAElC,6DAA+D;AAC/D,yDAA4D;AAC5D,6CAAiD;
|
|
1
|
+
{"version":3,"file":"get-messages.js","sourceRoot":"","sources":["../../../src/history/actions/get-messages.ts"],"names":[],"mappings":";;;AAAA,uDAIkC;AAElC,6DAA+D;AAC/D,yDAA4D;AAC5D,6CAAiD;AAEjD,uDAA2D;AAG3D,SAAS,SAAS,CAAC,IAAqB,EAAE,UAAkB;IAC1D,OAAO,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,IAAI,UAAU,CAAC;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,KAAK,UAAU,YAAY,CACzB,OAAuB,EACvB,SAAiB,EACjB,IAAqB;IAErB,IAAI,CAAC;QACH,OAAO,MAAM,IAAA,+BAAiB,EAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,OAAO,CAAC,gBAAgB,KAAK,OAAO;YAAE,MAAM,KAAK,CAAC;QACtD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,6CAA6C,EAAE;YAClE,SAAS;YACT,OAAO,EAAE,IAAI,CAAC,EAAE;SACjB,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACI,KAAK,sBACV,OAAuB,EACvB,SAAiB;IAEjB,IAAA,8BAAiB,EAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACjD,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,IAAA,wBAAa,EAAC;QACpC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,IAAA,oBAAY,EAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC;QAClD,QAAQ,EAAE,MAAM,CAAC,iBAAiB;QAClC,aAAa,EAAE,MAAM,CAAC,iBAAiB;KACxC,CAAC,EAAE,CAAC;QACH,MAAM,IAAI,GAAG,GAAsB,CAAC;QACpC,IAAI,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC;YAAE,SAAS;QAC1C,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAC7D,IAAI,OAAO;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IACD,OAAO,IAAA,0CAA+B,EAAC,MAAM,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -3,7 +3,8 @@ import type { SessionMetadata } from '../types';
|
|
|
3
3
|
/**
|
|
4
4
|
* List all sessions as metadata summaries, newest-updated first. The scan is
|
|
5
5
|
* filtered to session items so the adapter works on a table shared with the
|
|
6
|
-
* checkpointer/store; foreign rows are also skipped defensively
|
|
6
|
+
* checkpointer/store; foreign rows are also skipped defensively, and rows past
|
|
7
|
+
* their TTL are filtered out exactly as `getMessages` filters expired messages.
|
|
7
8
|
*/
|
|
8
9
|
export declare function listSessions(context: HistoryContext, options?: {
|
|
9
10
|
maxIterations?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-sessions.d.ts","sourceRoot":"","sources":["../../../src/history/actions/list-sessions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"list-sessions.d.ts","sourceRoot":"","sources":["../../../src/history/actions/list-sessions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAmB,eAAe,EAAE,MAAM,UAAU,CAAC;AAYjE;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,OAAO,CAAC,EAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,eAAe,EAAE,CAAC,CAiC5B"}
|
|
@@ -3,13 +3,24 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.listSessions = listSessions;
|
|
4
4
|
const scan_1 = require("../../shared/dynamodb/scan");
|
|
5
5
|
const keys_1 = require("../internal/keys");
|
|
6
|
+
/**
|
|
7
|
+
* True when a session row is past its TTL. DynamoDB's background sweep can lag
|
|
8
|
+
* up to 48h, so an expired session would otherwise keep appearing in listings
|
|
9
|
+
* after `getMessages` had already stopped returning its messages — the same
|
|
10
|
+
* filter that read path applies.
|
|
11
|
+
*/
|
|
12
|
+
function isExpired(item, nowSeconds) {
|
|
13
|
+
return item.ttl !== undefined && item.ttl <= nowSeconds;
|
|
14
|
+
}
|
|
6
15
|
/**
|
|
7
16
|
* List all sessions as metadata summaries, newest-updated first. The scan is
|
|
8
17
|
* filtered to session items so the adapter works on a table shared with the
|
|
9
|
-
* checkpointer/store; foreign rows are also skipped defensively
|
|
18
|
+
* checkpointer/store; foreign rows are also skipped defensively, and rows past
|
|
19
|
+
* their TTL are filtered out exactly as `getMessages` filters expired messages.
|
|
10
20
|
*/
|
|
11
21
|
async function listSessions(context, options) {
|
|
12
22
|
const sessions = [];
|
|
23
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
13
24
|
for await (const raw of (0, scan_1.paginateScan)({
|
|
14
25
|
client: context.client,
|
|
15
26
|
params: {
|
|
@@ -24,6 +35,8 @@ async function listSessions(context, options) {
|
|
|
24
35
|
const item = raw;
|
|
25
36
|
if (item.SK !== keys_1.SESSION_SORT_KEY || typeof item.sessionId !== 'string')
|
|
26
37
|
continue;
|
|
38
|
+
if (isExpired(item, nowSeconds))
|
|
39
|
+
continue;
|
|
27
40
|
sessions.push({
|
|
28
41
|
sessionId: item.sessionId,
|
|
29
42
|
title: item.title,
|
|
@@ -32,7 +45,13 @@ async function listSessions(context, options) {
|
|
|
32
45
|
updatedAt: item.updatedAt,
|
|
33
46
|
});
|
|
34
47
|
}
|
|
35
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Ordinal, not `localeCompare`: these are ISO-8601 timestamps, whose
|
|
50
|
+
* byte order already is their chronological order. Locale-aware collation
|
|
51
|
+
* applies rules (case folding, punctuation weighting) that have no meaning
|
|
52
|
+
* here and are not guaranteed to agree with it in every locale.
|
|
53
|
+
*/
|
|
54
|
+
sessions.sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0));
|
|
36
55
|
return sessions;
|
|
37
56
|
}
|
|
38
57
|
//# sourceMappingURL=list-sessions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-sessions.js","sourceRoot":"","sources":["../../../src/history/actions/list-sessions.ts"],"names":[],"mappings":";;;AAAA,qDAA0D;AAC1D,2CAAoD;AAIpD
|
|
1
|
+
{"version":3,"file":"list-sessions.js","sourceRoot":"","sources":["../../../src/history/actions/list-sessions.ts"],"names":[],"mappings":";;;AAAA,qDAA0D;AAC1D,2CAAoD;AAIpD;;;;;GAKG;AACH,SAAS,SAAS,CAAC,IAAqB,EAAE,UAAkB;IAC1D,OAAO,IAAI,CAAC,GAAG,KAAK,SAAS,IAAI,IAAI,CAAC,GAAG,IAAI,UAAU,CAAC;AAC1D,CAAC;AAED;;;;;GAKG;AACI,KAAK,uBACV,OAAuB,EACvB,OAAuD;IAEvD,MAAM,QAAQ,GAAsB,EAAE,CAAC;IACvC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IACjD,IAAI,KAAK,EAAE,MAAM,GAAG,IAAI,IAAA,mBAAY,EAAC;QACnC,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE;YACN,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,gBAAgB,EAAE,gBAAgB;YAClC,wBAAwB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE;YACzC,yBAAyB,EAAE,EAAE,UAAU,EAAE,uBAAgB,EAAE;SAC5D;QACD,aAAa,EAAE,OAAO,EAAE,aAAa;QACrC,QAAQ,EAAE,OAAO,EAAE,QAAQ;KAC5B,CAAC,EAAE,CAAC;QACH,MAAM,IAAI,GAAG,GAAsB,CAAC;QACpC,IAAI,IAAI,CAAC,EAAE,KAAK,uBAAgB,IAAI,OAAO,IAAI,CAAC,SAAS,KAAK,QAAQ;YAAE,SAAS;QACjF,IAAI,SAAS,CAAC,IAAI,EAAE,UAAU,CAAC;YAAE,SAAS;QAC1C,QAAQ,CAAC,IAAI,CAAC;YACZ,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,SAAS,EAAE,IAAI,CAAC,SAAS;SAC1B,CAAC,CAAC;IACL,CAAC;IACD;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9F,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-count.d.ts","sourceRoot":"","sources":["../../../src/history/actions/reconcile-count.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"reconcile-count.d.ts","sourceRoot":"","sources":["../../../src/history/actions/reconcile-count.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAiBxD;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,CAwBjB"}
|
|
@@ -3,9 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.reconcileMessageCount = reconcileMessageCount;
|
|
4
4
|
const retry_1 = require("../../shared/dynamodb/retry");
|
|
5
5
|
const errors_1 = require("../../shared/errors/errors");
|
|
6
|
-
const primitives_1 = require("../../shared/validation/primitives");
|
|
7
6
|
const keys_1 = require("../internal/keys");
|
|
8
7
|
const query_1 = require("../internal/query");
|
|
8
|
+
const validation_1 = require("../internal/validation");
|
|
9
9
|
async function countMessages(context, sessionId) {
|
|
10
10
|
const base = (0, query_1.messageQuery)(context.tableName, sessionId);
|
|
11
11
|
let total = 0;
|
|
@@ -28,7 +28,7 @@ async function countMessages(context, sessionId) {
|
|
|
28
28
|
* reconciled count.
|
|
29
29
|
*/
|
|
30
30
|
async function reconcileMessageCount(context, sessionId) {
|
|
31
|
-
(0,
|
|
31
|
+
(0, validation_1.validateSessionId)(sessionId);
|
|
32
32
|
const count = await countMessages(context, sessionId);
|
|
33
33
|
try {
|
|
34
34
|
await (0, retry_1.withDynamoDBRetry)(() => context.client.update({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile-count.js","sourceRoot":"","sources":["../../../src/history/actions/reconcile-count.ts"],"names":[],"mappings":";;;AAEA,uDAAgE;AAChE,uDAA2D;AAC3D,
|
|
1
|
+
{"version":3,"file":"reconcile-count.js","sourceRoot":"","sources":["../../../src/history/actions/reconcile-count.ts"],"names":[],"mappings":";;;AAEA,uDAAgE;AAChE,uDAA2D;AAC3D,2CAAsE;AACtE,6CAAiD;AAEjD,uDAA2D;AAE3D,KAAK,UAAU,aAAa,CAAC,OAAuB,EAAE,SAAiB;IACrE,MAAM,IAAI,GAAG,IAAA,oBAAY,EAAC,OAAO,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACxD,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAA0D,CAAC;IAC/D,GAAG,CAAC;QACF,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAiB,EAAC,GAAG,EAAE,CACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,CAAC,CAChF,CAAC;QACF,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;QACzB,QAAQ,GAAG,IAAI,CAAC,gBAAgB,CAAC;IACnC,CAAC,QAAQ,QAAQ,EAAE;IACnB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACI,KAAK,gCACV,OAAuB,EACvB,SAAiB;IAEjB,IAAA,8BAAiB,EAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,MAAM,IAAA,yBAAiB,EAAC,GAAG,EAAE,CAC3B,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC;YACpB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,GAAG,EAAE,EAAE,EAAE,EAAE,IAAA,uBAAgB,EAAC,SAAS,CAAC,EAAE,EAAE,EAAE,uBAAgB,EAAE;YAC9D,gBAAgB,EAAE,qBAAqB;YACvC,wBAAwB,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE;YACtD,yBAAyB,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE;YAC9C,mBAAmB,EAAE,sBAAsB;SAC5C,CAAC,CACH,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA2B,CAAC,IAAI,KAAK,iCAAiC,EAAE,CAAC;YAC5E,MAAM,IAAI,sBAAa,CACrB,2CAA2C,SAAS,kBAAkB,EACtE,KAAc,CACf,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"append-saga.d.ts","sourceRoot":"","sources":["../../../src/history/internal/append-saga.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"append-saga.d.ts","sourceRoot":"","sources":["../../../src/history/internal/append-saga.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAGhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,eAAe,EAAE,EAAE,EAC3B,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAyBf"}
|
|
@@ -1,85 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.appendChunks = appendChunks;
|
|
4
|
-
const descriptor_keys_1 = require("../../shared/codec/descriptor-keys");
|
|
5
|
-
const orphans_1 = require("../../shared/codec/s3/orphans");
|
|
6
|
-
const batch_write_1 = require("../../shared/dynamodb/batch-write");
|
|
7
|
-
const errors_1 = require("../../shared/errors/errors");
|
|
8
4
|
const wrap_error_1 = require("../../shared/errors/wrap-error");
|
|
5
|
+
const compensation_1 = require("./compensation");
|
|
9
6
|
const message_transaction_1 = require("./message-transaction");
|
|
10
|
-
const session_count_1 = require("./session-count");
|
|
11
|
-
/**
|
|
12
|
-
* Best-effort delete the offloaded S3 objects of the `chunks` slice given.
|
|
13
|
-
* {@link compensate} calls it once per commit status, never for the whole
|
|
14
|
-
* batch, so a committed chunk's objects arrive only once its rows are gone.
|
|
15
|
-
*/
|
|
16
|
-
async function cleanBatchS3(context, chunks) {
|
|
17
|
-
if (!context.offloader)
|
|
18
|
-
return;
|
|
19
|
-
const descriptors = chunks.flat().map((item) => item.message);
|
|
20
|
-
await (0, orphans_1.cleanUpS3Orphans)(context.offloader, (0, descriptor_keys_1.collectS3Keys)(descriptors), 'history.addMessages', context.logger);
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Delete every committed chunk's items, then revert the counted total by
|
|
24
|
-
* however many rows the delete actually persisted before failing (not
|
|
25
|
-
* necessarily every committed row — see the catch branch below).
|
|
26
|
-
*/
|
|
27
|
-
async function rollbackCommitted(context, sessionId, committed) {
|
|
28
|
-
const keys = committed.flatMap((chunk) => chunk.keys);
|
|
29
|
-
const total = committed.reduce((sum, chunk) => sum + chunk.count, 0);
|
|
30
|
-
if (keys.length === 0) {
|
|
31
|
-
await (0, session_count_1.revertSessionCount)(context, sessionId, total);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
await (0, batch_write_1.batchWriteAll)(context.client, context.tableName, keys.map((Key) => ({ DeleteRequest: { Key } })));
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
/**
|
|
39
|
-
* batchWriteAll has exactly one throw site and it is always a
|
|
40
|
-
* BatchWriteAllIncompleteError (see Task 9) — asserted, not instanceof-
|
|
41
|
-
* checked, since the false case is unreachable and this project
|
|
42
|
-
* enforces 100% branch coverage with no exceptions.
|
|
43
|
-
*/
|
|
44
|
-
const deleted = error.succeededCount;
|
|
45
|
-
await (0, session_count_1.revertSessionCount)(context, sessionId, deleted);
|
|
46
|
-
throw error;
|
|
47
|
-
}
|
|
48
|
-
await (0, session_count_1.revertSessionCount)(context, sessionId, total);
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Undo a failed batch. Always throws. S3 cleanup is split by commit status so
|
|
52
|
-
* no live row is ever left pointing at a deleted object: the never-committed
|
|
53
|
-
* suffix is cleaned immediately, the committed prefix only after its rows are
|
|
54
|
-
* confirmed deleted. If the rollback itself fails, the committed chunks' S3
|
|
55
|
-
* objects are deliberately left in place (their rows may survive) and it
|
|
56
|
-
* raises {@link CompensationFailedError} carrying both the trigger and the
|
|
57
|
-
* rollback error; otherwise it rethrows the trigger.
|
|
58
|
-
*/
|
|
59
|
-
async function compensate(context, sessionId, chunks, committed, trigger) {
|
|
60
|
-
if (committed.length > 0) {
|
|
61
|
-
context.logger.warn('history.addMessages compensating committed chunks after a chunk failed', {
|
|
62
|
-
sessionId,
|
|
63
|
-
committedChunks: committed.length,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
/** The failed/never-attempted suffix never had a DynamoDB row, so it's safe to clean now. */
|
|
67
|
-
await cleanBatchS3(context, chunks.slice(committed.length));
|
|
68
|
-
try {
|
|
69
|
-
await rollbackCommitted(context, sessionId, committed);
|
|
70
|
-
}
|
|
71
|
-
catch (rollbackError) {
|
|
72
|
-
context.logger.error('history.addMessages rollback failed; messageCount may have drifted', {
|
|
73
|
-
sessionId,
|
|
74
|
-
committedChunks: committed.length,
|
|
75
|
-
});
|
|
76
|
-
/** Skip S3 cleanup here: rollback may have failed, so committed rows might still reference these objects. */
|
|
77
|
-
throw new errors_1.CompensationFailedError(trigger, (0, wrap_error_1.toError)(rollbackError));
|
|
78
|
-
}
|
|
79
|
-
/** Only now that committed rows are confirmed deleted is it safe to delete their S3 objects. */
|
|
80
|
-
await cleanBatchS3(context, chunks.slice(0, committed.length));
|
|
81
|
-
throw trigger;
|
|
82
|
-
}
|
|
83
7
|
/**
|
|
84
8
|
* Append message chunks with caller-observed atomicity. Each chunk commits its
|
|
85
9
|
* messages and count in one transaction; if a later chunk fails, every
|
|
@@ -104,7 +28,7 @@ async function appendChunks(context, sessionId, chunks, fields) {
|
|
|
104
28
|
});
|
|
105
29
|
}
|
|
106
30
|
catch (error) {
|
|
107
|
-
await compensate(context, sessionId, chunks, committed, (0, wrap_error_1.toError)(error));
|
|
31
|
+
await (0, compensation_1.compensate)(context, sessionId, chunks, committed, (0, wrap_error_1.toError)(error), fields.now, fields.title);
|
|
108
32
|
}
|
|
109
33
|
}
|
|
110
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"append-saga.js","sourceRoot":"","sources":["../../../src/history/internal/append-saga.ts"],"names":[],"mappings":";;;AAAA
|
|
1
|
+
{"version":3,"file":"append-saga.js","sourceRoot":"","sources":["../../../src/history/internal/append-saga.ts"],"names":[],"mappings":";;;AAAA,+DAAyD;AAEzD,iDAAiE;AACjE,+DAA0D;AAW1D;;;;;;;;GAQG;AACI,KAAK,uBACV,OAAuB,EACvB,SAAiB,EACjB,MAA2B,EAC3B,MAAoB;IAEpB,MAAM,SAAS,GAAqB,EAAE,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,IAAA,uCAAiB,EAAC,OAAO,EAAE,KAAK,EAAE;gBACtC,GAAG,MAAM;gBACT,SAAS;gBACT,KAAK,EAAE,KAAK,CAAC,MAAM;aACpB,CAAC,CAAC;YACH,SAAS,CAAC,IAAI,CAAC;gBACb,IAAI,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;gBACzD,KAAK,EAAE,KAAK,CAAC,MAAM;aACpB,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAA,yBAAU,EACd,OAAO,EACP,SAAS,EACT,MAAM,EACN,SAAS,EACT,IAAA,oBAAO,EAAC,KAAc,CAAC,EACvB,MAAM,CAAC,GAAG,EACV,MAAM,CAAC,KAAK,CACb,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ChatMessageItem } from '../types';
|
|
2
|
+
import type { HistoryContext } from './setup';
|
|
3
|
+
/** A chunk that committed, retained so it can be rolled back on a later failure. */
|
|
4
|
+
export interface CommittedChunk {
|
|
5
|
+
keys: {
|
|
6
|
+
PK: string;
|
|
7
|
+
SK: string;
|
|
8
|
+
}[];
|
|
9
|
+
count: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Undo a failed batch. Always throws. S3 cleanup is split by commit status so
|
|
13
|
+
* no live row is ever left pointing at a deleted object: the never-committed
|
|
14
|
+
* suffix is cleaned immediately, the committed prefix only after its rows are
|
|
15
|
+
* confirmed deleted. If the rollback itself fails, the committed chunks' S3
|
|
16
|
+
* objects are deliberately left in place (their rows may survive) and it
|
|
17
|
+
* raises {@link CompensationFailedError} carrying both the trigger and the
|
|
18
|
+
* rollback error; otherwise it rethrows the trigger.
|
|
19
|
+
*/
|
|
20
|
+
export declare function compensate(context: HistoryContext, sessionId: string, chunks: ChatMessageItem[][], committed: CommittedChunk[], trigger: Error, now: string, title: string | undefined): Promise<never>;
|
|
21
|
+
//# sourceMappingURL=compensation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compensation.d.ts","sourceRoot":"","sources":["../../../src/history/internal/compensation.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAEhD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AA0DD;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,eAAe,EAAE,EAAE,EAC3B,SAAS,EAAE,cAAc,EAAE,EAC3B,OAAO,EAAE,KAAK,EACd,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,OAAO,CAAC,KAAK,CAAC,CAsBhB"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.compensate = compensate;
|
|
4
|
+
const descriptor_keys_1 = require("../../shared/codec/descriptor-keys");
|
|
5
|
+
const orphans_1 = require("../../shared/codec/s3/orphans");
|
|
6
|
+
const batch_write_1 = require("../../shared/dynamodb/batch-write");
|
|
7
|
+
const errors_1 = require("../../shared/errors/errors");
|
|
8
|
+
const wrap_error_1 = require("../../shared/errors/wrap-error");
|
|
9
|
+
const session_count_1 = require("./session-count");
|
|
10
|
+
/**
|
|
11
|
+
* Best-effort delete the offloaded S3 objects of the `chunks` slice given.
|
|
12
|
+
* {@link compensate} calls it once per commit status, never for the whole
|
|
13
|
+
* batch, so a committed chunk's objects arrive only once its rows are gone.
|
|
14
|
+
*/
|
|
15
|
+
async function cleanBatchS3(context, chunks) {
|
|
16
|
+
if (!context.offloader)
|
|
17
|
+
return;
|
|
18
|
+
const descriptors = chunks.flat().map((item) => item.message);
|
|
19
|
+
await (0, orphans_1.cleanUpS3Orphans)(context.offloader, (0, descriptor_keys_1.collectS3Keys)(descriptors), 'history.addMessages', context.logger);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Delete every committed chunk's items, then undo their effect on the session
|
|
23
|
+
* row — deleting it outright when this call created it (see
|
|
24
|
+
* {@link revertSessionCreation}), so a failed first append leaves no ghost
|
|
25
|
+
* session holding the rolled-back message's title. A *partial* delete is not a
|
|
26
|
+
* clean creation to undo, so that branch reverts only the count.
|
|
27
|
+
*/
|
|
28
|
+
async function rollbackCommitted(context, sessionId, committed, now, title) {
|
|
29
|
+
const keys = committed.flatMap((chunk) => chunk.keys);
|
|
30
|
+
const total = committed.reduce((sum, chunk) => sum + chunk.count, 0);
|
|
31
|
+
if (keys.length === 0) {
|
|
32
|
+
await (0, session_count_1.revertSessionCreation)(context, sessionId, total, now, title);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
await (0, batch_write_1.batchWriteAll)(context.client, context.tableName, keys.map((Key) => ({ DeleteRequest: { Key } })));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
/**
|
|
40
|
+
* batchWriteAll has exactly one throw site and it is always a
|
|
41
|
+
* BatchWriteAllIncompleteError (see Task 9) — asserted, not instanceof-
|
|
42
|
+
* checked, since the false case is unreachable and this project
|
|
43
|
+
* enforces 100% branch coverage with no exceptions.
|
|
44
|
+
*/
|
|
45
|
+
const deleted = error.succeededCount;
|
|
46
|
+
await (0, session_count_1.revertSessionCount)(context, sessionId, deleted);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
await (0, session_count_1.revertSessionCreation)(context, sessionId, total, now, title);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Undo a failed batch. Always throws. S3 cleanup is split by commit status so
|
|
53
|
+
* no live row is ever left pointing at a deleted object: the never-committed
|
|
54
|
+
* suffix is cleaned immediately, the committed prefix only after its rows are
|
|
55
|
+
* confirmed deleted. If the rollback itself fails, the committed chunks' S3
|
|
56
|
+
* objects are deliberately left in place (their rows may survive) and it
|
|
57
|
+
* raises {@link CompensationFailedError} carrying both the trigger and the
|
|
58
|
+
* rollback error; otherwise it rethrows the trigger.
|
|
59
|
+
*/
|
|
60
|
+
async function compensate(context, sessionId, chunks, committed, trigger, now, title) {
|
|
61
|
+
if (committed.length > 0) {
|
|
62
|
+
context.logger.warn('history.addMessages compensating committed chunks after a chunk failed', {
|
|
63
|
+
sessionId,
|
|
64
|
+
committedChunks: committed.length,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** The failed/never-attempted suffix never had a DynamoDB row, so it's safe to clean now. */
|
|
68
|
+
await cleanBatchS3(context, chunks.slice(committed.length));
|
|
69
|
+
try {
|
|
70
|
+
await rollbackCommitted(context, sessionId, committed, now, title);
|
|
71
|
+
}
|
|
72
|
+
catch (rollbackError) {
|
|
73
|
+
context.logger.error('history.addMessages rollback failed; messageCount may have drifted', {
|
|
74
|
+
sessionId,
|
|
75
|
+
committedChunks: committed.length,
|
|
76
|
+
});
|
|
77
|
+
/** Skip S3 cleanup here: rollback may have failed, so committed rows might still reference these objects. */
|
|
78
|
+
throw new errors_1.CompensationFailedError(trigger, (0, wrap_error_1.toError)(rollbackError));
|
|
79
|
+
}
|
|
80
|
+
/** Only now that committed rows are confirmed deleted is it safe to delete their S3 objects. */
|
|
81
|
+
await cleanBatchS3(context, chunks.slice(0, committed.length));
|
|
82
|
+
throw trigger;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=compensation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compensation.js","sourceRoot":"","sources":["../../../src/history/internal/compensation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
/** Reserved separator joining sort-key segments; forbidden inside a session id. */
|
|
2
|
+
export declare const SORT_KEY_SEPARATOR = "#";
|
|
1
3
|
/** Fixed sort key for the per-session metadata item. */
|
|
2
|
-
export declare const SESSION_SORT_KEY = "SESSION";
|
|
3
|
-
/** Partition key for a chat session: the session id
|
|
4
|
+
export declare const SESSION_SORT_KEY = "HISTORY#SESSION";
|
|
5
|
+
/** Partition key for a chat session: the adapter tag plus the session id. */
|
|
4
6
|
export declare function sessionPartition(sessionId: string): string;
|
|
5
7
|
/** Sort key for a single message item, ordered by its monotonic ULID. */
|
|
6
8
|
export declare function messageSortKey(ulid: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* True when `sortKey` is one this adapter writes. A partition query carries no
|
|
11
|
+
* sort-key condition, so a partition-wide delete uses this to leave any row it
|
|
12
|
+
* does not own in place rather than deleting the whole partition blindly.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isHistorySortKey(sortKey: string): boolean;
|
|
7
15
|
/** `begins_with` prefix selecting every message item in a session. */
|
|
8
16
|
export declare function messageSortKeyPrefix(): string;
|
|
9
17
|
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/history/internal/keys.ts"],"names":[],"mappings":"AAAA,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/history/internal/keys.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAatC,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,oBAA6B,CAAC;AAW3D,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,sEAAsE;AACtE,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
|
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SESSION_SORT_KEY = void 0;
|
|
3
|
+
exports.SESSION_SORT_KEY = exports.SORT_KEY_SEPARATOR = void 0;
|
|
4
4
|
exports.sessionPartition = sessionPartition;
|
|
5
5
|
exports.messageSortKey = messageSortKey;
|
|
6
|
+
exports.isHistorySortKey = isHistorySortKey;
|
|
6
7
|
exports.messageSortKeyPrefix = messageSortKeyPrefix;
|
|
8
|
+
/** Reserved separator joining sort-key segments; forbidden inside a session id. */
|
|
9
|
+
exports.SORT_KEY_SEPARATOR = '#';
|
|
10
|
+
/**
|
|
11
|
+
* Item-kind tag distinguishing this adapter's sort keys from another
|
|
12
|
+
* adapter's on a table shared via `DynamoDBFactory.createAll()` — matches the
|
|
13
|
+
* pattern the checkpointer module already uses for its own META#/PAYLOAD#/
|
|
14
|
+
* WRITE# keys. Without it, `SESSION_SORT_KEY` alone was a bare, common-word
|
|
15
|
+
* literal a store caller could produce by accident (e.g.
|
|
16
|
+
* `store.put([sessionId], 'SESSION', ...)`, since `sortKey` collapses a
|
|
17
|
+
* single-element namespace down to just the key).
|
|
18
|
+
*/
|
|
19
|
+
const ADAPTER_PREFIX = `HISTORY${exports.SORT_KEY_SEPARATOR}`;
|
|
7
20
|
/** Fixed sort key for the per-session metadata item. */
|
|
8
|
-
exports.SESSION_SORT_KEY =
|
|
9
|
-
const MESSAGE_PREFIX =
|
|
10
|
-
/**
|
|
21
|
+
exports.SESSION_SORT_KEY = `${ADAPTER_PREFIX}SESSION`;
|
|
22
|
+
const MESSAGE_PREFIX = `${ADAPTER_PREFIX}MSG#`;
|
|
23
|
+
/**
|
|
24
|
+
* Adapter tag prefixed to every chat-history partition key — see the
|
|
25
|
+
* equivalent in checkpointer/internal/keys.ts for why the three adapters'
|
|
26
|
+
* partitions must not overlap on a shared table.
|
|
27
|
+
*/
|
|
28
|
+
const ADAPTER_PARTITION_PREFIX = `HIST${exports.SORT_KEY_SEPARATOR}`;
|
|
29
|
+
/** Partition key for a chat session: the adapter tag plus the session id. */
|
|
11
30
|
function sessionPartition(sessionId) {
|
|
12
|
-
return sessionId
|
|
31
|
+
return `${ADAPTER_PARTITION_PREFIX}${sessionId}`;
|
|
13
32
|
}
|
|
14
33
|
/** Sort key for a single message item, ordered by its monotonic ULID. */
|
|
15
34
|
function messageSortKey(ulid) {
|
|
16
35
|
return `${MESSAGE_PREFIX}${ulid}`;
|
|
17
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* True when `sortKey` is one this adapter writes. A partition query carries no
|
|
39
|
+
* sort-key condition, so a partition-wide delete uses this to leave any row it
|
|
40
|
+
* does not own in place rather than deleting the whole partition blindly.
|
|
41
|
+
*/
|
|
42
|
+
function isHistorySortKey(sortKey) {
|
|
43
|
+
return sortKey.startsWith(ADAPTER_PREFIX);
|
|
44
|
+
}
|
|
18
45
|
/** `begins_with` prefix selecting every message item in a session. */
|
|
19
46
|
function messageSortKeyPrefix() {
|
|
20
47
|
return MESSAGE_PREFIX;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/history/internal/keys.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/history/internal/keys.ts"],"names":[],"mappings":";;;;;;;AAAA,mFAAmF;AACtE,QAAA,kBAAkB,GAAG,GAAG,CAAC;AAEtC;;;;;;;;GAQG;AACH,MAAM,cAAc,GAAG,UAAU,QAAA,kBAAkB,EAAE,CAAC;AAEtD,wDAAwD;AAC3C,QAAA,gBAAgB,GAAG,GAAG,cAAc,SAAS,CAAC;AAE3D,MAAM,cAAc,GAAG,GAAG,cAAc,MAAM,CAAC;AAE/C;;;;GAIG;AACH,MAAM,wBAAwB,GAAG,OAAO,QAAA,kBAAkB,EAAE,CAAC;AAE7D,6EAA6E;AAC7E,0BAAiC,SAAiB;IAChD,OAAO,GAAG,wBAAwB,GAAG,SAAS,EAAE,CAAC;AACnD,CAAC;AAED,yEAAyE;AACzE,wBAA+B,IAAY;IACzC,OAAO,GAAG,cAAc,GAAG,IAAI,EAAE,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,0BAAiC,OAAe;IAC9C,OAAO,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAC5C,CAAC;AAED,sEAAsE;AACtE;IACE,OAAO,cAAc,CAAC;AACxB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-chunker.d.ts","sourceRoot":"","sources":["../../../src/history/internal/message-chunker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"message-chunker.d.ts","sourceRoot":"","sources":["../../../src/history/internal/message-chunker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AA4BhD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAQ/D;AAaD;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EAAE,EACxB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,GACf,eAAe,EAAE,EAAE,CAgBrB"}
|