@farukada/aws-langgraph-dynamodb-ts 0.9.0 → 1.0.0-rc.1
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 +282 -49
- package/dist/checkpointer/actions/delete-thread.d.ts +3 -2
- package/dist/checkpointer/actions/delete-thread.js +5 -2
- package/dist/checkpointer/actions/get-tuple.d.ts +3 -2
- package/dist/checkpointer/actions/get-tuple.js +10 -4
- package/dist/checkpointer/actions/list.d.ts +14 -4
- package/dist/checkpointer/actions/list.js +89 -63
- package/dist/checkpointer/actions/put-writes.d.ts +5 -5
- package/dist/checkpointer/actions/put-writes.js +12 -41
- package/dist/checkpointer/actions/put.d.ts +10 -3
- package/dist/checkpointer/actions/put.js +42 -12
- package/dist/checkpointer/internal/assemble.d.ts +10 -3
- package/dist/checkpointer/internal/assemble.js +7 -6
- package/dist/checkpointer/internal/checkpoint-write-verify.d.ts +24 -0
- package/dist/checkpointer/internal/checkpoint-write-verify.js +67 -0
- package/dist/checkpointer/internal/configurable.d.ts +0 -1
- package/dist/checkpointer/internal/configurable.js +10 -9
- package/dist/checkpointer/internal/fetch.d.ts +14 -5
- package/dist/checkpointer/internal/fetch.js +56 -14
- package/dist/checkpointer/internal/filter-match.d.ts +3 -2
- package/dist/checkpointer/internal/filter-match.js +4 -3
- package/dist/checkpointer/internal/item-reader.d.ts +17 -6
- package/dist/checkpointer/internal/item-reader.js +39 -14
- package/dist/checkpointer/internal/item-writer.d.ts +12 -3
- package/dist/checkpointer/internal/item-writer.js +24 -9
- package/dist/checkpointer/internal/keys.d.ts +11 -1
- package/dist/checkpointer/internal/keys.js +33 -3
- package/dist/checkpointer/internal/list-scope.d.ts +61 -0
- package/dist/checkpointer/internal/list-scope.js +108 -0
- package/dist/checkpointer/internal/pending-sends.d.ts +13 -0
- package/dist/checkpointer/internal/pending-sends.js +29 -0
- package/dist/checkpointer/internal/query.d.ts +2 -1
- package/dist/checkpointer/internal/query.js +9 -3
- package/dist/checkpointer/internal/regular-write.d.ts +28 -0
- package/dist/checkpointer/internal/regular-write.js +66 -0
- package/dist/checkpointer/internal/setup.d.ts +3 -1
- package/dist/checkpointer/internal/setup.js +10 -8
- package/dist/checkpointer/internal/special-write-cas.d.ts +1 -2
- package/dist/checkpointer/internal/special-write-cas.js +9 -9
- package/dist/checkpointer/internal/special-write-cleanup.d.ts +1 -2
- package/dist/checkpointer/internal/special-write-cleanup.js +10 -7
- package/dist/checkpointer/internal/special-write-verify.d.ts +0 -1
- package/dist/checkpointer/internal/special-write-verify.js +18 -3
- package/dist/checkpointer/internal/stored-channels.d.ts +31 -0
- package/dist/checkpointer/internal/stored-channels.js +70 -0
- package/dist/checkpointer/internal/validation.d.ts +11 -6
- package/dist/checkpointer/internal/validation.js +19 -9
- package/dist/checkpointer/internal/write-guard.d.ts +8 -1
- package/dist/checkpointer/internal/write-guard.js +16 -7
- package/dist/checkpointer/internal/write-index.d.ts +0 -1
- package/dist/checkpointer/internal/write-index.js +0 -1
- package/dist/checkpointer/saver.d.ts +50 -10
- package/dist/checkpointer/saver.js +59 -17
- package/dist/checkpointer/types.d.ts +8 -1
- package/dist/checkpointer/types.js +0 -1
- package/dist/factory/factory.d.ts +22 -25
- package/dist/factory/factory.js +58 -18
- package/dist/factory/types.d.ts +57 -0
- package/dist/factory/types.js +2 -0
- package/dist/history/actions/add-messages.d.ts +1 -2
- package/dist/history/actions/add-messages.js +4 -4
- package/dist/history/actions/clear.d.ts +3 -2
- package/dist/history/actions/clear.js +5 -2
- package/dist/history/actions/get-messages.d.ts +13 -6
- package/dist/history/actions/get-messages.js +63 -42
- package/dist/history/actions/list-sessions.d.ts +1 -1
- package/dist/history/actions/list-sessions.js +8 -12
- package/dist/history/actions/reconcile-count.d.ts +1 -2
- package/dist/history/actions/reconcile-count.js +20 -7
- package/dist/history/chat-message-history.d.ts +51 -26
- package/dist/history/chat-message-history.js +60 -30
- package/dist/history/internal/append-saga.d.ts +7 -2
- package/dist/history/internal/append-saga.js +58 -14
- package/dist/history/internal/compensation.d.ts +6 -2
- package/dist/history/internal/compensation.js +14 -6
- package/dist/history/internal/item-mapper.d.ts +0 -3
- package/dist/history/internal/item-mapper.js +2 -8
- package/dist/history/internal/keys.d.ts +0 -1
- package/dist/history/internal/keys.js +0 -1
- package/dist/history/internal/message-chunker.d.ts +0 -1
- package/dist/history/internal/message-chunker.js +0 -1
- package/dist/history/internal/message-transaction.d.ts +0 -1
- package/dist/history/internal/message-transaction.js +3 -2
- package/dist/history/internal/message-window.d.ts +17 -0
- package/dist/history/internal/message-window.js +49 -0
- package/dist/history/internal/query.d.ts +16 -3
- package/dist/history/internal/query.js +13 -5
- package/dist/history/internal/session-count.d.ts +8 -2
- package/dist/history/internal/session-count.js +14 -8
- package/dist/history/internal/session-title.d.ts +0 -1
- package/dist/history/internal/session-title.js +1 -2
- package/dist/history/internal/session-update.d.ts +0 -1
- package/dist/history/internal/session-update.js +0 -1
- package/dist/history/internal/setup.d.ts +3 -1
- package/dist/history/internal/setup.js +16 -8
- package/dist/history/internal/title-generator.d.ts +0 -1
- package/dist/history/internal/title-generator.js +19 -3
- package/dist/history/internal/ttl-anchor.d.ts +1 -2
- package/dist/history/internal/ttl-anchor.js +5 -5
- package/dist/history/internal/validation.d.ts +19 -3
- package/dist/history/internal/validation.js +41 -4
- package/dist/history/session-adapter.d.ts +9 -3
- package/dist/history/session-adapter.js +6 -3
- package/dist/history/types.d.ts +23 -2
- package/dist/history/types.js +0 -1
- package/dist/index.d.ts +12 -5
- package/dist/index.js +5 -3
- package/dist/shared/clock.d.ts +6 -1
- package/dist/shared/clock.js +9 -1
- package/dist/shared/codec/codec.d.ts +26 -17
- package/dist/shared/codec/codec.js +46 -24
- package/dist/shared/codec/compression.d.ts +1 -2
- package/dist/shared/codec/compression.js +2 -3
- package/dist/shared/codec/descriptor-keys.d.ts +11 -3
- package/dist/shared/codec/descriptor-keys.js +2 -2
- package/dist/shared/codec/encode.d.ts +13 -0
- package/dist/shared/codec/encode.js +43 -0
- package/dist/shared/codec/json-serde.d.ts +0 -1
- package/dist/shared/codec/json-serde.js +0 -1
- package/dist/shared/codec/payload-loss.d.ts +12 -0
- package/dist/shared/codec/payload-loss.js +38 -0
- package/dist/shared/codec/s3/adapter-config.d.ts +13 -0
- package/dist/shared/codec/s3/adapter-config.js +22 -0
- package/dist/shared/codec/s3/bounded-body.d.ts +14 -0
- package/dist/shared/codec/s3/bounded-body.js +47 -0
- package/dist/shared/codec/s3/client-types.d.ts +34 -0
- package/dist/shared/codec/s3/client-types.js +7 -0
- package/dist/shared/codec/s3/client.d.ts +11 -5
- package/dist/shared/codec/s3/client.js +25 -3
- package/dist/shared/codec/s3/config.d.ts +32 -5
- package/dist/shared/codec/s3/config.js +37 -5
- package/dist/shared/codec/s3/delete.d.ts +0 -1
- package/dist/shared/codec/s3/delete.js +0 -1
- package/dist/shared/codec/s3/key-scope.d.ts +23 -0
- package/dist/shared/codec/s3/key-scope.js +44 -0
- package/dist/shared/codec/s3/lifecycle.d.ts +11 -5
- package/dist/shared/codec/s3/lifecycle.js +32 -16
- package/dist/shared/codec/s3/offloader.d.ts +6 -2
- package/dist/shared/codec/s3/offloader.js +21 -3
- package/dist/shared/codec/s3/orphans.d.ts +6 -1
- package/dist/shared/codec/s3/orphans.js +15 -2
- package/dist/shared/codec/s3/read-write.d.ts +7 -3
- package/dist/shared/codec/s3/read-write.js +16 -8
- package/dist/shared/codec/s3/retry.d.ts +4 -4
- package/dist/shared/codec/s3/retry.js +13 -16
- package/dist/shared/concurrency.d.ts +6 -0
- package/dist/shared/concurrency.js +30 -0
- package/dist/shared/constants.d.ts +53 -5
- package/dist/shared/constants.js +55 -7
- package/dist/shared/dynamodb/abort.d.ts +9 -0
- package/dist/shared/dynamodb/abort.js +27 -0
- package/dist/shared/dynamodb/backoff.d.ts +4 -4
- package/dist/shared/dynamodb/backoff.js +7 -7
- package/dist/shared/dynamodb/batch-write.d.ts +0 -1
- package/dist/shared/dynamodb/batch-write.js +0 -1
- package/dist/shared/dynamodb/cancellation.d.ts +0 -1
- package/dist/shared/dynamodb/cancellation.js +0 -1
- package/dist/shared/dynamodb/client.d.ts +9 -2
- package/dist/shared/dynamodb/client.js +24 -1
- package/dist/shared/dynamodb/conditional-put.d.ts +18 -2
- package/dist/shared/dynamodb/conditional-put.js +19 -2
- package/dist/shared/dynamodb/drain-unprocessed.d.ts +3 -1
- package/dist/shared/dynamodb/drain-unprocessed.js +1 -2
- package/dist/shared/dynamodb/expiry.d.ts +16 -0
- package/dist/shared/dynamodb/expiry.js +29 -0
- package/dist/shared/dynamodb/paginate-core.d.ts +5 -3
- package/dist/shared/dynamodb/paginate-core.js +47 -17
- package/dist/shared/dynamodb/paginate.d.ts +0 -1
- package/dist/shared/dynamodb/paginate.js +1 -2
- package/dist/shared/dynamodb/partition-delete.d.ts +7 -1
- package/dist/shared/dynamodb/partition-delete.js +4 -3
- package/dist/shared/dynamodb/retry-classifier.d.ts +10 -2
- package/dist/shared/dynamodb/retry-classifier.js +42 -11
- package/dist/shared/dynamodb/retry-policy.d.ts +26 -0
- package/dist/shared/dynamodb/retry-policy.js +22 -0
- package/dist/shared/dynamodb/retry.d.ts +14 -1
- package/dist/shared/dynamodb/retry.js +11 -7
- package/dist/shared/dynamodb/scan.d.ts +0 -1
- package/dist/shared/dynamodb/scan.js +1 -2
- package/dist/shared/dynamodb/types.d.ts +1 -2
- package/dist/shared/dynamodb/types.js +0 -1
- package/dist/shared/errors/base-error.d.ts +14 -6
- package/dist/shared/errors/base-error.js +8 -9
- package/dist/shared/errors/boundary.d.ts +18 -0
- package/dist/shared/errors/boundary.js +46 -0
- package/dist/shared/errors/error-code.d.ts +2 -2
- package/dist/shared/errors/error-code.js +1 -1
- package/dist/shared/errors/errors.d.ts +13 -13
- package/dist/shared/errors/errors.js +18 -16
- package/dist/shared/errors/upstream-error.d.ts +16 -0
- package/dist/shared/errors/upstream-error.js +27 -0
- package/dist/shared/errors/wrap-error.d.ts +0 -9
- package/dist/shared/errors/wrap-error.js +0 -15
- package/dist/shared/logging/logger.d.ts +6 -2
- package/dist/shared/logging/logger.js +0 -1
- package/dist/shared/logging/redaction-walk.d.ts +2 -2
- package/dist/shared/logging/redaction-walk.js +38 -18
- package/dist/shared/logging/redaction.d.ts +9 -5
- package/dist/shared/logging/redaction.js +19 -4
- package/dist/shared/logging/secret-patterns.d.ts +21 -3
- package/dist/shared/logging/secret-patterns.js +32 -6
- package/dist/shared/options.d.ts +9 -4
- package/dist/shared/options.js +0 -1
- package/dist/shared/ulid.d.ts +15 -2
- package/dist/shared/ulid.js +35 -3
- package/dist/shared/validation/options.d.ts +8 -0
- package/dist/shared/validation/options.js +90 -0
- package/dist/shared/validation/primitives.d.ts +14 -10
- package/dist/shared/validation/primitives.js +23 -25
- package/dist/shared/validation/ttl.d.ts +14 -7
- package/dist/shared/validation/ttl.js +29 -11
- package/dist/store/actions/get.d.ts +11 -3
- package/dist/store/actions/get.js +52 -14
- package/dist/store/actions/list-namespaces.d.ts +0 -1
- package/dist/store/actions/list-namespaces.js +10 -6
- package/dist/store/actions/put.d.ts +0 -1
- package/dist/store/actions/put.js +24 -13
- package/dist/store/actions/reconcile-vector-index.d.ts +3 -2
- package/dist/store/actions/reconcile-vector-index.js +2 -3
- package/dist/store/actions/search.d.ts +1 -2
- package/dist/store/actions/search.js +11 -38
- package/dist/store/internal/backend-search.d.ts +1 -2
- package/dist/store/internal/backend-search.js +12 -6
- package/dist/store/internal/batch-plan.d.ts +11 -0
- package/dist/store/internal/batch-plan.js +52 -0
- package/dist/store/internal/candidates.d.ts +23 -0
- package/dist/store/internal/candidates.js +94 -0
- package/dist/store/internal/filter.d.ts +0 -1
- package/dist/store/internal/filter.js +0 -1
- package/dist/store/internal/index-reconcile.d.ts +5 -6
- package/dist/store/internal/index-reconcile.js +25 -12
- package/dist/store/internal/index-sync.d.ts +0 -1
- package/dist/store/internal/index-sync.js +0 -1
- package/dist/store/internal/item-mapper.d.ts +5 -3
- package/dist/store/internal/item-mapper.js +14 -6
- package/dist/store/internal/keys.d.ts +0 -1
- package/dist/store/internal/keys.js +0 -1
- package/dist/store/internal/namespace-match.d.ts +0 -1
- package/dist/store/internal/namespace-match.js +0 -1
- package/dist/store/internal/option-validation.d.ts +13 -0
- package/dist/store/internal/option-validation.js +77 -0
- package/dist/store/internal/overwrite-swap.d.ts +0 -1
- package/dist/store/internal/overwrite-swap.js +10 -4
- package/dist/store/internal/persist.d.ts +3 -2
- package/dist/store/internal/persist.js +16 -11
- package/dist/store/internal/query.d.ts +6 -1
- package/dist/store/internal/query.js +17 -1
- package/dist/store/internal/ranker.d.ts +7 -2
- package/dist/store/internal/ranker.js +10 -2
- package/dist/store/internal/read-existing.d.ts +9 -5
- package/dist/store/internal/read-existing.js +22 -10
- package/dist/store/internal/score-direction.d.ts +0 -1
- package/dist/store/internal/score-direction.js +0 -1
- package/dist/store/internal/search-filter.d.ts +0 -1
- package/dist/store/internal/search-filter.js +0 -1
- package/dist/store/internal/semantic-search.d.ts +26 -5
- package/dist/store/internal/semantic-search.js +59 -19
- package/dist/store/internal/setup.d.ts +5 -13
- package/dist/store/internal/setup.js +11 -63
- package/dist/store/internal/text-path.d.ts +17 -0
- package/dist/store/internal/text-path.js +147 -0
- package/dist/store/internal/validation.d.ts +8 -3
- package/dist/store/internal/validation.js +21 -5
- package/dist/store/internal/write-verify.d.ts +15 -10
- package/dist/store/internal/write-verify.js +24 -21
- package/dist/store/store.d.ts +37 -9
- package/dist/store/store.js +52 -17
- package/dist/store/types.d.ts +6 -2
- package/dist/store/types.js +0 -1
- package/dist/store/vector-backend.d.ts +0 -1
- package/dist/store/vector-backend.js +0 -1
- package/package.json +21 -15
- package/dist/checkpointer/actions/delete-thread.d.ts.map +0 -1
- package/dist/checkpointer/actions/delete-thread.js.map +0 -1
- package/dist/checkpointer/actions/get-tuple.d.ts.map +0 -1
- package/dist/checkpointer/actions/get-tuple.js.map +0 -1
- package/dist/checkpointer/actions/list.d.ts.map +0 -1
- package/dist/checkpointer/actions/list.js.map +0 -1
- package/dist/checkpointer/actions/put-writes.d.ts.map +0 -1
- package/dist/checkpointer/actions/put-writes.js.map +0 -1
- package/dist/checkpointer/actions/put.d.ts.map +0 -1
- package/dist/checkpointer/actions/put.js.map +0 -1
- package/dist/checkpointer/internal/assemble.d.ts.map +0 -1
- package/dist/checkpointer/internal/assemble.js.map +0 -1
- package/dist/checkpointer/internal/configurable.d.ts.map +0 -1
- package/dist/checkpointer/internal/configurable.js.map +0 -1
- package/dist/checkpointer/internal/fetch.d.ts.map +0 -1
- package/dist/checkpointer/internal/fetch.js.map +0 -1
- package/dist/checkpointer/internal/filter-match.d.ts.map +0 -1
- package/dist/checkpointer/internal/filter-match.js.map +0 -1
- package/dist/checkpointer/internal/item-reader.d.ts.map +0 -1
- package/dist/checkpointer/internal/item-reader.js.map +0 -1
- package/dist/checkpointer/internal/item-writer.d.ts.map +0 -1
- package/dist/checkpointer/internal/item-writer.js.map +0 -1
- package/dist/checkpointer/internal/keys.d.ts.map +0 -1
- package/dist/checkpointer/internal/keys.js.map +0 -1
- package/dist/checkpointer/internal/query.d.ts.map +0 -1
- package/dist/checkpointer/internal/query.js.map +0 -1
- package/dist/checkpointer/internal/setup.d.ts.map +0 -1
- package/dist/checkpointer/internal/setup.js.map +0 -1
- package/dist/checkpointer/internal/special-write-cas.d.ts.map +0 -1
- package/dist/checkpointer/internal/special-write-cas.js.map +0 -1
- package/dist/checkpointer/internal/special-write-cleanup.d.ts.map +0 -1
- package/dist/checkpointer/internal/special-write-cleanup.js.map +0 -1
- package/dist/checkpointer/internal/special-write-verify.d.ts.map +0 -1
- package/dist/checkpointer/internal/special-write-verify.js.map +0 -1
- package/dist/checkpointer/internal/validation.d.ts.map +0 -1
- package/dist/checkpointer/internal/validation.js.map +0 -1
- package/dist/checkpointer/internal/write-guard.d.ts.map +0 -1
- package/dist/checkpointer/internal/write-guard.js.map +0 -1
- package/dist/checkpointer/internal/write-index.d.ts.map +0 -1
- package/dist/checkpointer/internal/write-index.js.map +0 -1
- package/dist/checkpointer/saver.d.ts.map +0 -1
- package/dist/checkpointer/saver.js.map +0 -1
- package/dist/checkpointer/types.d.ts.map +0 -1
- package/dist/checkpointer/types.js.map +0 -1
- package/dist/factory/factory.d.ts.map +0 -1
- package/dist/factory/factory.js.map +0 -1
- package/dist/history/actions/add-messages.d.ts.map +0 -1
- package/dist/history/actions/add-messages.js.map +0 -1
- package/dist/history/actions/clear.d.ts.map +0 -1
- package/dist/history/actions/clear.js.map +0 -1
- package/dist/history/actions/get-messages.d.ts.map +0 -1
- package/dist/history/actions/get-messages.js.map +0 -1
- package/dist/history/actions/list-sessions.d.ts.map +0 -1
- package/dist/history/actions/list-sessions.js.map +0 -1
- package/dist/history/actions/reconcile-count.d.ts.map +0 -1
- package/dist/history/actions/reconcile-count.js.map +0 -1
- package/dist/history/chat-message-history.d.ts.map +0 -1
- package/dist/history/chat-message-history.js.map +0 -1
- package/dist/history/internal/append-saga.d.ts.map +0 -1
- package/dist/history/internal/append-saga.js.map +0 -1
- package/dist/history/internal/compensation.d.ts.map +0 -1
- package/dist/history/internal/compensation.js.map +0 -1
- package/dist/history/internal/item-mapper.d.ts.map +0 -1
- package/dist/history/internal/item-mapper.js.map +0 -1
- package/dist/history/internal/keys.d.ts.map +0 -1
- package/dist/history/internal/keys.js.map +0 -1
- package/dist/history/internal/message-chunker.d.ts.map +0 -1
- package/dist/history/internal/message-chunker.js.map +0 -1
- package/dist/history/internal/message-transaction.d.ts.map +0 -1
- package/dist/history/internal/message-transaction.js.map +0 -1
- package/dist/history/internal/query.d.ts.map +0 -1
- package/dist/history/internal/query.js.map +0 -1
- package/dist/history/internal/session-count.d.ts.map +0 -1
- package/dist/history/internal/session-count.js.map +0 -1
- package/dist/history/internal/session-title.d.ts.map +0 -1
- package/dist/history/internal/session-title.js.map +0 -1
- package/dist/history/internal/session-update.d.ts.map +0 -1
- package/dist/history/internal/session-update.js.map +0 -1
- package/dist/history/internal/setup.d.ts.map +0 -1
- package/dist/history/internal/setup.js.map +0 -1
- package/dist/history/internal/title-generator.d.ts.map +0 -1
- package/dist/history/internal/title-generator.js.map +0 -1
- package/dist/history/internal/ttl-anchor.d.ts.map +0 -1
- package/dist/history/internal/ttl-anchor.js.map +0 -1
- package/dist/history/internal/validation.d.ts.map +0 -1
- package/dist/history/internal/validation.js.map +0 -1
- package/dist/history/session-adapter.d.ts.map +0 -1
- package/dist/history/session-adapter.js.map +0 -1
- package/dist/history/types.d.ts.map +0 -1
- package/dist/history/types.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/shared/clock.d.ts.map +0 -1
- package/dist/shared/clock.js.map +0 -1
- package/dist/shared/codec/codec.d.ts.map +0 -1
- package/dist/shared/codec/codec.js.map +0 -1
- package/dist/shared/codec/compression.d.ts.map +0 -1
- package/dist/shared/codec/compression.js.map +0 -1
- package/dist/shared/codec/descriptor-keys.d.ts.map +0 -1
- package/dist/shared/codec/descriptor-keys.js.map +0 -1
- package/dist/shared/codec/json-serde.d.ts.map +0 -1
- package/dist/shared/codec/json-serde.js.map +0 -1
- package/dist/shared/codec/s3/client.d.ts.map +0 -1
- package/dist/shared/codec/s3/client.js.map +0 -1
- package/dist/shared/codec/s3/config.d.ts.map +0 -1
- package/dist/shared/codec/s3/config.js.map +0 -1
- package/dist/shared/codec/s3/delete.d.ts.map +0 -1
- package/dist/shared/codec/s3/delete.js.map +0 -1
- package/dist/shared/codec/s3/lifecycle.d.ts.map +0 -1
- package/dist/shared/codec/s3/lifecycle.js.map +0 -1
- package/dist/shared/codec/s3/offloader.d.ts.map +0 -1
- package/dist/shared/codec/s3/offloader.js.map +0 -1
- package/dist/shared/codec/s3/orphans.d.ts.map +0 -1
- package/dist/shared/codec/s3/orphans.js.map +0 -1
- package/dist/shared/codec/s3/read-write.d.ts.map +0 -1
- package/dist/shared/codec/s3/read-write.js.map +0 -1
- package/dist/shared/codec/s3/retry.d.ts.map +0 -1
- package/dist/shared/codec/s3/retry.js.map +0 -1
- package/dist/shared/constants.d.ts.map +0 -1
- package/dist/shared/constants.js.map +0 -1
- package/dist/shared/dynamodb/backoff.d.ts.map +0 -1
- package/dist/shared/dynamodb/backoff.js.map +0 -1
- package/dist/shared/dynamodb/batch-write.d.ts.map +0 -1
- package/dist/shared/dynamodb/batch-write.js.map +0 -1
- package/dist/shared/dynamodb/cancellation.d.ts.map +0 -1
- package/dist/shared/dynamodb/cancellation.js.map +0 -1
- package/dist/shared/dynamodb/client.d.ts.map +0 -1
- package/dist/shared/dynamodb/client.js.map +0 -1
- package/dist/shared/dynamodb/conditional-put.d.ts.map +0 -1
- package/dist/shared/dynamodb/conditional-put.js.map +0 -1
- package/dist/shared/dynamodb/drain-unprocessed.d.ts.map +0 -1
- package/dist/shared/dynamodb/drain-unprocessed.js.map +0 -1
- package/dist/shared/dynamodb/paginate-core.d.ts.map +0 -1
- package/dist/shared/dynamodb/paginate-core.js.map +0 -1
- package/dist/shared/dynamodb/paginate.d.ts.map +0 -1
- package/dist/shared/dynamodb/paginate.js.map +0 -1
- package/dist/shared/dynamodb/partition-delete.d.ts.map +0 -1
- package/dist/shared/dynamodb/partition-delete.js.map +0 -1
- package/dist/shared/dynamodb/retry-classifier.d.ts.map +0 -1
- package/dist/shared/dynamodb/retry-classifier.js.map +0 -1
- package/dist/shared/dynamodb/retry.d.ts.map +0 -1
- package/dist/shared/dynamodb/retry.js.map +0 -1
- package/dist/shared/dynamodb/scan.d.ts.map +0 -1
- package/dist/shared/dynamodb/scan.js.map +0 -1
- package/dist/shared/dynamodb/types.d.ts.map +0 -1
- package/dist/shared/dynamodb/types.js.map +0 -1
- package/dist/shared/errors/base-error.d.ts.map +0 -1
- package/dist/shared/errors/base-error.js.map +0 -1
- package/dist/shared/errors/error-code.d.ts.map +0 -1
- package/dist/shared/errors/error-code.js.map +0 -1
- package/dist/shared/errors/errors.d.ts.map +0 -1
- package/dist/shared/errors/errors.js.map +0 -1
- package/dist/shared/errors/wrap-error.d.ts.map +0 -1
- package/dist/shared/errors/wrap-error.js.map +0 -1
- package/dist/shared/logging/logger.d.ts.map +0 -1
- package/dist/shared/logging/logger.js.map +0 -1
- package/dist/shared/logging/redaction-walk.d.ts.map +0 -1
- package/dist/shared/logging/redaction-walk.js.map +0 -1
- package/dist/shared/logging/redaction.d.ts.map +0 -1
- package/dist/shared/logging/redaction.js.map +0 -1
- package/dist/shared/logging/secret-patterns.d.ts.map +0 -1
- package/dist/shared/logging/secret-patterns.js.map +0 -1
- package/dist/shared/options.d.ts.map +0 -1
- package/dist/shared/options.js.map +0 -1
- package/dist/shared/ulid.d.ts.map +0 -1
- package/dist/shared/ulid.js.map +0 -1
- package/dist/shared/validation/primitives.d.ts.map +0 -1
- package/dist/shared/validation/primitives.js.map +0 -1
- package/dist/shared/validation/ttl.d.ts.map +0 -1
- package/dist/shared/validation/ttl.js.map +0 -1
- package/dist/store/actions/get.d.ts.map +0 -1
- package/dist/store/actions/get.js.map +0 -1
- package/dist/store/actions/list-namespaces.d.ts.map +0 -1
- package/dist/store/actions/list-namespaces.js.map +0 -1
- package/dist/store/actions/put.d.ts.map +0 -1
- package/dist/store/actions/put.js.map +0 -1
- package/dist/store/actions/reconcile-vector-index.d.ts.map +0 -1
- package/dist/store/actions/reconcile-vector-index.js.map +0 -1
- package/dist/store/actions/search.d.ts.map +0 -1
- package/dist/store/actions/search.js.map +0 -1
- package/dist/store/internal/backend-search.d.ts.map +0 -1
- package/dist/store/internal/backend-search.js.map +0 -1
- package/dist/store/internal/filter.d.ts.map +0 -1
- package/dist/store/internal/filter.js.map +0 -1
- package/dist/store/internal/index-reconcile.d.ts.map +0 -1
- package/dist/store/internal/index-reconcile.js.map +0 -1
- package/dist/store/internal/index-sync.d.ts.map +0 -1
- package/dist/store/internal/index-sync.js.map +0 -1
- package/dist/store/internal/item-mapper.d.ts.map +0 -1
- package/dist/store/internal/item-mapper.js.map +0 -1
- package/dist/store/internal/keys.d.ts.map +0 -1
- package/dist/store/internal/keys.js.map +0 -1
- package/dist/store/internal/namespace-match.d.ts.map +0 -1
- package/dist/store/internal/namespace-match.js.map +0 -1
- package/dist/store/internal/overwrite-swap.d.ts.map +0 -1
- package/dist/store/internal/overwrite-swap.js.map +0 -1
- package/dist/store/internal/persist.d.ts.map +0 -1
- package/dist/store/internal/persist.js.map +0 -1
- package/dist/store/internal/query.d.ts.map +0 -1
- package/dist/store/internal/query.js.map +0 -1
- package/dist/store/internal/ranker.d.ts.map +0 -1
- package/dist/store/internal/ranker.js.map +0 -1
- package/dist/store/internal/read-existing.d.ts.map +0 -1
- package/dist/store/internal/read-existing.js.map +0 -1
- package/dist/store/internal/score-direction.d.ts.map +0 -1
- package/dist/store/internal/score-direction.js.map +0 -1
- package/dist/store/internal/search-filter.d.ts.map +0 -1
- package/dist/store/internal/search-filter.js.map +0 -1
- package/dist/store/internal/semantic-search.d.ts.map +0 -1
- package/dist/store/internal/semantic-search.js.map +0 -1
- package/dist/store/internal/setup.d.ts.map +0 -1
- package/dist/store/internal/setup.js.map +0 -1
- package/dist/store/internal/validation.d.ts.map +0 -1
- package/dist/store/internal/validation.js.map +0 -1
- package/dist/store/internal/write-verify.d.ts.map +0 -1
- package/dist/store/internal/write-verify.js.map +0 -1
- package/dist/store/store.d.ts.map +0 -1
- package/dist/store/store.js.map +0 -1
- package/dist/store/types.d.ts.map +0 -1
- package/dist/store/types.js.map +0 -1
- package/dist/store/vector-backend.d.ts.map +0 -1
- package/dist/store/vector-backend.js.map +0 -1
package/README.md
CHANGED
|
@@ -32,7 +32,9 @@ Every adapter supports optional **gzip compression**, **S3 offloading** of paylo
|
|
|
32
32
|
- [Infrastructure setup](#infrastructure-setup)
|
|
33
33
|
- [IAM permissions](#iam-permissions)
|
|
34
34
|
- [Migrating from earlier versions](#migrating-from-earlier-versions)
|
|
35
|
+
- [Operations](#operations)
|
|
35
36
|
- [Testing](#testing)
|
|
37
|
+
- [Support and policies](#support-and-policies)
|
|
36
38
|
- [License](#license)
|
|
37
39
|
|
|
38
40
|
---
|
|
@@ -42,7 +44,8 @@ Every adapter supports optional **gzip compression**, **S3 offloading** of paylo
|
|
|
42
44
|
```bash
|
|
43
45
|
npm install @farukada/aws-langgraph-dynamodb-ts \
|
|
44
46
|
@aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb \
|
|
45
|
-
@langchain/core @langchain/langgraph
|
|
47
|
+
@langchain/core @langchain/langgraph-checkpoint
|
|
48
|
+
# plus @langchain/langgraph itself, which your application already depends on
|
|
46
49
|
```
|
|
47
50
|
|
|
48
51
|
Optional peer dependencies, installed only if you use the matching feature:
|
|
@@ -55,6 +58,15 @@ npm install @aws-sdk/client-s3
|
|
|
55
58
|
npm install @langchain/aws # e.g. Bedrock Titan embeddings
|
|
56
59
|
```
|
|
57
60
|
|
|
61
|
+
The build is CommonJS and works from both module systems:
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { DynamoDBSaver } from '@farukada/aws-langgraph-dynamodb-ts'; // ESM or TypeScript
|
|
65
|
+
const { DynamoDBSaver } = require('@farukada/aws-langgraph-dynamodb-ts'); // CommonJS
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Node 22 or later is required; the shipped declarations target TypeScript 5.x and later. **Bundling:** the optional `@aws-sdk/client-s3` peer is loaded lazily through a dynamic `import()`, so a bundler (esbuild, rollup, webpack) must either have it installed or mark `@aws-sdk/*` external — CDK's `NodejsFunction` does the latter by default, a bare esbuild build does not.
|
|
69
|
+
|
|
58
70
|
## Table schema
|
|
59
71
|
|
|
60
72
|
Every adapter uses the **same simple key schema**: a string partition key `PK`, a string sort key `SK`, and an optional Number `ttl` attribute for expiry. **A single table can back all three adapters**, or you can use a separate table per adapter — your choice via the `tableName` option.
|
|
@@ -65,6 +77,8 @@ Every adapter uses the **same simple key schema**: a string partition key `PK`,
|
|
|
65
77
|
| `SK` | String (RANGE) | sort key |
|
|
66
78
|
| `ttl` | Number | (optional) Unix-epoch-seconds expiry; enable DynamoDB TTL on this attribute |
|
|
67
79
|
|
|
80
|
+
Payloads live under one reserved attribute per row kind (`checkpoint`, `metadata`, `value`, `message`) as a **payload descriptor**: `{ schemaVersion: 1, location: 'INLINE' | 'S3', serdeType, compressed, bytes | s3Key }`. This shape is a compatibility contract: unknown fields are ignored, a missing `schemaVersion` reads as 1, and a higher `schemaVersion` or an unknown `location` is refused with a `ValidationError` (field `descriptor`) rather than misread. Offloaded S3 keys embed the row's identifiers base64url-encoded (`<keyPrefix><enc(thread_id)>/<enc(checkpoint_ns)>/…`), which is trivially reversible — treat S3 keys and `S3_OFFLOAD_FAILED` error context as identifier-bearing in your log-redaction policy.
|
|
81
|
+
|
|
68
82
|
How each adapter lays out keys (informational — you don't manage this):
|
|
69
83
|
|
|
70
84
|
- **Checkpointer** — `PK = CHKPT#<thread_id>`; `SK` = `META#<ns>#<checkpoint_id>` (metadata), `PAYLOAD#<ns>#<checkpoint_id>` (checkpoint), `WRITE#<ns>#<checkpoint_id>#<task>#<idx>#<channel>` (pending writes).
|
|
@@ -73,7 +87,7 @@ How each adapter lays out keys (informational — you don't manage this):
|
|
|
73
87
|
|
|
74
88
|
**Why the key spaces cannot collide.** Each adapter tags its partition key with its own prefix, and those three tags differ in their very first character, so no `CHKPT#…` can ever equal a `STORE#…` or `HIST#…` — whatever identifiers you pass. That matters because reusing one id across adapters (a "conversation id" used as both a `thread_id` and a `sessionId`) is an entirely ordinary design: without the tags it put unrelated adapters' rows in one partition, where `deleteThread()`/`history.clear()` would delete each other's data and identically-composed sort keys could silently overwrite one another.
|
|
75
89
|
|
|
76
|
-
Two further guards back that up, for a table holding hand-written rows or rows written before an upgrade: `deleteThread()`/`clear()` delete only rows whose sort key belongs to the calling adapter and log anything they leave in place, and every read narrows a row's shape before decoding it rather than trusting the key it was found at.
|
|
90
|
+
Two further guards back that up, for a table holding hand-written rows or rows written before an upgrade: `deleteThread()`/`clear()` delete only rows whose sort key belongs to the calling adapter and log anything they leave in place, and every read narrows a row's shape before decoding it rather than trusting the key it was found at. An offloaded payload's `s3Key` is bound the same way: before it is downloaded or deleted it must lie under the adapter's `keyPrefix` *and* the S3 path the row's own identifiers produce (`enc(thread_id)/…`, `enc(namespace…)/enc(key)`, `enc(sessionId)/…`), and a store row's `namespace`/`key` must agree with the partition and sort key it was found at — so a row planted in one partition can never make the library read or delete another tenant's object. A read of such a row fails with a `ValidationError` (field `s3Key`; chat history treats it as a corrupt message), and a delete skips the object with a warning.
|
|
77
91
|
|
|
78
92
|
## Quick start
|
|
79
93
|
|
|
@@ -133,7 +147,7 @@ await store.listNamespaces({ prefix: ['library'], maxDepth: 1 });
|
|
|
133
147
|
|
|
134
148
|
```typescript
|
|
135
149
|
import { DynamoDBChatMessageHistory } from '@farukada/aws-langgraph-dynamodb-ts';
|
|
136
|
-
import { HumanMessage } from '@langchain/core/messages';
|
|
150
|
+
import { AIMessage, HumanMessage } from '@langchain/core/messages';
|
|
137
151
|
|
|
138
152
|
const history = new DynamoDBChatMessageHistory({
|
|
139
153
|
tableName: 'langgraph',
|
|
@@ -141,8 +155,10 @@ const history = new DynamoDBChatMessageHistory({
|
|
|
141
155
|
});
|
|
142
156
|
|
|
143
157
|
await history.addMessages('session-1', [new HumanMessage('Hello!')]);
|
|
158
|
+
await history.addMessage('session-1', new AIMessage('Hi!'));
|
|
144
159
|
const messages = await history.getMessages('session-1');
|
|
145
|
-
const
|
|
160
|
+
const recent = await history.getMessages('session-1', { limit: 20 }); // newest 20, chronological
|
|
161
|
+
const sessions = await history.listSessions({ maxItems: 500 }); // [{ sessionId, title, messageCount, expiresAt?, ... }]
|
|
146
162
|
await history.clear('session-1');
|
|
147
163
|
```
|
|
148
164
|
|
|
@@ -159,6 +175,8 @@ const withHistory = new RunnableWithMessageHistory({
|
|
|
159
175
|
});
|
|
160
176
|
```
|
|
161
177
|
|
|
178
|
+
By default a read returns the whole session. `getMessages(sessionId, { limit, before })` returns a window instead — the newest `limit` messages, or only those appended before `before` — and `history.forSession(sessionId, { limit: 50 })` bounds what the adapter feeds the chain to the newest fifty, so a long-lived session does not grow the prompt without limit.
|
|
179
|
+
|
|
162
180
|
### Factory
|
|
163
181
|
|
|
164
182
|
`createAll` builds all three adapters on **one shared DynamoDB client** and returns a single `destroy()` that tears everything down.
|
|
@@ -179,6 +197,8 @@ const { saver, store, history, destroy } = factory.createAll({
|
|
|
179
197
|
destroy(); // closes the one shared client
|
|
180
198
|
```
|
|
181
199
|
|
|
200
|
+
Any section may be omitted (`createAll({ store: { tableName } })` returns `saver` and `history` as `undefined`), the factory's own `ttl`, `compression`, `s3`, `retry` and `logger` apply to every adapter unless a section overrides them, and `createSaver`, `createStore` and `createChatMessageHistory` build one adapter each on its own client with the same defaults.
|
|
201
|
+
|
|
182
202
|
## Options
|
|
183
203
|
|
|
184
204
|
All adapters share a common base. Provide **either** a prebuilt `client` (which the adapter will not own/close) **or** `clientConfig` (the adapter builds and owns the client).
|
|
@@ -186,10 +206,11 @@ All adapters share a common base. Provide **either** a prebuilt `client` (which
|
|
|
186
206
|
| Option | Type | Applies to | Notes |
|
|
187
207
|
| --- | --- | --- | --- |
|
|
188
208
|
| `tableName` | `string` | all | **required** |
|
|
189
|
-
| `client` | `DynamoDBDocument` | all | reuse an existing client; not closed by `destroy()` |
|
|
209
|
+
| `client` | `DynamoDBDocument` | all | reuse an existing client; not closed by `destroy()`. Construct it with `maxAttempts: 1` (`DynamoDBDocument.from(new DynamoDBClient({ maxAttempts: 1, … }))`): the SDK's own retries are not disabled on an injected client and would stack inside the library's retry budget — a `warn` is logged at construction when they would |
|
|
190
210
|
| `clientConfig` | `DynamoDBClientConfig` | all | used to build a client when `client` is omitted |
|
|
191
|
-
| `ttl` | `{ days: number }` \| `{ seconds: number }` | all | expiry written to the `ttl` attribute |
|
|
211
|
+
| `ttl` | `{ days: number }` \| `{ seconds: number }` | all | expiry written to the `ttl` attribute; one form only, capped at five years |
|
|
192
212
|
| `logger` | `Logger` | all | per-instance logger (default: silent) |
|
|
213
|
+
| `retry` | `{ maxAttempts?, baseDelayMs?, maxDelayMs? }` | all | retry budget and backoff for every DynamoDB call (default 5 attempts, 100 ms base, 5 s cap — see [Retries and backoff](#retries-and-backoff)) |
|
|
193
214
|
| `compression` | `CompressionConfig` | all | `{ enabled, minSizeBytes?, level?, maxDecompressedBytes? }` |
|
|
194
215
|
| `s3` | `S3OffloadConfig` | all | offload large payloads to S3 (see below) |
|
|
195
216
|
| `serde` | `SerializerProtocol` | all | serializer override (checkpointer defaults to LangGraph's; store/history to JSON) |
|
|
@@ -197,47 +218,84 @@ All adapters share a common base. Provide **either** a prebuilt `client` (which
|
|
|
197
218
|
| `index` | `IndexConfig` | store only | `{ dims, embeddings, fields? }` for semantic search |
|
|
198
219
|
| `vectorBackend` | `VectorBackend` | store only | delegate similarity search to an external index; DynamoDB keeps the canonical item. **Requires `index`** — constructing a store with one and not the other throws |
|
|
199
220
|
| `maxSearchCandidates` | `number` | store only | cap for the in-DB ranker before it errors (default 1000) |
|
|
200
|
-
| `maxScanItems` | `number` | store only | cap on
|
|
221
|
+
| `maxScanItems` | `number` | store only | cap on rows read for one call before it errors (default 10000; counts rows, not namespaces). Gates a plain `search()` page only when the page cannot be filled from fewer rows, semantic candidate collection, `listNamespaces()` and `reconcileVectorIndex()` |
|
|
222
|
+
| `vectorScoreDirection` | `'relevance' \| 'distance'` | store only | the direction of the score a `vectorBackend` returns (default `relevance`, higher is better); `distance` negates and re-sorts so a distance-native backend ranks correctly; any other value throws at construction |
|
|
201
223
|
|
|
202
|
-
`S3OffloadConfig`: `{ bucketName, keyPrefix?, thresholdBytes?, serverSideEncryption?, sseKmsKeyId?, clientConfig? }`.
|
|
224
|
+
`S3OffloadConfig`: `{ bucketName, keyPrefix?, thresholdBytes?, serverSideEncryption?, sseKmsKeyId?, maxDownloadBytes?, clientConfig? }`. `clientConfig` takes an `S3ClientConfig`; it is typed structurally (`S3ClientConfigLike`), so the shipped declarations compile whether or not `@aws-sdk/client-s3` is installed. When `clientConfig.region` is omitted here, the S3 client inherits the adapter's DynamoDB `clientConfig.region` (the S3 SDK does not follow region redirects, so a cross-region bucket otherwise fails with `PermanentRedirect`). `maxDownloadBytes` (default 50 MiB) caps the size of an offloaded object the adapter will buffer from S3 — checked against `ContentLength` before the body is read, and while streaming when the length is unknown — so together with `maxDecompressedBytes` no single payload can claim more memory than you allow. Defaults: `thresholdBytes` 350 KB, `serverSideEncryption` `AES256` (set `'aws:kms'` plus `sseKmsKeyId` for a customer key), `maxDownloadBytes` 50 MiB, and a per-adapter `keyPrefix` under `langgraph-checkpoints/`.
|
|
203
225
|
|
|
204
|
-
When `keyPrefix` is omitted, each adapter defaults to its own sub-prefix under the shared base (`langgraph-checkpoints/store/`, `langgraph-checkpoints/checkpointer/`, `langgraph-checkpoints/history/`) so that multiple adapters can safely share one bucket — their offloaded object keys and `ensureS3LifecycleRule()` TTL rules never collide. An explicit `keyPrefix` is always honored verbatim, including across adapters if you want them to share one; at that point avoiding a lifecycle-rule collision (e.g. by giving them the same TTL) is your responsibility, same as with any other explicit override.
|
|
226
|
+
When `keyPrefix` is omitted, each adapter defaults to its own sub-prefix under the shared base (`langgraph-checkpoints/store/`, `langgraph-checkpoints/checkpointer/`, `langgraph-checkpoints/history/`) so that multiple adapters can safely share one bucket — their offloaded object keys and `ensureS3LifecycleRule()` TTL rules never collide. An explicit `keyPrefix` is always honored verbatim, including across adapters if you want them to share one; at that point avoiding a lifecycle-rule collision (e.g. by giving them the same TTL) is your responsibility, same as with any other explicit override. A `keyPrefix` must be a non-empty path ending in `/`: it is also the lifecycle rule's `Filter.Prefix`, so an empty or root prefix would expire the whole bucket and a slash-less one would match sibling prefixes — both are rejected at construction and again by `ensureS3LifecycleRule()`.
|
|
205
227
|
|
|
206
228
|
## Features
|
|
207
229
|
|
|
208
|
-
**Gzip compression** — set `compression: { enabled: true }`. Payloads at or above `minSizeBytes` (default 1 KB) are gzipped transparently;
|
|
230
|
+
**Gzip compression** — set `compression: { enabled: true }`. Payloads at or above `minSizeBytes` (default 1 KB) are gzipped transparently; the stored descriptor records whether a payload was compressed, so reads never infer it from the bytes, and decompression is guarded against decompression-bomb expansion (`maxDecompressedBytes`, default 50 MiB).
|
|
209
231
|
|
|
210
|
-
**S3 offloading** — set `s3: { bucketName }`. Any serialized payload at or above `thresholdBytes` (default 350 KB) is written to S3, with only a reference stored in DynamoDB
|
|
232
|
+
**S3 offloading** — set `s3: { bucketName }`. Any serialized payload at or above `thresholdBytes` (default 350 KB) is written to S3, with only a reference stored in DynamoDB. Only the payload counts toward the threshold: the store's inline embedding (about 10 bytes per dimension, so ~10 KB at 1024 dims and ~45 KB at 4096) is stored on the same item, so keep `thresholdBytes` plus the embedding's size under DynamoDB's 400 KB item limit or the put fails with a raw `ValidationException`; reads rehydrate transparently. Requires the optional `@aws-sdk/client-s3` peer: constructing an adapter with `s3` starts loading it, and a missing package fails the first S3 operation with a `ValidationError` naming the install command — bundlers must keep it installed or external. Deleting a checkpoint thread / chat session also best-effort deletes its offloaded objects. When a `ttl` is also configured, call `ensureS3LifecycleRule()` once (e.g. during deployment) to best-effort install a matching S3 lifecycle expiration rule (logged, never fatal) — this is opt-in rather than automatic, since it requires the broader `s3:PutLifecycleConfiguration` bucket-level permission and is not safe to fire on every adapter construction. If you configure `ttl` + `s3` but never call it, nothing reclaims objects that best-effort cleanup misses — they stay in the bucket until you remove them or add a lifecycle rule yourself. Both the store's concurrent-`put` overwrite race and the checkpointer's *special*-write overwrite race (`__error__`, `__interrupt__`, `__resume__`, `__scheduled__`) are now **prevented** by a compare-and-swap: each overwrite pins the previous descriptor it observed and re-reads on rejection, so it deletes exactly the payload it actually superseded instead of racing another writer for the same one. A leak from either path is now possible only in these residual cases, still backstopped by `ensureS3LifecycleRule()`: the bounded compare-and-swap (3 attempts) is exhausted under pathological contention, which falls back to an unconditional overwrite and logs a `warn`; a best-effort delete genuinely fails; or one double-fault interleaving — a write that loses the swap and then exhausts its transient-error retries on an attempt that actually landed — leaves cleanup targeting the stale descriptor rather than the one it truly superseded, orphaning one object (it never deletes a live object). Separately, and unchanged by any of the above, the checkpointer's *regular* (non-special) writes still resolve a genuine race first-write-wins with no compare-and-swap, so the loser's own upload there remains an orphan reclaimed only by best-effort cleanup and `ensureS3LifecycleRule()`. Likewise, a store `delete` whose acknowledgement is lost after the row was removed cannot learn which object that row referenced — the row is gone and its `ReturnValues` travelled with the lost response — so that one object is left to the lifecycle rule too.
|
|
211
233
|
|
|
212
|
-
**TTL expiry** — set `ttl: { days }` or `ttl: { seconds }`. The `ttl` attribute is written as a Unix-epoch-seconds timestamp; enable DynamoDB TTL on the `ttl` attribute for automatic deletion. Chat history anchors a single **uniform whole-conversation TTL** on the session's metadata row, shared by every message: normally it's set once, at session creation, via `if_not_exists`; but if the previously-stored anchor is ever found missing or already expired (DynamoDB's own TTL sweep can lag up to ~48h), the next append heals it with a plain overwrite instead of staying stuck. Every message written at any point in time shares whatever the current anchor is; expired messages are also filtered out on read. If the append that triggers a stale-anchor heal is itself later rolled back (a later chunk in the same call failed), the healed ttl is not reverted — the session simply keeps the fresher, never-shorter expiry rather than risk regressing a value a concurrent legitimate extension may have since written; this is a deliberate, self-healing tradeoff, not a bug.
|
|
234
|
+
**TTL expiry** — set `ttl: { days }` or `ttl: { seconds }`. The `ttl` attribute is written as a Unix-epoch-seconds timestamp; enable DynamoDB TTL on the `ttl` attribute for automatic deletion. Every adapter filters rows past their `ttl` on read — `get`/`search`/`listNamespaces` in the store, `getTuple`/`list` in the checkpointer, `getMessages`/`listSessions` in chat history — so nothing expired comes back during DynamoDB's sweep lag. For the checkpointer that means a thread whose head expired reads as its newest *live* checkpoint (or as empty), older checkpoints can expire while the head lives (so `parentConfig` may point at a checkpoint that is gone, which LangGraph's resume path does not need), and a swept payload reads as "no checkpoint" only for an already-expired head. Chat history anchors a single **uniform whole-conversation TTL** on the session's metadata row, shared by every message: normally it's set once, at session creation, via `if_not_exists`; but if the previously-stored anchor is ever found missing or already expired (DynamoDB's own TTL sweep can lag up to ~48h), the next append heals it with a plain overwrite instead of staying stuck. Every message written at any point in time shares whatever the current anchor is; expired messages are also filtered out on read. If the append that triggers a stale-anchor heal is itself later rolled back (a later chunk in the same call failed), the healed ttl is not reverted — the session simply keeps the fresher, never-shorter expiry rather than risk regressing a value a concurrent legitimate extension may have since written; this is a deliberate, self-healing tradeoff, not a bug. Turning `ttl` on for a chat-history table that already holds sessions stamps the anchor and every *new* message only; message rows written before that keep no `ttl`, outlive their session row, and still come back from `getMessages` — clear those sessions or backfill a `ttl` onto their rows when enabling expiry retroactively.
|
|
213
235
|
|
|
214
|
-
**Plain (metadata) search** (store) — a `search()` call with no `query` (or with a `query` but no `index`/`vectorBackend` configured)
|
|
236
|
+
**Plain (metadata) search** (store) — a `search()` call with no `query` (or with a `query` but no `index`/`vectorBackend` configured) reads rows under the `namespacePrefix` and decodes them in batches of 8 — applying `filter` in-process — until `offset + limit` matching items are in hand, then stops: the page is the complete answer, so a namespace far larger than the page costs neither a full decode nor a `ResultTruncatedError`. Only a page that cannot be filled from fewer rows is bounded by `maxScanItems` (default 10,000; exceeding it throws rather than silently returning a partial result). This is a different cap from `maxSearchCandidates` below: `maxScanItems` gates rows read, `maxSearchCandidates` gates the in-DB semantic ranker. For namespaces that routinely exceed the default, prefer a `vectorBackend` or a narrower `namespacePrefix` over raising the cap indefinitely.
|
|
215
237
|
|
|
216
|
-
**Semantic search** (store) — provide `index` with a LangChain `Embeddings` implementation. On `put`, the configured `fields` are embedded; on `search` with a `query`, results are ranked by cosine similarity. By default the embedding is stored on the item and ranking happens in-process over the scoped candidate set (bounded by `maxSearchCandidates`, default 1000 — exceeding it throws, steering you to an external index). For large corpora, pass a `vectorBackend`: the embedding is sent there instead, similarity search is delegated to it, and DynamoDB still holds the canonical item. Per-item indexing can be overridden via the `index` argument to `put` (`false` to skip, or a `string[]` of fields).
|
|
238
|
+
**Semantic search** (store) — provide `index` with a LangChain `Embeddings` implementation. On `put`, the configured `fields` are embedded; on `search` with a `query`, results are ranked by cosine similarity. By default the embedding is stored on the item and ranking happens in-process over the scoped candidate set (bounded by `maxSearchCandidates`, default 1000 — exceeding it throws a `ValidationError` as soon as more rows than that exist under the prefix, before any row is decoded or the query embedded, steering you to an external index). A `vectorBackend` search that reaches `maxSearchCandidates` while its `filter` has left fewer than `offset + limit` matches throws the same error instead of returning a silently short page. For large corpora, pass a `vectorBackend`: the embedding is sent there instead, similarity search is delegated to it, and DynamoDB still holds the canonical item. Per-item indexing can be overridden via the `index` argument to `put` (`false` to skip, or a `string[]` of fields).
|
|
217
239
|
|
|
218
240
|
**Vector index consistency** — when a `vectorBackend` is configured, **DynamoDB holds the canonical item** and the backend is a rebuildable index. After each canonical write the embedding is synced to the backend best-effort: a failure is logged (not thrown), so a backend hiccup never fails an otherwise-successful `put`/`delete`. To repair drift, call `store.reconcileVectorIndex(namespacePrefix)` — it re-pushes every live embedding and, when the backend implements the optional `listKeys`, prunes vectors with no canonical item; it returns `{ upserted, pruned }`. Run it when the namespace is idle. Caveats: reconciliation re-embeds with the store's **configured** index fields, so per-`put` field overrides are not reproduced; prune happens only when `listKeys` is implemented (otherwise reconcile re-pushes only and logs that prune was skipped); the prefix must be a non-empty namespace.
|
|
219
241
|
|
|
242
|
+
**Checkpointer semantics** — `put()` of an existing `checkpoint_id` is last-writer-wins, as in the reference savers: the transaction is unconditional, so two processes writing the same id keep whichever landed last, and the loser's offloaded objects wait for the lifecycle rule. `putWrites` issues one guarded `PutItem` per write, all in parallel, so a `Send` fan-out of a thousand branches is a thousand concurrent puts (fine on on-demand tables; size provisioned capacity accordingly). `deleteThread()` reads the partition once and deletes what it saw: a graph still running on the thread can leave fresh rows behind or recreate one, so call it when the thread is quiescent, and a delete that fails part-way leaves the objects of its already-deleted rows to the lifecycle rule. `list()` without a `thread_id` scans the whole table, like the reference savers.
|
|
243
|
+
|
|
244
|
+
**Chat history semantics** — message order is the write order of one adapter instance (its ULIDs are strictly monotonic even within a millisecond); across instances or processes it is the writers' wall clocks at millisecond precision, so a process whose clock lags can sort a later turn before an earlier one. The default `serde` is plain JSON: a `Uint8Array`/`Buffer` inside a message (a `ToolMessage.artifact`, say) reads back as an index-keyed object and a `Date` as a string — pass `serde: new JsonPlusSerializer()` from `@langchain/langgraph-checkpoint` for binary and `Date` fidelity. A batch over 99 messages or 3.5 MB is committed in chunks and is atomic from the writer's perspective only: a concurrent reader can see the first chunks before the append settles, and a rolled-back append still bumps the session's `updatedAt`. Under heavy contention on one session an append can spend up to about 61 seconds per chunk in retries (18 attempts, 5 s cap) — three times that when an injected client keeps the SDK's own retries. `clear()` has the same single-pass, quiescent-session caveat as `deleteThread()`.
|
|
245
|
+
|
|
246
|
+
**Differences from `InMemoryStore`** — the store follows the reference semantics with these deliberate exceptions: one embedding per item (the configured fields are joined and embedded once, where the reference embeds each field and ranks by the best); `$gt`/`$gte`/`$lt`/`$lte` never coerce types (`'10'` does not match `{ $gt: 5 }`), `$eq`/`$ne`/`$in`/`$nin` use deep equality, and an empty field condition `{}` matches nothing; results come back in key order, not insertion order; and the per-item `index` argument of `put` is honoured only on direct `DynamoDBStore` calls — LangGraph's `AsyncBatchedStore`, which wraps the store inside a graph, does not forward it.
|
|
247
|
+
|
|
220
248
|
**Strong consistency** — checkpointer read-your-writes (`getTuple`) and every `store.get` use `ConsistentRead`, so a value written and immediately read back is never served a stale replica. Bulk reads (`list`, `listNamespaces`, `listSessions`) stay eventually consistent for lower cost.
|
|
221
249
|
|
|
250
|
+
## Retries and backoff
|
|
251
|
+
|
|
252
|
+
Every DynamoDB call the library makes runs inside its own retry layer, and that layer is the only one: clients the library constructs disable the SDK's retries (`maxAttempts: 1`), so the numbers below are exact. `list()` without a `checkpoint_ns` covers every namespace of the thread (rows come grouped by namespace, newest first within each); with an explicit namespace, `before` is applied in the key condition so newer rows are never read, and a `checkpoint_id` is fetched directly instead of scanning. An injected `client` that keeps SDK retries stacks them inside each attempt — construct it with `maxAttempts: 1` (a `warn` is logged at construction otherwise).
|
|
253
|
+
|
|
254
|
+
- **What is retried** — throttling and capacity errors, transaction conflicts, request timeouts, HTTP 429/5xx responses (including ones the SDK cannot map to a modeled exception), errors carrying the SDK's `$retryable` trait, and Node socket errors. Everything else — `ValidationException`, `ConditionalCheckFailedException`, `ResourceNotFoundException`, `AccessDeniedException`, a `TransactionCanceledException` with a permanent reason — is thrown on the first attempt.
|
|
255
|
+
- **Schedule** — `retry.maxAttempts` (default 5) attempts with full-jitter exponential backoff from `retry.baseDelayMs` (default 100 ms), doubling per attempt and capped at `retry.maxDelayMs` (default 5 s): about 1.5 s worst case and 0.75 s expected before `RetryExhaustedError`. `addMessages` never uses fewer than 18 attempts (about 61 s worst case), because every concurrent append to one session contends on the same metadata row. `BatchWriteItem` `UnprocessedItems` are re-submitted for up to 10 rounds with the same backoff.
|
|
256
|
+
- **Visibility** — every retry is logged at `debug` with the attempt number, the delay about to be slept and the error name; `RetryExhaustedError` carries the last error as `cause` (with the SDK's `$metadata.requestId`) and `context.attempts`.
|
|
257
|
+
|
|
222
258
|
## Error handling
|
|
223
259
|
|
|
224
|
-
|
|
260
|
+
Every error the library throws extends `DynamoDBLangGraphError` and carries a stable `code` from the `ErrorCode` enum, a structured `context` (`tableName`, `operation`, `field`, `key`, `attempts` — identifiers and counts, never a payload) and a native `cause` chain. Raw AWS SDK errors never escape a public method: each one is wrapped in an `UpstreamError` (`code: 'UPSTREAM'`) that names the operation, keeps the SDK error as `cause`, and copies its `upstreamName`, `requestId` and `httpStatusCode` for logging and support tickets. Branch on `code` and detect library errors with the exported brand check rather than `instanceof`, which breaks when a bundler duplicates the package:
|
|
225
261
|
|
|
226
262
|
```typescript
|
|
227
|
-
import { ErrorCode,
|
|
263
|
+
import { ErrorCode, isDynamoDBLangGraphError } from '@farukada/aws-langgraph-dynamodb-ts';
|
|
228
264
|
|
|
229
265
|
try {
|
|
230
266
|
await store.put([''], 'k', { v: 1 });
|
|
231
267
|
} catch (error) {
|
|
232
|
-
if (error
|
|
233
|
-
|
|
268
|
+
if (isDynamoDBLangGraphError(error as Error)) {
|
|
269
|
+
if (error.code === ErrorCode.VALIDATION) { /* bad input: error.context.field names it */ }
|
|
270
|
+
if (error.code === ErrorCode.UPSTREAM) { /* an AWS error: error.cause, error.requestId */ }
|
|
234
271
|
}
|
|
235
272
|
}
|
|
236
273
|
```
|
|
237
274
|
|
|
238
|
-
`ErrorCode`
|
|
275
|
+
| `ErrorCode` | Class | Thrown by |
|
|
276
|
+
| --- | --- | --- |
|
|
277
|
+
| `VALIDATION` | `ValidationError` | every constructor for a bad option; every method for a bad identifier, key, window or value; S3 offload configured without the `@aws-sdk/client-s3` peer; a descriptor the reader cannot honour |
|
|
278
|
+
| `UPSTREAM` | `UpstreamError` | every public method, wrapping an AWS SDK error that was not retryable or that the library does not classify (`AccessDeniedException`, `ResourceNotFoundException`, `ValidationException`, …) |
|
|
279
|
+
| `RETRY_EXHAUSTED` | `RetryExhaustedError` | every DynamoDB call after `retry.maxAttempts` transient failures (`context.attempts`, the last error as `cause`) |
|
|
280
|
+
| `ABORTED` | `AbortError` | any cancellable method whose `AbortSignal` fired |
|
|
281
|
+
| `CONDITION_CONFLICT` | `ConflictError` | `history.reconcileMessageCount` when the session changed while it counted |
|
|
282
|
+
| `COMPENSATION_FAILED` | `CompensationFailedError` | `history.addMessages` / `addMessage` when a multi-chunk append failed and the rollback of the committed chunks failed too (`rollbackError`; run `reconcileMessageCount`) |
|
|
283
|
+
| `BATCH_WRITE_INCOMPLETE` | `BatchWriteAllIncompleteError` | `saver.deleteThread`, `history.clear` when a multi-chunk delete does not fully drain (`succeededCount`, `failedChunks`); `BatchWriteIncompleteError` is the per-chunk error inside it |
|
|
284
|
+
| `RESULT_TRUNCATED` | `ResultTruncatedError` | the paginated reads that keep rows in memory — `store.search`, `store.listNamespaces`, `store.reconcileVectorIndex`, `history.listSessions` — past `maxScanItems` / `maxItems` / `maxIterations` |
|
|
285
|
+
| `S3_OFFLOAD_FAILED` | `DynamoDBLangGraphError` | an upload, download or delete of an offloaded object that failed after the S3 retries, an object over `maxDownloadBytes`, or an object that no longer exists (`context.key`) |
|
|
286
|
+
| `COMPRESSION_LIMIT` | `DynamoDBLangGraphError` | a payload whose decompressed size would exceed `maxDecompressedBytes` |
|
|
287
|
+
|
|
288
|
+
**Cancellation** — every long-running method takes an `AbortSignal`: the checkpointer reads `RunnableConfig.signal` (which LangGraph propagates) on `getTuple`, `list`, `put` and `putWrites`, and `deleteThread`, `search`, `reconcileVectorIndex`, `getMessages`, `addMessages`, `addMessage`, `clear`, `listSessions` and `reconcileMessageCount` take a trailing `{ signal }`. A signal that is already aborted, or aborts while the library waits (a retry backoff, the next page of a paginated read), rejects the call with the library's `AbortError` (`code: 'ABORTED'`) whatever the abort reason was — the raw reason (a `DOMException` for a bare `controller.abort()`) is kept as `cause`. Cleanup and verification reads that run after a failure are not cancelled, so an abort never strands a live row pointing at a deleted object. Typed subclasses are exported where callers commonly branch: `ValidationError`, `ConflictError`, `RetryExhaustedError`, `BatchWriteIncompleteError`, `BatchWriteAllIncompleteError`, `ResultTruncatedError`, `AbortError`, `CompensationFailedError`.
|
|
289
|
+
|
|
290
|
+
`CompensationFailedError` is the one error that carries another: the append's original failure is `cause` and the rollback failure is `rollbackError`, which can itself be a `BatchWriteAllIncompleteError`. Check `rollbackError.code` (or `.name`) rather than `instanceof` for the same package-copy reason as above. The session's stored `messageCount` may be wrong at that point; `reconcileMessageCount` repairs it.
|
|
239
291
|
|
|
240
|
-
|
|
292
|
+
### Maintenance operations
|
|
293
|
+
|
|
294
|
+
Three methods repair or provision state and are meant for deployment scripts and operators, not request paths:
|
|
295
|
+
|
|
296
|
+
- **`ensureS3LifecycleRule()`** (all three adapters) — installs the S3 lifecycle expiration rule that matches the configured `ttl` under the adapter's key prefix, idempotently. It **throws** when the bucket cannot be read or written (`AccessDenied`, `NoSuchBucket`, throttling) — nothing is swallowed or merely logged — so call it once at deployment time, from a role that holds the two lifecycle actions, and treat a failure as a deployment failure. It is a no-op when `s3` or `ttl` is not configured.
|
|
297
|
+
- **`store.reconcileVectorIndex(namespacePrefix)`** — re-pushes every live item's embedding to the configured `vectorBackend` and, when the backend implements `listKeys`, prunes vectors whose item is gone; returns `{ upserted, pruned }`. Run it when the namespace is idle; it reads every row under the prefix (bounded by `maxScanItems`).
|
|
298
|
+
- **`history.reconcileMessageCount(sessionId)`** — recounts a session's live messages and rewrites the stored `messageCount`; returns the count. Run it after a `CompensationFailedError` or the `rollback failed` log event, when the session is idle; it throws `ConflictError` if an append lands while it counts.
|
|
241
299
|
|
|
242
300
|
## Logging
|
|
243
301
|
|
|
@@ -258,16 +316,53 @@ const store = new DynamoDBStore({ tableName: 'langgraph', logger: redactLogger(l
|
|
|
258
316
|
|
|
259
317
|
`redactLogger` wraps a logger so secret-looking fields (access keys, tokens, passwords, …) are replaced with `[REDACTED]` in structured log arguments. It also scans **string values, including an error's `message` and `stack`**, for recognisable credential shapes — AWS access key ids, `Bearer` tokens, JWTs, and `password=`/`token=` assignments — replacing just the matched substring so the text stays readable. Pass `extraKeys` to add field names and `extraValuePatterns` to add shapes. `redactSecrets` exposes the same redaction for arbitrary objects.
|
|
260
318
|
|
|
261
|
-
**What
|
|
319
|
+
**What is logged.** Identifiers and counts only: thread, namespace, checkpoint, session and task ids, store namespaces and keys, sort keys, channel names, S3 object keys, attempt and row counts, and the *name* of an underlying error. Never a payload, an embedding, a message body or a credential. `redactLogger` therefore matters most for the application logs around the library; it does not redact identifiers — pass `extraKeys: ['threadId', 'sessionId', 'namespace', 'key', 'sortKey', 's3Key']` when your deployment treats identifiers as personal data.
|
|
320
|
+
|
|
321
|
+
**Using pino or winston.** `Logger` methods take a message and then structured arguments — at most one plain object per call. winston and `console` accept that shape directly. pino treats a leading string as a format string and drops trailing objects, so merge the arguments into its first parameter:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import pino from 'pino';
|
|
325
|
+
import type { LogArgument, Logger } from '@farukada/aws-langgraph-dynamodb-ts';
|
|
326
|
+
|
|
327
|
+
const base = pino();
|
|
328
|
+
const fields = (args: LogArgument[]) =>
|
|
329
|
+
Object.assign({}, ...args.filter((arg) => typeof arg === 'object' && arg !== null));
|
|
330
|
+
const logger: Logger = {
|
|
331
|
+
info: (message, ...args) => base.info(fields(args), message),
|
|
332
|
+
warn: (message, ...args) => base.warn(fields(args), message),
|
|
333
|
+
error: (message, ...args) => base.error(fields(args), message),
|
|
334
|
+
debug: (message, ...args) => base.debug(fields(args), message),
|
|
335
|
+
};
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**What the library logs.** Nothing until a logger is injected. Every `error` and `warn` below is actionable; the table is generated from the code and a static test fails when a new event is added without a row. `debug` carries retries (`retrying after a transient error`, with the attempt, the delay and the error name), lost-response commits and duplicate pending writes that were skipped.
|
|
262
339
|
|
|
263
|
-
| Level |
|
|
264
|
-
| --- | --- |
|
|
265
|
-
| `
|
|
266
|
-
| `
|
|
267
|
-
| `warn` |
|
|
268
|
-
| `warn` |
|
|
269
|
-
| `warn` |
|
|
270
|
-
| `
|
|
340
|
+
| Level | Message | Fields | Meaning and what to do |
|
|
341
|
+
| --- | --- | --- | --- |
|
|
342
|
+
| `error` | `history.addMessages rollback failed; messageCount may have drifted` | `sessionId`, `committedChunks`, `message` | a multi-chunk append failed and its rollback failed too (`CompensationFailedError`); run `reconcileMessageCount` for the session once it is idle |
|
|
343
|
+
| `error` | `getMessages: skipped a corrupt message item` | `sessionId`, `sortKey`, `reason` | a message row could not be decoded (or its S3 object is gone) and was dropped under `onCorruptMessage: 'skip'`; inspect or delete the row |
|
|
344
|
+
| `warn` | `store.put: compare-and-swap exhausted; overwriting unconditionally` | `namespace`, `key`, `attempts` | three concurrent overwrites of one item; the put succeeded but one S3 object may be orphaned until the lifecycle rule sweeps it |
|
|
345
|
+
| `warn` | `putWrites: special-write compare-and-swap exhausted; overwriting unconditionally` | `sortKey`, `channel`, `attempts` | same, for an interrupt/resume/error write written concurrently for one task |
|
|
346
|
+
| `warn` | `Some orphaned S3 objects could not be deleted after` | `failedCount` | objects leaked after a failed write or a delete; `ensureS3LifecycleRule()` reclaims them, otherwise clean up by prefix |
|
|
347
|
+
| `warn` | `Failed to clean up orphaned S3 objects after` | `message`, `keys` | the cleanup itself failed after retries; same remedy |
|
|
348
|
+
| `warn` | `: refusing to delete an S3 object outside this row's scope` | `key` | a row referenced an object outside its own key path — a tampered or foreign row; the object was left alone, investigate the writer |
|
|
349
|
+
| `warn` | `store.put vector-index sync failed; reconcileVectorIndex will repair` | `namespace`, `key`, `message` | the `vectorBackend` rejected an upsert or delete; the canonical item is fine, run `reconcileVectorIndex` when convenient |
|
|
350
|
+
| `warn` | `injected DynamoDB client keeps the SDK's own retries` | `maxAttempts` | construct the injected client with `maxAttempts: 1` unless you want the SDK's retries to stack inside the library's budget |
|
|
351
|
+
| `warn` | `putWrites: write row held by an unexpected channel; write not persisted` | `sortKey`, `expected`, `found` | another writer holds this task's row for a different channel; only this library should write the key space |
|
|
352
|
+
| `warn` | `history.addMessages compensating committed chunks after a chunk failed` | `sessionId`, `committedChunks` | a large append is being rolled back; the caller receives the original error |
|
|
353
|
+
| `warn` | `list: scanned a large number of rows without the caller stopping` | `threadId`, `checkpointNs`, `scanned` | a `list()` walked over 10 000 rows; pass `limit` or narrow the filter |
|
|
354
|
+
| `warn` | `getTuple: a checkpoint carries very many pending-write rows; the read is complete but slow` | `threadId`, `checkpointId`, `rows` | over 10 000 pending writes on one checkpoint (a huge fan-out or many retried tasks); the read is correct |
|
|
355
|
+
| `warn` | `search: some candidates carry an embedding of a different dimension than the query` | `namespacePrefix`, `count` | items embedded with another model or `dims`; re-put them or run `reconcileVectorIndex` |
|
|
356
|
+
| `warn` | `search: vectorBackend returned ascending scores; VectorMatch.score must be a relevance` | `namespacePrefix` | the backend reports distances; set `vectorScoreDirection: 'distance'` |
|
|
357
|
+
| `warn` | `search: skipped an unusable vectorBackend match` | `namespace`, `key` | the backend returned a key this store cannot address; run `reconcileVectorIndex` |
|
|
358
|
+
| `warn` | `: left a foreign row in place` | `sortKey` | `deleteThread`/`clear` found a row another adapter owns in the partition and kept it |
|
|
359
|
+
| `warn` | `list: skipped a row that is not a checkpoint meta item` | `sortKey` | a foreign row shares the `META#` prefix on a shared table |
|
|
360
|
+
| `warn` | `getTuple: skipped a row that is not a checkpoint meta item` | `sortKey` | same, on the read-your-writes path |
|
|
361
|
+
| `warn` | `store.get: ignored a row that is not a store item` | `namespace`, `key` | a foreign row at a store key |
|
|
362
|
+
| `warn` | `reconcileVectorIndex: skipped a row that is not a store item` | `sortKey` | same, during reconciliation |
|
|
363
|
+
| `info` | `: deleted rows` | `deleted`, `skipped` | `deleteThread`/`clear` finished |
|
|
364
|
+
| `info` | `reconcileVectorIndex prune skipped: backend has no listKeys` | `prefix` | the backend cannot enumerate vectors, so stale ones were not pruned |
|
|
365
|
+
| `info` | `reconcileVectorIndex: kept a vector whose item reappeared` | `namespace`, `key` | an item was written while pruning; nothing to do |
|
|
271
366
|
|
|
272
367
|
## Infrastructure setup
|
|
273
368
|
|
|
@@ -314,20 +409,86 @@ resource "aws_dynamodb_table" "langgraph" {
|
|
|
314
409
|
|
|
315
410
|
## IAM permissions
|
|
316
411
|
|
|
317
|
-
|
|
412
|
+
Transactional writes are authorised by the item-level actions they carry — there is no `TransactWriteItems` action to grant — and the library never calls `BatchGetItem`. A least-privilege policy for one table:
|
|
413
|
+
|
|
414
|
+
```json
|
|
415
|
+
{
|
|
416
|
+
"Version": "2012-10-17",
|
|
417
|
+
"Statement": [
|
|
418
|
+
{
|
|
419
|
+
"Sid": "LangGraphItems",
|
|
420
|
+
"Effect": "Allow",
|
|
421
|
+
"Action": [
|
|
422
|
+
"dynamodb:GetItem",
|
|
423
|
+
"dynamodb:PutItem",
|
|
424
|
+
"dynamodb:UpdateItem",
|
|
425
|
+
"dynamodb:DeleteItem",
|
|
426
|
+
"dynamodb:Query",
|
|
427
|
+
"dynamodb:BatchWriteItem"
|
|
428
|
+
],
|
|
429
|
+
"Resource": "arn:aws:dynamodb:<region>:<account>:table/langgraph"
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"Sid": "LangGraphTableScans",
|
|
433
|
+
"Effect": "Allow",
|
|
434
|
+
"Action": ["dynamodb:Scan"],
|
|
435
|
+
"Resource": "arn:aws:dynamodb:<region>:<account>:table/langgraph"
|
|
436
|
+
}
|
|
437
|
+
]
|
|
438
|
+
}
|
|
439
|
+
```
|
|
318
440
|
|
|
441
|
+
`LangGraphTableScans` is needed only by the four table-wide reads — a rootless `store.search([])`, `store.listNamespaces()` without a concrete prefix root, `history.listSessions()`, and `saver.list()` without a `thread_id`. Every other operation is a `GetItem`, a partition `Query` or a write. Leave the statement out of any role that must not read across tenants (see below).
|
|
442
|
+
|
|
443
|
+
When S3 offloading is enabled, the role also needs the object actions under the configured key prefix (`langgraph-checkpoints/` by default; adjust when `keyPrefix` is set) and, only for the deployment-time `ensureS3LifecycleRule()` call, the two lifecycle actions on the bucket itself:
|
|
444
|
+
|
|
445
|
+
```json
|
|
446
|
+
{
|
|
447
|
+
"Sid": "LangGraphS3Objects",
|
|
448
|
+
"Effect": "Allow",
|
|
449
|
+
"Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
|
|
450
|
+
"Resource": "arn:aws:s3:::<bucket>/langgraph-checkpoints/*"
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"Sid": "LangGraphS3Lifecycle",
|
|
454
|
+
"Effect": "Allow",
|
|
455
|
+
"Action": ["s3:GetLifecycleConfiguration", "s3:PutLifecycleConfiguration"],
|
|
456
|
+
"Resource": "arn:aws:s3:::<bucket>"
|
|
457
|
+
}
|
|
319
458
|
```
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
459
|
+
|
|
460
|
+
With `serverSideEncryption: 'aws:kms'` the role additionally needs `kms:GenerateDataKey` (uploads) and `kms:Decrypt` (downloads) on the key. Semantic search through Bedrock embeddings needs `bedrock:InvokeModel` on the model. A static test (`test/static/iam-actions.test.ts`) keeps the DynamoDB and S3 actions above equal to the calls the code makes.
|
|
461
|
+
|
|
462
|
+
### Multi-tenant deployments
|
|
463
|
+
|
|
464
|
+
Isolation is anchored on the identifiers you choose. The library composes keys safely and never lets one adapter's rows collide with another's, but it does nothing to scope a read to a tenant: put the tenant first in every `thread_id`, `sessionId` and store namespace (`namespace[0]`), with a delimiter other than the reserved `#` (`acme/thread-7`, `acme:session-1`, `['acme', 'users', 'u1']`). Every checkpointer and chat-history operation, and every store operation with a concrete namespace prefix, then touches only that tenant's partitions.
|
|
465
|
+
|
|
466
|
+
Four operations are table scans and return **every tenant's** rows by construction: `store.search([])`, `store.listNamespaces()` without a prefix root, `history.listSessions()` and `saver.list()` without a `thread_id`. Treat them as administrative. `listSessions()` also returns each session's `title`, which is derived from the first human message — user content.
|
|
467
|
+
|
|
468
|
+
Tenancy can be enforced at the IAM layer with `dynamodb:LeadingKeys`, because every partition key starts with the adapter tag and then the identifier. A role for tenant `acme` grants the item actions with a key condition and omits `dynamodb:Scan` entirely (`LeadingKeys` does not apply to scans, so a role that may scan can read every tenant):
|
|
469
|
+
|
|
470
|
+
```json
|
|
471
|
+
{
|
|
472
|
+
"Sid": "LangGraphTenantAcme",
|
|
473
|
+
"Effect": "Allow",
|
|
474
|
+
"Action": [
|
|
475
|
+
"dynamodb:GetItem",
|
|
476
|
+
"dynamodb:PutItem",
|
|
477
|
+
"dynamodb:UpdateItem",
|
|
478
|
+
"dynamodb:DeleteItem",
|
|
479
|
+
"dynamodb:Query",
|
|
480
|
+
"dynamodb:BatchWriteItem"
|
|
481
|
+
],
|
|
482
|
+
"Resource": "arn:aws:dynamodb:<region>:<account>:table/langgraph",
|
|
483
|
+
"Condition": {
|
|
484
|
+
"ForAllValues:StringLike": {
|
|
485
|
+
"dynamodb:LeadingKeys": ["CHKPT#acme/*", "STORE#acme", "HIST#acme/*"]
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
328
489
|
```
|
|
329
490
|
|
|
330
|
-
|
|
491
|
+
For the store the tenant must be the whole first namespace element (`STORE#acme`), since the partition key is exactly `STORE#<namespace[0]>`; the checkpointer and history patterns match any identifier under the tenant prefix. Offloaded S3 objects can be scoped the same way with an object-key condition on `arn:aws:s3:::<bucket>/langgraph-checkpoints/<adapter>/<base64url tenant prefix>*`, or by giving each tenant its own `keyPrefix`.
|
|
331
492
|
|
|
332
493
|
## Migrating from earlier versions
|
|
333
494
|
|
|
@@ -378,15 +539,71 @@ unaffected.
|
|
|
378
539
|
- **One `ttl` option** — `{ days }` or `{ seconds }` — replaces `ttlDays`/`ttlSeconds`.
|
|
379
540
|
- **S3 config option renamed** `s3OffloadConfig` → `s3`.
|
|
380
541
|
- **Per-instance `logger` option** replaces the global `setGlobalLogger` singleton; default logging is now silent.
|
|
381
|
-
- **Unified error model** — all errors extend `
|
|
542
|
+
- **Unified error model** — all errors extend `DynamoDBLangGraphError` with an `ErrorCode`.
|
|
382
543
|
|
|
383
544
|
## Production notes
|
|
384
545
|
|
|
385
546
|
- **Sharing one table** across all three adapters is supported — adapter-tagged partition keys make the key spaces provably disjoint (see [Table schema](#table-schema)), and table-wide reads filter to their own items. Checkpointer, chat-history, and *scoped* store reads are all partition-scoped (`Query`/`GetItem`).
|
|
386
|
-
- **Scoped reads are `Query`s.** `store.search`/`store.listNamespaces` with a concrete namespace prefix and `history.getMessages` are native `Query`s. Only a rootless `store.search([])` / unprefixed `listNamespaces` and `
|
|
547
|
+
- **Scoped reads are `Query`s.** `store.search`/`store.listNamespaces` with a concrete namespace prefix and `history.getMessages` are native `Query`s. Only a rootless `store.search([])` / unprefixed `listNamespaces`, `history.listSessions` and a `saver.list()` called without a `thread_id` (which, like the reference savers, lists every thread in the table) fall back to `Scan` (cost scales with table size, and the result spans every tenant) — keep those rare or use a dedicated table. `listSessions` accepts an optional `{ maxIterations }` override for tables where non-session rows dominate the scan.
|
|
548
|
+
- **One S3 GET per offloaded payload per read.** Every offloaded row a read touches (`getTuple` pending writes, a plain `search()` applying its filter, `getMessages`) costs one S3 GET, and the returned bytes are decoded in memory. Reads decode up to 8 offloaded payloads at a time rather than one after another, but the request count is still linear in the offloaded row count — keep `thresholdBytes` high and compression on so that few payloads offload, and prefer a `vectorBackend` over the in-DB ranker for large semantic corpora.
|
|
387
549
|
- **Hot partitions.** The store's partition key is `STORE#<namespace[0]>` and chat history's is `HIST#<sessionId>` — the adapter tag is constant, so throughput still concentrates on the identifier you choose. A single partition tops out around ~1000 WCU / 3000 RCU, so avoid funneling very high write throughput through one tenant/session id; spread load across scope roots (e.g. include a tenant id as `namespace[0]`).
|
|
550
|
+
- **Identifier rules.** Every caller-supplied identifier (thread_id, checkpoint_ns, checkpoint_id, taskId, sessionId, store namespace elements and keys, pending-write channels) is validated before it reaches DynamoDB: it must be a non-blank string with no control characters and no reserved `#`, at most 1024 bytes of UTF-8 for the partition identifiers (`thread_id`, `sessionId`) and 256 bytes for every sort-key segment (an empty `checkpoint_ns` is legal, it is the root namespace). Composed keys are checked too: a store namespace + key, or a checkpointer pending-write key, may not exceed DynamoDB's 1024-byte sort-key cap, and an offloaded S3 object key may not exceed S3's 1024 bytes. A violation is a `ValidationError` whose `context.field` names the offending value, thrown before any request is sent.
|
|
388
551
|
- **Very large vector corpora** outgrow the in-DB ranker (`maxSearchCandidates`). Configure a `vectorBackend` (OpenSearch, pgvector, …) — the library keeps DynamoDB as the source of truth and only delegates similarity ranking.
|
|
389
|
-
- **TTL deletion timing** is governed by DynamoDB (typically within 48 h of expiry) and S3 lifecycle expiry is day-granular — the library writes the correct expiry timestamp (and filters expired chat messages on read) but does not guarantee instant deletion. The matching S3 lifecycle rule is not written automatically: it is installed only when you call `ensureS3LifecycleRule()`.
|
|
552
|
+
- **TTL deletion timing** is governed by DynamoDB (typically within 48 h of expiry) and S3 lifecycle expiry is day-granular — the library writes the correct expiry timestamp (and filters expired chat messages on read) but does not guarantee instant deletion. The matching S3 lifecycle rule is not written automatically: it is installed only when you call `ensureS3LifecycleRule()`. That rule expires objects `ceil(ttl in days) + 2` days after creation — the two-day margin covers DynamoDB's sweep lag so an object never disappears before its row — and also expires noncurrent versions after the same number of days, so a versioned bucket does not retain every superseded payload forever (on such buckets the library's best-effort deletes only add delete markers). `ensureS3LifecycleRule()` is a read-modify-write of the bucket's whole lifecycle configuration: call it sequentially across adapters and deployers, never concurrently.
|
|
553
|
+
|
|
554
|
+
## Operations
|
|
555
|
+
|
|
556
|
+
### Limits
|
|
557
|
+
|
|
558
|
+
| Limit | Value | Where it bites |
|
|
559
|
+
| --- | --- | --- |
|
|
560
|
+
| DynamoDB item size | 400 KB | a payload over `thresholdBytes` (default 350 KB) must offload to S3; without `s3` a serialized payload over 392 KB is refused with a `ValidationError` before the write |
|
|
561
|
+
| Partition identifiers (`thread_id`, `sessionId`) | 1024 bytes UTF-8 | `ValidationError` |
|
|
562
|
+
| Sort-key segments (`checkpoint_ns`, `checkpoint_id`, `taskId`, channel, store namespace element, store `key`) | 256 bytes each, 1024 bytes composed | `ValidationError` |
|
|
563
|
+
| S3 object key | 1024 bytes | identifiers are base64url-encoded into it, so long ids reach it first |
|
|
564
|
+
| `ttl` | 5 years | `ValidationError` at construction |
|
|
565
|
+
| Chat-history append transaction | 99 messages or 3.5 MB per chunk | larger batches are split into chunks with caller-observed atomicity |
|
|
566
|
+
| Delete batches | 25 rows per `BatchWriteItem`, `UnprocessedItems` re-driven up to 10 times | `BatchWriteAllIncompleteError` |
|
|
567
|
+
| Rows held in memory by a listing (`maxScanItems`, `listSessions({ maxItems })`) | 10 000 | `ResultTruncatedError` |
|
|
568
|
+
| Pages walked by a listing (`listSessions({ maxIterations })`) | 1000 | `ResultTruncatedError` |
|
|
569
|
+
| In-DB semantic candidates (`maxSearchCandidates`) | 1000 | `ValidationError` |
|
|
570
|
+
| Decompressed payload (`maxDecompressedBytes`) and buffered S3 object (`maxDownloadBytes`) | 50 MiB each | `COMPRESSION_LIMIT` / `S3_OFFLOAD_FAILED` |
|
|
571
|
+
| Retries per DynamoDB call (`retry.maxAttempts`) | 5 (about 1.5 s worst case); message appends 18 (about 61 s) | `RetryExhaustedError` |
|
|
572
|
+
| Offloaded payloads decoded concurrently by one read | 8 | latency, not an error |
|
|
573
|
+
|
|
574
|
+
### What each operation costs
|
|
575
|
+
|
|
576
|
+
Requests per call, before retries. "Consistent" reads are `ConsistentRead: true` (twice the read units of an eventually consistent read); S3 requests apply only to offloaded payloads.
|
|
577
|
+
|
|
578
|
+
| Operation | DynamoDB | S3 |
|
|
579
|
+
| --- | --- | --- |
|
|
580
|
+
| `saver.getTuple` | 1 consistent `GetItem` (by id) or `Query` (newest) for the META row, 1 consistent `GetItem` for the payload, 1 consistent `Query` for the pending writes; a pre-v4 checkpoint adds a `Query` of its parent's writes | 1 `GET` per offloaded payload, 8 at a time |
|
|
581
|
+
| `saver.put` | 1 `TransactWriteItems` (META + PAYLOAD); 1 consistent `GetItem` of the parent META when `newVersions` leaves channels to carry over; verification reads only after a failure | 1 `PUT` per offloaded payload |
|
|
582
|
+
| `saver.putWrites` | 1 guarded `PutItem` per write, all in parallel; with `s3` each special write adds 1 consistent `GetItem` and up to 3 compare-and-swap attempts | 1 `PUT` per offloaded write, `DELETE` of a superseded special write |
|
|
583
|
+
| `saver.list` | 1 eventually consistent `Query` per page (or `Scan` without a `thread_id`); per yielded tuple 1 `GetItem` and 1 `Query` for its writes | `GET` per offloaded payload and metadata |
|
|
584
|
+
| `saver.deleteThread`, `history.clear` | 1 consistent `Query` per page, 1 `BatchWriteItem` per 25 rows | 1 `DeleteObjects` per 1000 keys |
|
|
585
|
+
| `store.get` | 1 consistent `GetItem` | 1 `GET` |
|
|
586
|
+
| `store.put` | 1 consistent `GetItem` (previous descriptor and revision), 1 guarded `PutItem` (up to 3 attempts under contention, each re-reading from the rejection), plus the `vectorBackend` upsert | 1 `PUT`, then `DELETE` of the superseded object |
|
|
587
|
+
| `store.delete` | 1 `DeleteItem` returning the old row, plus the `vectorBackend` delete | `DELETE` of the removed object |
|
|
588
|
+
| `store.search` | 1 eventually consistent `Query` per page (`Scan` for `[]`), reading rows in batches of 8 until the page is full; a `query` adds one embedding call | 1 `GET` per offloaded candidate |
|
|
589
|
+
| `store.listNamespaces` | key-only `Query` (`Scan` without a prefix root) per page | none |
|
|
590
|
+
| `history.addMessages` | 1 consistent `GetItem` of the session row when `ttl` is set, then 1 `TransactWriteItems` per chunk (up to 99 messages plus the session update); a rollback costs 1 `BatchWriteItem` per 25 rows plus a session update | 1 `PUT` per offloaded message |
|
|
591
|
+
| `history.getMessages` | 1 consistent `Query` per page (newest-first with a page cap under `limit`) | 1 `GET` per offloaded message, 8 at a time |
|
|
592
|
+
| `history.listSessions` | 1 `Scan` per page | none |
|
|
593
|
+
| `history.reconcileMessageCount` | `Query` (`Select: COUNT`) per page, 1 guarded `UpdateItem` | none |
|
|
594
|
+
| `store.reconcileVectorIndex` | 1 `Query` per page, embedding calls in batches, backend upserts and deletes | `GET` per offloaded item |
|
|
595
|
+
|
|
596
|
+
### Monitoring
|
|
597
|
+
|
|
598
|
+
Alert on the two `error` events (a corrupt message row, a failed append rollback) and on the four `warn` events that name an orphan or an exhausted compare-and-swap (see [Logging](#logging)); count `RetryExhaustedError` and `UpstreamError` by `context.operation` and `httpStatusCode`. `RetryExhaustedError.context.attempts` and every `debug` retry line carry the SDK `requestId` of the last failure for AWS Support. Watch the table's `ThrottledRequests` and `ConsumedWriteCapacityUnits` per partition key prefix — the [hot-partition](#production-notes) note explains which identifier concentrates load.
|
|
599
|
+
|
|
600
|
+
### Lambda and other short-lived runtimes
|
|
601
|
+
|
|
602
|
+
Construct the adapters once at module scope (or one `DynamoDBFactory.createAll()`), reuse them across invocations, and pass a `client` you own if the function also uses DynamoDB elsewhere; `destroy()` is only needed when a process wants to release sockets before exit. Size the function timeout against the worst-case retry budgets above: a heavily contended chat append can take about a minute, and `retry.maxAttempts` / `retry.maxDelayMs` trade that ceiling against resilience to throttling. Every long-running method takes an `AbortSignal`, so a timeout can cancel cleanly (see [Error handling](#error-handling)).
|
|
603
|
+
|
|
604
|
+
### Multi-tenancy
|
|
605
|
+
|
|
606
|
+
See [Multi-tenant deployments](#multi-tenant-deployments) under IAM permissions for the identifier convention, the table-scan operations that are cross-tenant by construction, and the `dynamodb:LeadingKeys` policy.
|
|
390
607
|
|
|
391
608
|
## Testing
|
|
392
609
|
|
|
@@ -405,17 +622,33 @@ npm run test:integration # integration flows + LangGraph/LangChain contra
|
|
|
405
622
|
npm run test:integration:down
|
|
406
623
|
```
|
|
407
624
|
|
|
408
|
-
|
|
625
|
+
The real-AWS tier runs the same adapters against real DynamoDB, S3 and Bedrock. Every suite creates and tears down its own uniquely named table and bucket (`aws-langgraph-<suite>test-<uuid>`) in the account of the default credential chain; CI runs it nightly through OIDC, and it runs on demand:
|
|
409
626
|
|
|
410
627
|
```bash
|
|
411
|
-
|
|
412
|
-
node examples/verify-store.mjs # filters, semantic search, S3 offload, TTL
|
|
413
|
-
node examples/verify-history.mjs # per-message model, concurrency, RunnableWithMessageHistory agent
|
|
414
|
-
node examples/verify-factory.mjs # shared-client createAll across all three adapters
|
|
415
|
-
node examples/verify-agents.mjs # real LangGraph agents using the saver + store as memory
|
|
416
|
-
node examples/verify-edge-cases.mjs # filter operators, multi-page reads, compression+S3, scale
|
|
628
|
+
npm run test:aws # needs AWS credentials; AWS_REGION selects the region
|
|
417
629
|
```
|
|
418
630
|
|
|
631
|
+
The `examples/live-*.mjs` scripts are demos against real AWS, not a test tier: `live-checkpointer.mjs` runs a LangGraph agent across two saver instances and deletes its table afterwards; `live-agent.mjs`, `live-persist.mjs` and `live-store.mjs` leave their table in place so you can inspect the rows in the console. They read `AWS_REGION` (default `eu-west-1`) and `LANGGRAPH_DEMO_TABLE` (default `langgraph-saver-demo` / `langgraph-store-demo`), and `live-agent.mjs` needs a Bedrock model enabled in that region.
|
|
632
|
+
|
|
633
|
+
### What the suite does and does not prove
|
|
634
|
+
|
|
635
|
+
| Tier | Runs | Proves |
|
|
636
|
+
| --- | --- | --- |
|
|
637
|
+
| Unit, static guards, type locks, property tests (`npm test`) | every push and PR, three OSes × Node 22 and 24 | every code path (100 % coverage), the repository rules (file size, JSDoc-only comments, no `any`/`unknown`/`instanceof`, no re-exports, no import cycles, no dead error codes), the exact public export set and adapter signatures, the stated invariants (sort-key order, item-size estimate, write resolution, redaction, backoff) |
|
|
638
|
+
| Integration (`npm run test:integration`, DynamoDB Local) | every push and PR | end-to-end adapter flows and fault injection; the write races the compare-and-swap exists for, with an in-memory S3 in the loop; the DynamoDB semantics the unit mocks assume; parity with `InMemoryStore` and `InMemoryChatMessageHistory` under `RunnableWithMessageHistory`; a 30-way single-session append storm |
|
|
639
|
+
| Conformance (`npm run test:conformance`, DynamoDB Local) | every push and PR, against the declared floor and the latest `@langchain/langgraph-checkpoint` | a compiled LangGraph graph over the saver (interrupt/resume, subgraph namespaces, forks, history windows, crash-and-resume, `Send` fan-out) and LangChain's official checkpointer validation suite |
|
|
640
|
+
| Package smoke (`npm run test:package-smoke`) | every push and PR | the packed tarball installs and imports without the optional S3 peer, and its declarations type-check without it |
|
|
641
|
+
| Real AWS (`npm run test:aws`) | nightly through OIDC | S3 offload, lifecycle rules and the S3 error taxonomy against the real services; real 30-way append contention; Bedrock embeddings (skipped with a reason when the model is not enabled) |
|
|
642
|
+
|
|
643
|
+
Nothing in the suite provokes real throttling or `ProvisionedThroughputExceededException` (only its classification is tested), receives `UnprocessedItems` from a batch write (DynamoDB Local and on-demand tables never return them), observes DynamoDB's TTL sweep (only the stamped attribute is asserted), uses a versioned bucket, exercises a hot partition, or measures the write capacity the compare-and-swap fallback consumes. An injected `client` that keeps the SDK's own retries multiplies the library's attempt budget; the integration tier pins that count once and every adapter warns about it at construction.
|
|
644
|
+
|
|
645
|
+
## Support and policies
|
|
646
|
+
|
|
647
|
+
- [Stability and compatibility policy](docs/STABILITY.md) — what `1.x` promises for the API, the on-disk layout, error codes and peer ranges.
|
|
648
|
+
- [Security policy](SECURITY.md) — private reporting, response targets, what the library does and does not do.
|
|
649
|
+
- [Support](SUPPORT.md) — where to ask, what to include.
|
|
650
|
+
- [Contributing](CONTRIBUTING.md) — setup, the guards, the test tiers, the toolchain, commits and releases.
|
|
651
|
+
|
|
419
652
|
## License
|
|
420
653
|
|
|
421
654
|
MIT © [Faruk Ada](https://github.com/FarukAda)
|
|
@@ -5,5 +5,6 @@ import type { CheckpointerContext } from '../internal/setup';
|
|
|
5
5
|
* this adapter does not own are left in place and logged, so a shared-table
|
|
6
6
|
* partition holding a foreign row is never collaterally wiped.
|
|
7
7
|
*/
|
|
8
|
-
export declare function deleteThread(context: CheckpointerContext, threadId: string
|
|
9
|
-
|
|
8
|
+
export declare function deleteThread(context: CheckpointerContext, threadId: string, options?: {
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
}): Promise<void>;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.deleteThread = deleteThread;
|
|
4
4
|
const partition_delete_1 = require("../../shared/dynamodb/partition-delete");
|
|
5
|
+
const retry_policy_1 = require("../../shared/dynamodb/retry-policy");
|
|
5
6
|
const keys_1 = require("../internal/keys");
|
|
6
7
|
const query_1 = require("../internal/query");
|
|
7
8
|
const validation_1 = require("../internal/validation");
|
|
@@ -19,17 +20,19 @@ function descriptorsOf(row) {
|
|
|
19
20
|
* this adapter does not own are left in place and logged, so a shared-table
|
|
20
21
|
* partition holding a foreign row is never collaterally wiped.
|
|
21
22
|
*/
|
|
22
|
-
async function deleteThread(context, threadId) {
|
|
23
|
+
async function deleteThread(context, threadId, options = {}) {
|
|
23
24
|
(0, validation_1.validateThreadId)(threadId);
|
|
24
25
|
await (0, partition_delete_1.deletePartitionRows)({
|
|
25
26
|
client: context.client,
|
|
26
27
|
tableName: context.tableName,
|
|
27
28
|
params: (0, query_1.partitionQuery)(context.tableName, (0, keys_1.partitionKey)(threadId), { consistent: true }),
|
|
28
29
|
logger: context.logger,
|
|
30
|
+
retry: (0, retry_policy_1.retryFor)(context, options.signal),
|
|
31
|
+
signal: options.signal,
|
|
29
32
|
offloader: context.offloader,
|
|
30
33
|
operation: 'deleteThread',
|
|
31
34
|
ownsSortKey: keys_1.isCheckpointerSortKey,
|
|
32
35
|
descriptorsOf,
|
|
36
|
+
scope: [threadId],
|
|
33
37
|
});
|
|
34
38
|
}
|
|
35
|
-
//# sourceMappingURL=delete-thread.js.map
|
|
@@ -4,7 +4,8 @@ import type { CheckpointerContext } from '../internal/setup';
|
|
|
4
4
|
/**
|
|
5
5
|
* Load a checkpoint tuple: the target checkpoint (by id, or the newest in the
|
|
6
6
|
* namespace), its metadata, its pending writes, and a parent config when the
|
|
7
|
-
* checkpoint has a parent. Returns undefined when no matching checkpoint
|
|
7
|
+
* checkpoint has a parent. Returns undefined when no matching checkpoint
|
|
8
|
+
* exists — including for a config that names no thread at all, which the
|
|
9
|
+
* reference savers answer with "nothing" rather than an error.
|
|
8
10
|
*/
|
|
9
11
|
export declare function getCheckpointTuple(context: CheckpointerContext, config: RunnableConfig): Promise<CheckpointTuple | undefined>;
|
|
10
|
-
//# sourceMappingURL=get-tuple.d.ts.map
|