@farukada/aws-langgraph-dynamodb-ts 0.9.0 → 1.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1720 -154
- package/dist/backfill/backfill.d.ts +168 -0
- package/dist/backfill/backfill.js +393 -0
- package/dist/checkpointer/actions/delete-thread.d.ts +47 -6
- package/dist/checkpointer/actions/delete-thread.js +58 -21
- package/dist/checkpointer/actions/get-tuple.d.ts +29 -4
- package/dist/checkpointer/actions/get-tuple.js +44 -10
- package/dist/checkpointer/actions/list.d.ts +46 -4
- package/dist/checkpointer/actions/list.js +121 -66
- package/dist/checkpointer/actions/put-writes.d.ts +41 -9
- package/dist/checkpointer/actions/put-writes.js +62 -77
- package/dist/checkpointer/actions/put.d.ts +83 -4
- package/dist/checkpointer/actions/put.js +177 -25
- package/dist/checkpointer/internal/delta-history.d.ts +112 -0
- package/dist/checkpointer/internal/delta-history.js +252 -0
- package/dist/checkpointer/internal/listing.d.ts +149 -0
- package/dist/checkpointer/internal/listing.js +245 -0
- package/dist/checkpointer/internal/parse.d.ts +262 -0
- package/dist/checkpointer/internal/parse.js +372 -0
- package/dist/checkpointer/internal/pending-writes.d.ts +275 -0
- package/dist/checkpointer/internal/pending-writes.js +588 -0
- package/dist/checkpointer/internal/read.d.ts +130 -0
- package/dist/checkpointer/internal/read.js +264 -0
- package/dist/checkpointer/internal/rows.d.ts +571 -0
- package/dist/checkpointer/internal/rows.js +834 -0
- package/dist/checkpointer/internal/setup.d.ts +42 -19
- package/dist/checkpointer/internal/setup.js +65 -29
- package/dist/checkpointer/saver.d.ts +256 -16
- package/dist/checkpointer/saver.js +275 -29
- package/dist/checkpointer/types.d.ts +39 -39
- package/dist/checkpointer/types.js +10 -1
- package/dist/factory/factory.d.ts +134 -28
- package/dist/factory/factory.js +240 -21
- package/dist/factory/types.d.ts +76 -0
- package/dist/factory/types.js +10 -0
- package/dist/history/actions/add-messages.d.ts +31 -4
- package/dist/history/actions/add-messages.js +38 -58
- package/dist/history/actions/clear.d.ts +49 -6
- package/dist/history/actions/clear.js +66 -14
- package/dist/history/actions/get-messages.d.ts +54 -6
- package/dist/history/actions/get-messages.js +126 -43
- package/dist/history/actions/list-sessions.d.ts +52 -10
- package/dist/history/actions/list-sessions.js +139 -40
- package/dist/history/actions/reconcile-count.d.ts +42 -10
- package/dist/history/actions/reconcile-count.js +45 -45
- package/dist/history/chat-message-history.d.ts +220 -33
- package/dist/history/chat-message-history.js +240 -43
- package/dist/history/internal/append.d.ts +212 -0
- package/dist/history/internal/append.js +500 -0
- package/dist/history/internal/message-read.d.ts +84 -0
- package/dist/history/internal/message-read.js +204 -0
- package/dist/history/internal/parse.d.ts +153 -0
- package/dist/history/internal/parse.js +252 -0
- package/dist/history/internal/rows.d.ts +195 -0
- package/dist/history/internal/rows.js +250 -0
- package/dist/history/internal/session.d.ts +331 -0
- package/dist/history/internal/session.js +628 -0
- package/dist/history/internal/setup.d.ts +52 -17
- package/dist/history/internal/setup.js +92 -21
- package/dist/history/session-adapter.d.ts +102 -7
- package/dist/history/session-adapter.js +103 -9
- package/dist/history/types.d.ts +80 -29
- package/dist/history/types.js +10 -1
- package/dist/index.d.ts +42 -11
- package/dist/index.js +33 -12
- package/dist/shared/adapter.d.ts +135 -0
- package/dist/shared/adapter.js +143 -0
- package/dist/shared/clock.d.ts +51 -2
- package/dist/shared/clock.js +57 -2
- package/dist/shared/codec/codec.d.ts +288 -13
- package/dist/shared/codec/codec.js +416 -19
- package/dist/shared/codec/compression.d.ts +43 -7
- package/dist/shared/codec/compression.js +53 -13
- package/dist/shared/codec/json-serde.d.ts +76 -4
- package/dist/shared/codec/json-serde.js +181 -8
- package/dist/shared/codec/s3/client-types.d.ts +53 -0
- package/dist/shared/codec/s3/client-types.js +26 -0
- package/dist/shared/codec/s3/client.d.ts +43 -10
- package/dist/shared/codec/s3/client.js +82 -9
- package/dist/shared/codec/s3/config.d.ts +242 -11
- package/dist/shared/codec/s3/config.js +293 -11
- package/dist/shared/codec/s3/lifecycle.d.ts +164 -6
- package/dist/shared/codec/s3/lifecycle.js +335 -27
- package/dist/shared/codec/s3/offloader.d.ts +393 -18
- package/dist/shared/codec/s3/offloader.js +595 -37
- package/dist/shared/concurrency.d.ts +43 -0
- package/dist/shared/concurrency.js +78 -0
- package/dist/shared/dynamodb/abort.d.ts +47 -0
- package/dist/shared/dynamodb/abort.js +59 -0
- package/dist/shared/dynamodb/batch-write.d.ts +77 -14
- package/dist/shared/dynamodb/batch-write.js +146 -27
- package/dist/shared/dynamodb/cancellation.d.ts +121 -4
- package/dist/shared/dynamodb/cancellation.js +147 -3
- package/dist/shared/dynamodb/client.d.ts +162 -8
- package/dist/shared/dynamodb/client.js +153 -5
- package/dist/shared/dynamodb/idempotent-write.d.ts +551 -0
- package/dist/shared/dynamodb/idempotent-write.js +593 -0
- package/dist/shared/dynamodb/paginate.d.ts +105 -9
- package/dist/shared/dynamodb/paginate.js +175 -7
- package/dist/shared/dynamodb/partition-delete.d.ts +185 -14
- package/dist/shared/dynamodb/partition-delete.js +314 -44
- package/dist/shared/dynamodb/recency-index.d.ts +231 -0
- package/dist/shared/dynamodb/recency-index.js +377 -0
- package/dist/shared/dynamodb/retry.d.ts +276 -8
- package/dist/shared/dynamodb/retry.js +433 -23
- package/dist/shared/dynamodb/table-schema.d.ts +190 -0
- package/dist/shared/dynamodb/table-schema.js +209 -0
- package/dist/shared/errors/base-error.d.ts +184 -10
- package/dist/shared/errors/base-error.js +160 -14
- package/dist/shared/errors/boundary.d.ts +71 -0
- package/dist/shared/errors/boundary.js +143 -0
- package/dist/shared/errors/classify.d.ts +97 -0
- package/dist/shared/errors/classify.js +257 -0
- package/dist/shared/errors/error-code.d.ts +77 -2
- package/dist/shared/errors/error-code.js +83 -1
- package/dist/shared/errors/errors.d.ts +158 -59
- package/dist/shared/errors/errors.js +219 -92
- package/dist/shared/logging/logger.d.ts +69 -3
- package/dist/shared/logging/logger.js +97 -3
- package/dist/shared/logging/redaction.d.ts +92 -8
- package/dist/shared/logging/redaction.js +273 -17
- package/dist/shared/logging/secret-patterns.d.ts +149 -19
- package/dist/shared/logging/secret-patterns.js +188 -27
- package/dist/shared/logging/truncate.d.ts +197 -0
- package/dist/shared/logging/truncate.js +231 -0
- package/dist/shared/options.d.ts +59 -7
- package/dist/shared/options.js +9 -1
- package/dist/shared/ulid.d.ts +77 -7
- package/dist/shared/ulid.js +103 -8
- package/dist/shared/validation/collaborators.d.ts +141 -0
- package/dist/shared/validation/collaborators.js +188 -0
- package/dist/shared/validation/option-shape.d.ts +89 -0
- package/dist/shared/validation/option-shape.js +113 -0
- package/dist/shared/validation/options.d.ts +145 -0
- package/dist/shared/validation/options.js +328 -0
- package/dist/shared/validation/primitives.d.ts +288 -21
- package/dist/shared/validation/primitives.js +353 -50
- package/dist/shared/validation/ttl.d.ts +66 -10
- package/dist/shared/validation/ttl.js +113 -15
- package/dist/store/actions/list-namespaces.d.ts +76 -6
- package/dist/store/actions/list-namespaces.js +166 -24
- package/dist/store/actions/put.d.ts +33 -8
- package/dist/store/actions/put.js +53 -60
- package/dist/store/actions/reconcile-vector-index.d.ts +31 -10
- package/dist/store/actions/reconcile-vector-index.js +34 -15
- package/dist/store/actions/search.d.ts +34 -6
- package/dist/store/actions/search.js +56 -51
- package/dist/store/internal/batch-plan.d.ts +26 -0
- package/dist/store/internal/batch-plan.js +109 -0
- package/dist/store/internal/filter.d.ts +36 -3
- package/dist/store/internal/filter.js +66 -15
- package/dist/store/internal/get-item.d.ts +45 -0
- package/dist/store/internal/get-item.js +115 -0
- package/dist/store/internal/item-write.d.ts +230 -0
- package/dist/store/internal/item-write.js +463 -0
- package/dist/store/internal/parse.d.ts +225 -0
- package/dist/store/internal/parse.js +350 -0
- package/dist/store/internal/rows.d.ts +355 -0
- package/dist/store/internal/rows.js +447 -0
- package/dist/store/internal/semantic-search.d.ts +161 -6
- package/dist/store/internal/semantic-search.js +360 -18
- package/dist/store/internal/setup.d.ts +77 -20
- package/dist/store/internal/setup.js +178 -47
- package/dist/store/internal/table-search.d.ts +100 -0
- package/dist/store/internal/table-search.js +213 -0
- package/dist/store/internal/vector-index.d.ts +247 -0
- package/dist/store/internal/vector-index.js +546 -0
- package/dist/store/store.d.ts +270 -17
- package/dist/store/store.js +329 -38
- package/dist/store/types.d.ts +76 -26
- package/dist/store/types.js +13 -1
- package/dist/store/vector-backend.d.ts +64 -4
- package/dist/store/vector-backend.js +15 -1
- package/package.json +58 -36
- 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 +0 -10
- package/dist/checkpointer/internal/assemble.d.ts.map +0 -1
- package/dist/checkpointer/internal/assemble.js +0 -37
- package/dist/checkpointer/internal/assemble.js.map +0 -1
- package/dist/checkpointer/internal/configurable.d.ts +0 -13
- package/dist/checkpointer/internal/configurable.d.ts.map +0 -1
- package/dist/checkpointer/internal/configurable.js +0 -23
- package/dist/checkpointer/internal/configurable.js.map +0 -1
- package/dist/checkpointer/internal/fetch.d.ts +0 -10
- package/dist/checkpointer/internal/fetch.d.ts.map +0 -1
- package/dist/checkpointer/internal/fetch.js +0 -46
- package/dist/checkpointer/internal/fetch.js.map +0 -1
- package/dist/checkpointer/internal/filter-match.d.ts +0 -12
- package/dist/checkpointer/internal/filter-match.d.ts.map +0 -1
- package/dist/checkpointer/internal/filter-match.js +0 -14
- package/dist/checkpointer/internal/filter-match.js.map +0 -1
- package/dist/checkpointer/internal/item-reader.d.ts +0 -55
- package/dist/checkpointer/internal/item-reader.d.ts.map +0 -1
- package/dist/checkpointer/internal/item-reader.js +0 -88
- package/dist/checkpointer/internal/item-reader.js.map +0 -1
- package/dist/checkpointer/internal/item-writer.d.ts +0 -26
- package/dist/checkpointer/internal/item-writer.d.ts.map +0 -1
- package/dist/checkpointer/internal/item-writer.js +0 -92
- package/dist/checkpointer/internal/item-writer.js.map +0 -1
- package/dist/checkpointer/internal/keys.d.ts +0 -31
- package/dist/checkpointer/internal/keys.d.ts.map +0 -1
- package/dist/checkpointer/internal/keys.js +0 -87
- package/dist/checkpointer/internal/keys.js.map +0 -1
- package/dist/checkpointer/internal/query.d.ts +0 -20
- package/dist/checkpointer/internal/query.d.ts.map +0 -1
- package/dist/checkpointer/internal/query.js +0 -36
- 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 +0 -30
- package/dist/checkpointer/internal/special-write-cas.d.ts.map +0 -1
- package/dist/checkpointer/internal/special-write-cas.js +0 -104
- package/dist/checkpointer/internal/special-write-cas.js.map +0 -1
- package/dist/checkpointer/internal/special-write-cleanup.d.ts +0 -24
- package/dist/checkpointer/internal/special-write-cleanup.d.ts.map +0 -1
- package/dist/checkpointer/internal/special-write-cleanup.js +0 -47
- package/dist/checkpointer/internal/special-write-cleanup.js.map +0 -1
- package/dist/checkpointer/internal/special-write-verify.d.ts +0 -54
- package/dist/checkpointer/internal/special-write-verify.d.ts.map +0 -1
- package/dist/checkpointer/internal/special-write-verify.js +0 -65
- package/dist/checkpointer/internal/special-write-verify.js.map +0 -1
- package/dist/checkpointer/internal/validation.d.ts +0 -13
- package/dist/checkpointer/internal/validation.d.ts.map +0 -1
- package/dist/checkpointer/internal/validation.js +0 -30
- package/dist/checkpointer/internal/validation.js.map +0 -1
- package/dist/checkpointer/internal/write-guard.d.ts +0 -13
- package/dist/checkpointer/internal/write-guard.d.ts.map +0 -1
- package/dist/checkpointer/internal/write-guard.js +0 -39
- package/dist/checkpointer/internal/write-guard.js.map +0 -1
- package/dist/checkpointer/internal/write-index.d.ts +0 -37
- package/dist/checkpointer/internal/write-index.d.ts.map +0 -1
- package/dist/checkpointer/internal/write-index.js +0 -42
- 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 +0 -20
- package/dist/history/internal/append-saga.d.ts.map +0 -1
- package/dist/history/internal/append-saga.js +0 -35
- package/dist/history/internal/append-saga.js.map +0 -1
- package/dist/history/internal/compensation.d.ts +0 -21
- package/dist/history/internal/compensation.d.ts.map +0 -1
- package/dist/history/internal/compensation.js +0 -84
- package/dist/history/internal/compensation.js.map +0 -1
- package/dist/history/internal/item-mapper.d.ts +0 -12
- package/dist/history/internal/item-mapper.d.ts.map +0 -1
- package/dist/history/internal/item-mapper.js +0 -33
- package/dist/history/internal/item-mapper.js.map +0 -1
- package/dist/history/internal/keys.d.ts +0 -17
- package/dist/history/internal/keys.d.ts.map +0 -1
- package/dist/history/internal/keys.js +0 -49
- package/dist/history/internal/keys.js.map +0 -1
- package/dist/history/internal/message-chunker.d.ts +0 -14
- package/dist/history/internal/message-chunker.d.ts.map +0 -1
- package/dist/history/internal/message-chunker.js +0 -68
- package/dist/history/internal/message-chunker.js.map +0 -1
- package/dist/history/internal/message-transaction.d.ts +0 -26
- package/dist/history/internal/message-transaction.d.ts.map +0 -1
- package/dist/history/internal/message-transaction.js +0 -60
- package/dist/history/internal/message-transaction.js.map +0 -1
- package/dist/history/internal/query.d.ts +0 -10
- package/dist/history/internal/query.d.ts.map +0 -1
- package/dist/history/internal/query.js +0 -31
- package/dist/history/internal/query.js.map +0 -1
- package/dist/history/internal/session-count.d.ts +0 -41
- package/dist/history/internal/session-count.d.ts.map +0 -1
- package/dist/history/internal/session-count.js +0 -109
- package/dist/history/internal/session-count.js.map +0 -1
- package/dist/history/internal/session-title.d.ts +0 -20
- package/dist/history/internal/session-title.d.ts.map +0 -1
- package/dist/history/internal/session-title.js +0 -44
- package/dist/history/internal/session-title.js.map +0 -1
- package/dist/history/internal/session-update.d.ts +0 -28
- package/dist/history/internal/session-update.d.ts.map +0 -1
- package/dist/history/internal/session-update.js +0 -70
- 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 +0 -13
- package/dist/history/internal/title-generator.d.ts.map +0 -1
- package/dist/history/internal/title-generator.js +0 -25
- package/dist/history/internal/title-generator.js.map +0 -1
- package/dist/history/internal/ttl-anchor.d.ts +0 -25
- package/dist/history/internal/ttl-anchor.d.ts.map +0 -1
- package/dist/history/internal/ttl-anchor.js +0 -38
- package/dist/history/internal/ttl-anchor.js.map +0 -1
- package/dist/history/internal/validation.d.ts +0 -9
- package/dist/history/internal/validation.d.ts.map +0 -1
- package/dist/history/internal/validation.js +0 -16
- 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 +0 -4
- package/dist/shared/codec/descriptor-keys.d.ts.map +0 -1
- package/dist/shared/codec/descriptor-keys.js +0 -14
- 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 +0 -8
- package/dist/shared/codec/s3/delete.d.ts.map +0 -1
- package/dist/shared/codec/s3/delete.js +0 -29
- 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 +0 -18
- package/dist/shared/codec/s3/orphans.d.ts.map +0 -1
- package/dist/shared/codec/s3/orphans.js +0 -58
- package/dist/shared/codec/s3/orphans.js.map +0 -1
- package/dist/shared/codec/s3/read-write.d.ts +0 -14
- package/dist/shared/codec/s3/read-write.d.ts.map +0 -1
- package/dist/shared/codec/s3/read-write.js +0 -43
- package/dist/shared/codec/s3/read-write.js.map +0 -1
- package/dist/shared/codec/s3/retry.d.ts +0 -5
- package/dist/shared/codec/s3/retry.d.ts.map +0 -1
- package/dist/shared/codec/s3/retry.js +0 -25
- package/dist/shared/codec/s3/retry.js.map +0 -1
- package/dist/shared/constants.d.ts +0 -64
- package/dist/shared/constants.d.ts.map +0 -1
- package/dist/shared/constants.js +0 -67
- package/dist/shared/constants.js.map +0 -1
- package/dist/shared/dynamodb/backoff.d.ts +0 -15
- package/dist/shared/dynamodb/backoff.d.ts.map +0 -1
- package/dist/shared/dynamodb/backoff.js +0 -48
- 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 +0 -51
- package/dist/shared/dynamodb/conditional-put.d.ts.map +0 -1
- package/dist/shared/dynamodb/conditional-put.js +0 -59
- package/dist/shared/dynamodb/conditional-put.js.map +0 -1
- package/dist/shared/dynamodb/drain-unprocessed.d.ts +0 -19
- package/dist/shared/dynamodb/drain-unprocessed.d.ts.map +0 -1
- package/dist/shared/dynamodb/drain-unprocessed.js +0 -44
- package/dist/shared/dynamodb/drain-unprocessed.js.map +0 -1
- package/dist/shared/dynamodb/paginate-core.d.ts +0 -22
- package/dist/shared/dynamodb/paginate-core.d.ts.map +0 -1
- package/dist/shared/dynamodb/paginate-core.js +0 -52
- 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 +0 -9
- package/dist/shared/dynamodb/retry-classifier.d.ts.map +0 -1
- package/dist/shared/dynamodb/retry-classifier.js +0 -87
- 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 +0 -15
- package/dist/shared/dynamodb/scan.d.ts.map +0 -1
- package/dist/shared/dynamodb/scan.js +0 -20
- package/dist/shared/dynamodb/scan.js.map +0 -1
- package/dist/shared/dynamodb/types.d.ts +0 -24
- package/dist/shared/dynamodb/types.d.ts.map +0 -1
- package/dist/shared/dynamodb/types.js +0 -3
- 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 +0 -16
- package/dist/shared/errors/wrap-error.d.ts.map +0 -1
- package/dist/shared/errors/wrap-error.js +0 -30
- 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 +0 -23
- package/dist/shared/logging/redaction-walk.d.ts.map +0 -1
- package/dist/shared/logging/redaction-walk.js +0 -92
- 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 +0 -5
- package/dist/store/actions/get.d.ts.map +0 -1
- package/dist/store/actions/get.js +0 -35
- 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 +0 -5
- package/dist/store/internal/backend-search.d.ts.map +0 -1
- package/dist/store/internal/backend-search.js +0 -68
- 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 +0 -22
- package/dist/store/internal/index-reconcile.d.ts.map +0 -1
- package/dist/store/internal/index-reconcile.js +0 -105
- package/dist/store/internal/index-reconcile.js.map +0 -1
- package/dist/store/internal/index-sync.d.ts +0 -11
- package/dist/store/internal/index-sync.d.ts.map +0 -1
- package/dist/store/internal/index-sync.js +0 -26
- package/dist/store/internal/index-sync.js.map +0 -1
- package/dist/store/internal/item-mapper.d.ts +0 -25
- package/dist/store/internal/item-mapper.d.ts.map +0 -1
- package/dist/store/internal/item-mapper.js +0 -53
- package/dist/store/internal/item-mapper.js.map +0 -1
- package/dist/store/internal/keys.d.ts +0 -18
- package/dist/store/internal/keys.d.ts.map +0 -1
- package/dist/store/internal/keys.js +0 -42
- package/dist/store/internal/keys.js.map +0 -1
- package/dist/store/internal/namespace-match.d.ts +0 -12
- package/dist/store/internal/namespace-match.d.ts.map +0 -1
- package/dist/store/internal/namespace-match.js +0 -41
- package/dist/store/internal/namespace-match.js.map +0 -1
- package/dist/store/internal/overwrite-swap.d.ts +0 -33
- package/dist/store/internal/overwrite-swap.d.ts.map +0 -1
- package/dist/store/internal/overwrite-swap.js +0 -62
- package/dist/store/internal/overwrite-swap.js.map +0 -1
- package/dist/store/internal/persist.d.ts +0 -27
- package/dist/store/internal/persist.d.ts.map +0 -1
- package/dist/store/internal/persist.js +0 -59
- package/dist/store/internal/persist.js.map +0 -1
- package/dist/store/internal/query.d.ts +0 -6
- package/dist/store/internal/query.d.ts.map +0 -1
- package/dist/store/internal/query.js +0 -32
- package/dist/store/internal/query.js.map +0 -1
- package/dist/store/internal/ranker.d.ts +0 -13
- package/dist/store/internal/ranker.d.ts.map +0 -1
- package/dist/store/internal/ranker.js +0 -31
- package/dist/store/internal/ranker.js.map +0 -1
- package/dist/store/internal/read-existing.d.ts +0 -19
- package/dist/store/internal/read-existing.d.ts.map +0 -1
- package/dist/store/internal/read-existing.js +0 -29
- package/dist/store/internal/read-existing.js.map +0 -1
- package/dist/store/internal/score-direction.d.ts +0 -32
- package/dist/store/internal/score-direction.d.ts.map +0 -1
- package/dist/store/internal/score-direction.js +0 -39
- package/dist/store/internal/score-direction.js.map +0 -1
- package/dist/store/internal/search-filter.d.ts +0 -4
- package/dist/store/internal/search-filter.d.ts.map +0 -1
- package/dist/store/internal/search-filter.js +0 -11
- 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 +0 -13
- package/dist/store/internal/validation.d.ts.map +0 -1
- package/dist/store/internal/validation.js +0 -35
- package/dist/store/internal/validation.js.map +0 -1
- package/dist/store/internal/write-verify.d.ts +0 -37
- package/dist/store/internal/write-verify.d.ts.map +0 -1
- package/dist/store/internal/write-verify.js +0 -68
- 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
|
@@ -1,15 +1,53 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hides S3 behind one object.
|
|
4
|
+
*
|
|
5
|
+
* The client is built lazily from the optional peer. An upload writes only
|
|
6
|
+
* while its key is free, so a retried upload never replaces another write's
|
|
7
|
+
* object, and carries its row's key as metadata. A download refuses an object
|
|
8
|
+
* over the configured cap before and while reading it. A delete goes out in
|
|
9
|
+
* batches of a thousand, and a best-effort release retries transient failures,
|
|
10
|
+
* never deletes outside the row's own path, and logs rather than throws.
|
|
11
|
+
*/
|
|
2
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.S3Offloader = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
13
|
+
exports.S3Offloader = exports.S3_DELETE_BATCH_MAX = exports.DEFAULT_MAX_S3_DOWNLOAD_BYTES = exports.DEFAULT_S3_SSE = exports.DEFAULT_S3_THRESHOLD_BYTES = void 0;
|
|
14
|
+
exports.uploadObject = uploadObject;
|
|
15
|
+
exports.downloadObject = downloadObject;
|
|
16
|
+
exports.oversizedObjectError = oversizedObjectError;
|
|
17
|
+
exports.readBodyBounded = readBodyBounded;
|
|
18
|
+
exports.deleteObjects = deleteObjects;
|
|
19
|
+
exports.backlinkMetadata = backlinkMetadata;
|
|
20
|
+
exports.metadataBytes = metadataBytes;
|
|
21
|
+
exports.cleanUpS3Orphans = cleanUpS3Orphans;
|
|
22
|
+
const abort_1 = require("../../dynamodb/abort");
|
|
23
|
+
const client_1 = require("../../dynamodb/client");
|
|
24
|
+
const retry_1 = require("../../dynamodb/retry");
|
|
25
|
+
const base_error_1 = require("../../errors/base-error");
|
|
26
|
+
const classify_1 = require("../../errors/classify");
|
|
27
|
+
const error_code_1 = require("../../errors/error-code");
|
|
28
|
+
const logger_1 = require("../../logging/logger");
|
|
29
|
+
const secret_patterns_1 = require("../../logging/secret-patterns");
|
|
30
|
+
const truncate_1 = require("../../logging/truncate");
|
|
31
|
+
const client_2 = require("./client");
|
|
6
32
|
const config_1 = require("./config");
|
|
7
|
-
const delete_1 = require("./delete");
|
|
8
33
|
const lifecycle_1 = require("./lifecycle");
|
|
9
|
-
|
|
34
|
+
/** Default payload size that triggers S3 offload (350 KB; 50 KB under the DDB 400 KB cap). */
|
|
35
|
+
exports.DEFAULT_S3_THRESHOLD_BYTES = 350 * 1024;
|
|
36
|
+
/** Default S3 server-side encryption algorithm. */
|
|
37
|
+
exports.DEFAULT_S3_SSE = 'AES256';
|
|
10
38
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
39
|
+
* Default cap on an offloaded object buffered from S3 (50 MiB), checked against
|
|
40
|
+
* `ContentLength` before the body is read and enforced while streaming when
|
|
41
|
+
* the length is unknown. Together with
|
|
42
|
+
* `DEFAULT_MAX_DECOMPRESSED_BYTES` (`src/shared/codec/compression.ts`)
|
|
43
|
+
* it bounds the memory any single payload can claim.
|
|
44
|
+
*/
|
|
45
|
+
exports.DEFAULT_MAX_S3_DOWNLOAD_BYTES = 50 * 1024 * 1024;
|
|
46
|
+
/** S3 DeleteObjects maximum keys per request. */
|
|
47
|
+
exports.S3_DELETE_BATCH_MAX = 1000;
|
|
48
|
+
/**
|
|
49
|
+
* Owns the S3 configuration and the lazily-built client; every method is a
|
|
50
|
+
* small delegation to this module's own functions, keyed to that config.
|
|
13
51
|
*/
|
|
14
52
|
class S3Offloader {
|
|
15
53
|
clientPromise;
|
|
@@ -20,26 +58,52 @@ class S3Offloader {
|
|
|
20
58
|
thresholdBytes;
|
|
21
59
|
sse;
|
|
22
60
|
sseKmsKeyId;
|
|
61
|
+
maxDownloadBytes;
|
|
23
62
|
config;
|
|
63
|
+
/**
|
|
64
|
+
* Accepts: `config` — already validated by the adapter that builds this, with
|
|
65
|
+
* its key prefix resolved to the adapter's own path.
|
|
66
|
+
*
|
|
67
|
+
* Returns: an offloader whose S3 client is built lazily, on the first
|
|
68
|
+
* operation that needs one.
|
|
69
|
+
*
|
|
70
|
+
* Throws: nothing. A missing `@aws-sdk/client-s3` is not raised here: the
|
|
71
|
+
* import is warmed so the failure surfaces, typed, on the first S3 operation
|
|
72
|
+
* rather than on the first oversize payload days later.
|
|
73
|
+
*/
|
|
24
74
|
constructor(config) {
|
|
25
75
|
this.config = config;
|
|
26
76
|
this.bucketName = config.bucketName;
|
|
27
|
-
this.keyPrefix = config.keyPrefix ??
|
|
28
|
-
this.thresholdBytes = config.thresholdBytes ??
|
|
29
|
-
this.sse = config.serverSideEncryption ??
|
|
77
|
+
this.keyPrefix = config.keyPrefix ?? config_1.DEFAULT_S3_KEY_PREFIX;
|
|
78
|
+
this.thresholdBytes = config.thresholdBytes ?? exports.DEFAULT_S3_THRESHOLD_BYTES;
|
|
79
|
+
this.sse = config.serverSideEncryption ?? exports.DEFAULT_S3_SSE;
|
|
30
80
|
this.sseKmsKeyId = config.sseKmsKeyId;
|
|
81
|
+
this.maxDownloadBytes = config.maxDownloadBytes ?? exports.DEFAULT_MAX_S3_DOWNLOAD_BYTES;
|
|
82
|
+
// Warm the optional peer's import so a missing `@aws-sdk/client-s3`
|
|
83
|
+
// surfaces on the very first S3 operation, typed, rather than on the first
|
|
84
|
+
// oversize payload days later. The rejection is handled here; whichever
|
|
85
|
+
// operation runs first re-raises it through its own `loadS3Sdk()` call.
|
|
86
|
+
void (0, client_2.loadS3Sdk)().catch(() => undefined);
|
|
31
87
|
}
|
|
32
88
|
getClient() {
|
|
33
89
|
if (!this.clientPromise) {
|
|
34
90
|
const cfg = this.config.clientConfig ?? {};
|
|
91
|
+
// The hook is typed structurally for consumers; the runtime modules use
|
|
92
|
+
// the real SDK client. It hands over a constructor, not a configuration,
|
|
93
|
+
// so a caller who supplies one has not opted out of the bound: the same
|
|
94
|
+
// default handler {@link createDefaultS3Client} applies reaches it, for
|
|
95
|
+
// the reason recorded there. `cfg` still spreads last, so a caller who
|
|
96
|
+
// does want to replace it puts a `requestHandler` in `clientConfig`.
|
|
35
97
|
this.clientPromise = (this.config.createS3Client
|
|
36
|
-
? Promise.resolve(this.config.createS3Client({
|
|
37
|
-
|
|
98
|
+
? Promise.resolve(this.config.createS3Client({
|
|
99
|
+
maxAttempts: 1,
|
|
100
|
+
requestHandler: { socketTimeout: client_1.DEFAULT_SOCKET_TIMEOUT_MS },
|
|
101
|
+
...cfg,
|
|
102
|
+
}))
|
|
103
|
+
: (0, client_2.createDefaultS3Client)(cfg)).then((client) => {
|
|
38
104
|
this.resolvedClient = client;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
* no client yet to release. Release it now instead of leaking it.
|
|
42
|
-
*/
|
|
105
|
+
// `destroy()` may have run during this construction, when there was
|
|
106
|
+
// no client yet to release. Release it now instead of leaking it.
|
|
43
107
|
if (this.destroyed)
|
|
44
108
|
client.destroy();
|
|
45
109
|
return client;
|
|
@@ -50,46 +114,167 @@ class S3Offloader {
|
|
|
50
114
|
}
|
|
51
115
|
return this.clientPromise;
|
|
52
116
|
}
|
|
53
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Whether `data` is large enough to warrant S3 offload.
|
|
119
|
+
*
|
|
120
|
+
* Accepts: the encoded payload, after compression — what would actually be
|
|
121
|
+
* stored.
|
|
122
|
+
*
|
|
123
|
+
* Returns: whether it reaches `thresholdBytes`. The comparison is inclusive,
|
|
124
|
+
* so a payload exactly at the threshold offloads.
|
|
125
|
+
*
|
|
126
|
+
* Throws: nothing.
|
|
127
|
+
*/
|
|
54
128
|
shouldOffload(data) {
|
|
55
129
|
return data.length >= this.thresholdBytes;
|
|
56
130
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Build the S3 key of the object write `objectId` uploads for the row `parts`
|
|
133
|
+
* identify.
|
|
134
|
+
*
|
|
135
|
+
* Accepts: `parts` — at least one; the row's identity. `objectId` — the
|
|
136
|
+
* uploading write's id, appended as it is: key-safe, with no `/` (see
|
|
137
|
+
* {@link buildS3Key}).
|
|
138
|
+
*
|
|
139
|
+
* Returns: the key, under this offloader's prefix.
|
|
140
|
+
*
|
|
141
|
+
* Throws: `VALIDATION` naming `s3Key` for empty `parts` or a key over
|
|
142
|
+
* S3's 1024-byte cap.
|
|
143
|
+
*/
|
|
144
|
+
buildKey(parts, objectId) {
|
|
145
|
+
return (0, config_1.buildS3Key)(this.keyPrefix, parts, objectId);
|
|
60
146
|
}
|
|
61
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* The configured key prefix.
|
|
149
|
+
*
|
|
150
|
+
* Accepts: nothing.
|
|
151
|
+
*
|
|
152
|
+
* Returns: the prefix every key this offloader builds starts with, which is
|
|
153
|
+
* what a lifecycle rule is scoped to.
|
|
154
|
+
*
|
|
155
|
+
* Throws: nothing.
|
|
156
|
+
*/
|
|
62
157
|
getKeyPrefix() {
|
|
63
158
|
return this.keyPrefix;
|
|
64
159
|
}
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Whether `key` lies under this offloader's prefix and the `scope` parts'
|
|
162
|
+
* path.
|
|
163
|
+
*
|
|
164
|
+
* Accepts: `key` — any key, including one read off a row. `scope` — the
|
|
165
|
+
* identity of the row that claims it.
|
|
166
|
+
*
|
|
167
|
+
* Returns: whether the row may address that object.
|
|
168
|
+
*
|
|
169
|
+
* Throws: nothing — this is the question form; {@link assertOwnedKey} is the
|
|
170
|
+
* refusing form.
|
|
171
|
+
*/
|
|
172
|
+
ownsKey(key, scope) {
|
|
173
|
+
return (0, config_1.isKeyInScope)(key, this.keyPrefix, scope);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Refuse a row-sourced key outside the row's own path.
|
|
177
|
+
*
|
|
178
|
+
* Accepts: as {@link ownsKey}.
|
|
179
|
+
*
|
|
180
|
+
* Returns: nothing: `key` is kept under its declared type, and this checks
|
|
181
|
+
* it against `scope`.
|
|
182
|
+
*
|
|
183
|
+
* Throws: `VALIDATION`. Every download of a key taken from a row goes
|
|
184
|
+
* through here, so a tampered or foreign row cannot make a reader fetch an
|
|
185
|
+
* object belonging to another row.
|
|
186
|
+
*/
|
|
187
|
+
assertOwnedKey(key, scope) {
|
|
188
|
+
(0, config_1.assertKeyInScope)(key, this.keyPrefix, scope);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Upload `data` under `key`, writing only while the key is free.
|
|
192
|
+
*
|
|
193
|
+
* Accepts: `key` — built by {@link buildKey} for the write uploading `data`,
|
|
194
|
+
* so no other write uploads to it. `row` — written to the object as the
|
|
195
|
+
* DynamoDB backlink, for an out-of-band sweeper. `signal` — cancels the
|
|
196
|
+
* request itself, not merely the wait before the next attempt.
|
|
197
|
+
*
|
|
198
|
+
* Returns: the key, whether this request stored the object or an earlier
|
|
199
|
+
* attempt of this upload did (see `uploadObject`); the caller's obligation is
|
|
200
|
+
* the same either way.
|
|
201
|
+
*
|
|
202
|
+
* Throws: `S3_OFFLOAD_FAILED` carrying the key; `ABORTED` when the signal
|
|
203
|
+
* fires, which is the caller's own stop rather than a failed offload.
|
|
204
|
+
*/
|
|
205
|
+
async upload(key, data, row, signal) {
|
|
206
|
+
await uploadObject(await this.getClient(), {
|
|
68
207
|
bucket: this.bucketName,
|
|
69
208
|
key,
|
|
70
209
|
data,
|
|
71
210
|
serverSideEncryption: this.sse,
|
|
72
211
|
sseKmsKeyId: this.sseKmsKeyId,
|
|
212
|
+
metadata: backlinkMetadata(row),
|
|
213
|
+
signal,
|
|
73
214
|
});
|
|
74
215
|
return key;
|
|
75
216
|
}
|
|
76
|
-
/**
|
|
77
|
-
|
|
78
|
-
|
|
217
|
+
/**
|
|
218
|
+
* Download the bytes stored under `key`.
|
|
219
|
+
*
|
|
220
|
+
* Accepts: `key` — already checked against the reading row's scope.
|
|
221
|
+
* `signal` — cancels the request, including a body already streaming: the
|
|
222
|
+
* handler's abort listener outlives the response headers and destroys the
|
|
223
|
+
* socket, which is the one bound a stalled transfer has that the request
|
|
224
|
+
* timeout provably does not give it.
|
|
225
|
+
*
|
|
226
|
+
* Returns: the object's bytes.
|
|
227
|
+
*
|
|
228
|
+
* Throws: `S3_OFFLOAD_FAILED` for an object over `maxDownloadBytes` — the cap
|
|
229
|
+
* is enforced on the declared length and again while reading, so a lying
|
|
230
|
+
* `Content-Length` does not get past it — and for a missing object or a
|
|
231
|
+
* transport failure; `ABORTED` when the signal fires.
|
|
232
|
+
*/
|
|
233
|
+
async download(key, signal) {
|
|
234
|
+
return downloadObject(await this.getClient(), { bucket: this.bucketName, key, maxBytes: this.maxDownloadBytes }, signal);
|
|
79
235
|
}
|
|
80
|
-
/**
|
|
236
|
+
/**
|
|
237
|
+
* Delete `keys`.
|
|
238
|
+
*
|
|
239
|
+
* Accepts: any number of keys, including none.
|
|
240
|
+
*
|
|
241
|
+
* Returns: the keys S3 reported as failed, so the caller can log what leaked
|
|
242
|
+
* rather than assume it is gone.
|
|
243
|
+
*
|
|
244
|
+
* Throws: whatever the delete throws after its own retries; cleanup callers
|
|
245
|
+
* catch it, since a failed cleanup must not fail the operation it follows.
|
|
246
|
+
*/
|
|
81
247
|
async deleteBatch(keys) {
|
|
82
|
-
return
|
|
248
|
+
return deleteObjects(await this.getClient(), this.bucketName, keys);
|
|
83
249
|
}
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
250
|
+
/**
|
|
251
|
+
* Ensure a `${ttlDays}`-day expiration lifecycle rule exists for the prefix.
|
|
252
|
+
*
|
|
253
|
+
* Accepts: `ttlDays` — whole days, the only granularity S3 accepts.
|
|
254
|
+
* `logger` — the adapter's, for the bucket's versioning state, which is
|
|
255
|
+
* reported rather than enforced.
|
|
256
|
+
*
|
|
257
|
+
* Returns: nothing. Rules that are already correct are left alone, so this
|
|
258
|
+
* is safe to call on every deploy.
|
|
259
|
+
*
|
|
260
|
+
* Throws: `VALIDATION` naming `s3.keyPrefix` when a rule id this prefix
|
|
261
|
+
* would take is already held by a different prefix; whatever reading or
|
|
262
|
+
* writing the bucket's lifecycle configuration throws.
|
|
263
|
+
*/
|
|
264
|
+
async ensureLifecycleRule(ttlDays, logger) {
|
|
265
|
+
return (0, lifecycle_1.ensureLifecycleRule)(await this.getClient(), { bucket: this.bucketName, prefix: this.keyPrefix, days: ttlDays }, logger);
|
|
87
266
|
}
|
|
88
267
|
/**
|
|
89
|
-
* Release the underlying S3 client.
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
268
|
+
* Release the underlying S3 client.
|
|
269
|
+
*
|
|
270
|
+
* Accepts: nothing.
|
|
271
|
+
*
|
|
272
|
+
* Returns: nothing. Safe at any point in the client's lifecycle: called
|
|
273
|
+
* before construction starts it does nothing, called mid-construction it
|
|
274
|
+
* marks the offloader destroyed so the client is released the moment it
|
|
275
|
+
* resolves, and called after it releases it directly.
|
|
276
|
+
*
|
|
277
|
+
* Throws: whatever the SDK client's own `destroy` throws.
|
|
93
278
|
*/
|
|
94
279
|
destroy() {
|
|
95
280
|
this.destroyed = true;
|
|
@@ -97,4 +282,377 @@ class S3Offloader {
|
|
|
97
282
|
}
|
|
98
283
|
}
|
|
99
284
|
exports.S3Offloader = S3Offloader;
|
|
100
|
-
|
|
285
|
+
/**
|
|
286
|
+
* Re-throw a cancel as it is, before anything rebrands it.
|
|
287
|
+
*
|
|
288
|
+
* Both wrappers turn every failure into `S3_OFFLOAD_FAILED`, which is right
|
|
289
|
+
* for a failure and wrong for a stop the caller asked for: a cancelled
|
|
290
|
+
* `getMessages` would report that its payload could not be offloaded. The
|
|
291
|
+
* check has to sit ahead of the wrapping rather than inside the classifier,
|
|
292
|
+
* because by then `withRetry` has already decided this is a cancel and said
|
|
293
|
+
* so with the only code a caller branches on.
|
|
294
|
+
*/
|
|
295
|
+
function rethrowIfCancelled(error) {
|
|
296
|
+
if ((0, abort_1.isAbortError)(error))
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* True when S3 refused a conditional write because the key is already taken.
|
|
301
|
+
*
|
|
302
|
+
* With `If-None-Match: *` that is a `412 Precondition Failed` (S3 User Guide,
|
|
303
|
+
* *How to prevent object overwrites with conditional writes*). A key names one
|
|
304
|
+
* write's upload, and only that upload's own requests write it, so the object
|
|
305
|
+
* already there was stored by an earlier attempt of this upload. The upload
|
|
306
|
+
* has nothing left to do, and the 412 is the success case, not a failure. The
|
|
307
|
+
* classifier maps both `PreconditionFailed` and a bare 412 to the same code.
|
|
308
|
+
*/
|
|
309
|
+
function alreadyStored(error) {
|
|
310
|
+
return (0, classify_1.classifyAwsError)(error) === error_code_1.ErrorCode.CONDITION_CONFLICT;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Upload `data` to S3 unless an object already exists under `key`, wrapping
|
|
314
|
+
* failures as `S3_OFFLOAD_FAILED`.
|
|
315
|
+
*
|
|
316
|
+
* The write is conditional (`If-None-Match: *`), which costs nothing extra: it
|
|
317
|
+
* needs only `s3:PutObject`, the permission this package already requires, and
|
|
318
|
+
* a retried request writes nothing new: it can never overwrite the object an
|
|
319
|
+
* earlier attempt stored. A `409 Conflict` — S3's answer when a delete lands
|
|
320
|
+
* between the check and the write — is classified as transient and retried
|
|
321
|
+
* like any other conflict.
|
|
322
|
+
*
|
|
323
|
+
* Accepts: `params.key` — the key of one write's upload of `params.data`,
|
|
324
|
+
* written by no other write. `params.metadata` — the backlink, sent with every
|
|
325
|
+
* attempt; an object an earlier attempt stored keeps that attempt's metadata,
|
|
326
|
+
* which is the same. `params.signal` — cancels the request in flight, not only
|
|
327
|
+
* the wait before the next attempt.
|
|
328
|
+
*
|
|
329
|
+
* Returns: nothing, both when this request stored the object and when S3
|
|
330
|
+
* answered `412` because an earlier attempt of this upload already had. The
|
|
331
|
+
* two are not distinguished because the caller's obligation is identical: the
|
|
332
|
+
* bytes are at that key.
|
|
333
|
+
*
|
|
334
|
+
* Throws: `ABORTED` when the signal fires, unwrapped; `S3_OFFLOAD_FAILED`
|
|
335
|
+
* carrying the key and the underlying error, after three attempts on a
|
|
336
|
+
* transient failure. Its message quotes the SDK's, with credential shapes
|
|
337
|
+
* redacted — a signing failure names the key it signed with, and this message
|
|
338
|
+
* reaches `err.message` on a public error.
|
|
339
|
+
*
|
|
340
|
+
* Guarantees: a cancelled upload leaves at most the object it was writing, at
|
|
341
|
+
* a key ending in this write's own object id, which no row names because the
|
|
342
|
+
* row that would have named it is written afterwards. The conditional write
|
|
343
|
+
* means it can never have replaced anything. It is an orphan of exactly the
|
|
344
|
+
* kind `ensureS3LifecycleRule` sweeps, never a corrupted or half-written
|
|
345
|
+
* object: S3 stores a single `PutObject` whole or not at all.
|
|
346
|
+
*/
|
|
347
|
+
async function uploadObject(client, params) {
|
|
348
|
+
const { PutObjectCommand } = await (0, client_2.loadS3Sdk)();
|
|
349
|
+
try {
|
|
350
|
+
await (0, retry_1.withRetry)((request) => client.send(new PutObjectCommand({
|
|
351
|
+
Bucket: params.bucket,
|
|
352
|
+
Key: params.key,
|
|
353
|
+
Body: params.data,
|
|
354
|
+
ContentType: 'application/octet-stream',
|
|
355
|
+
IfNoneMatch: '*',
|
|
356
|
+
ServerSideEncryption: params.serverSideEncryption,
|
|
357
|
+
...(params.sseKmsKeyId ? { SSEKMSKeyId: params.sseKmsKeyId } : {}),
|
|
358
|
+
...(params.metadata ? { Metadata: params.metadata } : {}),
|
|
359
|
+
}), request), { maxAttempts: 3, isRetryable: retry_1.isTransientS3Error, signal: params.signal });
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
if (alreadyStored(error))
|
|
363
|
+
return;
|
|
364
|
+
rethrowIfCancelled(error);
|
|
365
|
+
throw new base_error_1.DynamoDBLangGraphError((0, secret_patterns_1.redactedMessage)(error), error_code_1.ErrorCode.S3_OFFLOAD_FAILED, { operation: 'upload', key: params.key }, error);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* The bytes stored under `object.key`.
|
|
370
|
+
*
|
|
371
|
+
* Accepts: `object.maxBytes` — the largest object this call will buffer.
|
|
372
|
+
* `signal` — cancels the request, and with it a body already streaming.
|
|
373
|
+
*
|
|
374
|
+
* Returns: the object's bytes.
|
|
375
|
+
*
|
|
376
|
+
* Throws: `ABORTED` when the signal fires, unwrapped; `S3_OFFLOAD_FAILED`
|
|
377
|
+
* naming the key — for an object over `maxBytes`, for a response with no body,
|
|
378
|
+
* and for any SDK failure that survives the retries. Its message quotes the
|
|
379
|
+
* underlying one with credential shapes redacted; the SDK error is kept as
|
|
380
|
+
* `cause`, so `NoSuchKey` stays distinguishable
|
|
381
|
+
* ({@link isMissingObjectError}).
|
|
382
|
+
*
|
|
383
|
+
* Guarantees: an object over the cap is refused from its declared
|
|
384
|
+
* `ContentLength` before the body is touched, and while streaming when the
|
|
385
|
+
* length is absent, so a replaced or hostile object cannot exhaust memory.
|
|
386
|
+
* Transient failures are retried by this package alone — the client is built
|
|
387
|
+
* with `maxAttempts: 1`.
|
|
388
|
+
*
|
|
389
|
+
* The body is read *inside* the retried attempt, so the signal covers the
|
|
390
|
+
* whole transfer rather than only the round trip to the headers. A signal that
|
|
391
|
+
* fires mid-body destroys the socket, and the stream rejects with a transport
|
|
392
|
+
* error that every classifier here reads as transient; it never reaches that
|
|
393
|
+
* classification, because the signal is read first. A cancelled download
|
|
394
|
+
* leaves nothing behind at all — no local file, no partial object, and the
|
|
395
|
+
* chunks already buffered are dropped with the call.
|
|
396
|
+
*/
|
|
397
|
+
async function downloadObject(client, object, signal) {
|
|
398
|
+
const { GetObjectCommand } = await (0, client_2.loadS3Sdk)();
|
|
399
|
+
const { bucket, key, maxBytes } = object;
|
|
400
|
+
try {
|
|
401
|
+
return await (0, retry_1.withRetry)(async (request) => {
|
|
402
|
+
const response = await client.send(new GetObjectCommand({ Bucket: bucket, Key: key }), request);
|
|
403
|
+
if (typeof response.ContentLength === 'number' && response.ContentLength > maxBytes) {
|
|
404
|
+
throw oversizedObjectError(key, response.ContentLength, maxBytes);
|
|
405
|
+
}
|
|
406
|
+
if (!response.Body) {
|
|
407
|
+
throw new Error(`S3 object body is empty for key: ${(0, truncate_1.truncateForLog)(key)}`);
|
|
408
|
+
}
|
|
409
|
+
return readBodyBounded(response.Body, key, maxBytes);
|
|
410
|
+
}, { maxAttempts: 3, isRetryable: retry_1.isTransientS3Error, signal });
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
rethrowIfCancelled(error);
|
|
414
|
+
throw new base_error_1.DynamoDBLangGraphError((0, secret_patterns_1.redactedMessage)(error), error_code_1.ErrorCode.S3_OFFLOAD_FAILED, { operation: 'download', key }, error);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* The typed error for an object over the download cap.
|
|
419
|
+
*
|
|
420
|
+
* Accepts: `bytes` — what the object declared or what had been read when the
|
|
421
|
+
* cap was passed; the message says which is not distinguished, because either
|
|
422
|
+
* way the download stops.
|
|
423
|
+
*
|
|
424
|
+
* Returns: the error, coded `S3_OFFLOAD_FAILED` and carrying the key. The
|
|
425
|
+
* message names the key bounded by {@link truncateForLog}, since it came off
|
|
426
|
+
* a row; `context.key` carries it whole, because that is the field a caller
|
|
427
|
+
* reads and it is one value per failed download rather than one per row.
|
|
428
|
+
*
|
|
429
|
+
* Throws: nothing — it builds the error, the caller throws it.
|
|
430
|
+
*/
|
|
431
|
+
function oversizedObjectError(key, bytes, maxBytes) {
|
|
432
|
+
return new base_error_1.DynamoDBLangGraphError(`S3 object ${(0, truncate_1.truncateForLog)(key)} exceeds the ${maxBytes}-byte maxDownloadBytes cap ` +
|
|
433
|
+
`(${bytes} bytes declared or read)`, error_code_1.ErrorCode.S3_OFFLOAD_FAILED, { operation: 'download', key });
|
|
434
|
+
}
|
|
435
|
+
function concat(chunks, total) {
|
|
436
|
+
const out = new Uint8Array(total);
|
|
437
|
+
let offset = 0;
|
|
438
|
+
for (const chunk of chunks) {
|
|
439
|
+
out.set(chunk, offset);
|
|
440
|
+
offset += chunk.length;
|
|
441
|
+
}
|
|
442
|
+
return out;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* The bytes of an S3 body, refused once they pass `maxBytes`.
|
|
446
|
+
*
|
|
447
|
+
* Accepts: `body` — a streaming body (Node's `IncomingMessage`, which the SDK
|
|
448
|
+
* returns) is consumed chunk by chunk; a body offering only
|
|
449
|
+
* `transformToByteArray()` is read whole and then checked. `key` — named in the
|
|
450
|
+
* error. `maxBytes` — the cap; `0` admits only an empty body.
|
|
451
|
+
*
|
|
452
|
+
* Returns: the buffered bytes.
|
|
453
|
+
*
|
|
454
|
+
* Throws: `S3_OFFLOAD_FAILED` naming the key once the total passes `maxBytes`.
|
|
455
|
+
* A streaming body is destroyed at that point, so the rest is never fetched.
|
|
456
|
+
*
|
|
457
|
+
* Guarantees: for a streaming body, peak memory is `maxBytes` plus the one
|
|
458
|
+
* chunk that crossed it — the chunks already held never exceed the cap. For a
|
|
459
|
+
* body without a stream the SDK has already buffered it, so the check bounds
|
|
460
|
+
* what is *returned*, not what was read.
|
|
461
|
+
*/
|
|
462
|
+
async function readBodyBounded(body, key, maxBytes) {
|
|
463
|
+
const streaming = body;
|
|
464
|
+
const iterate = streaming[Symbol.asyncIterator];
|
|
465
|
+
if (typeof iterate !== 'function') {
|
|
466
|
+
const whole = new Uint8Array(await body.transformToByteArray());
|
|
467
|
+
if (whole.length > maxBytes)
|
|
468
|
+
throw oversizedObjectError(key, whole.length, maxBytes);
|
|
469
|
+
return whole;
|
|
470
|
+
}
|
|
471
|
+
const chunks = [];
|
|
472
|
+
let total = 0;
|
|
473
|
+
for await (const chunk of { [Symbol.asyncIterator]: iterate.bind(streaming) }) {
|
|
474
|
+
total += chunk.length;
|
|
475
|
+
if (total > maxBytes) {
|
|
476
|
+
streaming.destroy?.();
|
|
477
|
+
throw oversizedObjectError(key, total, maxBytes);
|
|
478
|
+
}
|
|
479
|
+
chunks.push(chunk);
|
|
480
|
+
}
|
|
481
|
+
return concat(chunks, total);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Delete `keys` from `bucket`, in chunks of {@link S3_DELETE_BATCH_MAX} — the
|
|
485
|
+
* limit `DeleteObjects` accepts per request.
|
|
486
|
+
*
|
|
487
|
+
* Accepts: `keys` — any length, including empty, which issues no request.
|
|
488
|
+
*
|
|
489
|
+
* Returns: the keys S3 named in an error entry. A per-key failure is reported
|
|
490
|
+
* this way rather than thrown, so the caller (orphan cleanup) decides how
|
|
491
|
+
* loudly to react. An error entry carrying no `Key` cannot be attributed and is
|
|
492
|
+
* not reported.
|
|
493
|
+
*
|
|
494
|
+
* Throws: whatever the SDK rejects with — a request that never reached S3, or
|
|
495
|
+
* one refused whole (permissions, a missing bucket).
|
|
496
|
+
*/
|
|
497
|
+
async function deleteObjects(client, bucket, keys) {
|
|
498
|
+
if (keys.length === 0)
|
|
499
|
+
return [];
|
|
500
|
+
const { DeleteObjectsCommand } = await (0, client_2.loadS3Sdk)();
|
|
501
|
+
const failed = [];
|
|
502
|
+
for (let offset = 0; offset < keys.length; offset += exports.S3_DELETE_BATCH_MAX) {
|
|
503
|
+
const chunk = keys.slice(offset, offset + exports.S3_DELETE_BATCH_MAX);
|
|
504
|
+
const response = await client.send(new DeleteObjectsCommand({
|
|
505
|
+
Bucket: bucket,
|
|
506
|
+
Delete: { Objects: chunk.map((key) => ({ Key: key })), Quiet: true },
|
|
507
|
+
}));
|
|
508
|
+
for (const error of response.Errors ?? []) {
|
|
509
|
+
if (error.Key)
|
|
510
|
+
failed.push(error.Key);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return failed;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Metadata names carrying the backlink. S3 lower-cases user-metadata keys, so
|
|
517
|
+
* these are already lower-case; the `-b64` suffix states the encoding, because
|
|
518
|
+
* a sweeper has to decode them to query DynamoDB.
|
|
519
|
+
*/
|
|
520
|
+
const PK_FIELD = 'dynamodb-pk-b64';
|
|
521
|
+
const SK_FIELD = 'dynamodb-sk-b64';
|
|
522
|
+
/**
|
|
523
|
+
* The S3 user metadata linking an object back to the DynamoDB row that points
|
|
524
|
+
* at it — the maintenance aid AWS names for this layout: "Store the primary key
|
|
525
|
+
* value of the item as Amazon S3 metadata of the object" (*Best practices for
|
|
526
|
+
* storing large items and attributes in DynamoDB*). Nothing in this package
|
|
527
|
+
* reads it; it lets an out-of-band sweeper ask DynamoDB whether an object's
|
|
528
|
+
* parent row still exists without parsing object keys.
|
|
529
|
+
*
|
|
530
|
+
* Both values are base64url-encoded. A `thread_id` or a store key may hold any
|
|
531
|
+
* well-formed UTF-16 text, while S3 metadata travels in HTTP headers and the
|
|
532
|
+
* User Guide asks for US-ASCII there; encoding unconditionally keeps every
|
|
533
|
+
* value header-safe and keeps the decoding rule single, which a conditional
|
|
534
|
+
* encoding would not.
|
|
535
|
+
*
|
|
536
|
+
* The pair fits S3's 2 KB metadata budget by construction, not by luck: the
|
|
537
|
+
* same identifiers are already base64url-encoded into the object key, which
|
|
538
|
+
* `buildS3Key` caps at 1024 bytes, so anything that produces a usable key
|
|
539
|
+
* leaves these values far below the budget. `offloader-backlink.test.ts` pins
|
|
540
|
+
* that.
|
|
541
|
+
*
|
|
542
|
+
* Accepts: `row` — the DynamoDB key of the row that will point at the object.
|
|
543
|
+
*
|
|
544
|
+
* Returns: the two metadata fields, both base64url.
|
|
545
|
+
*
|
|
546
|
+
* Throws: nothing.
|
|
547
|
+
*/
|
|
548
|
+
function backlinkMetadata(row) {
|
|
549
|
+
return { [PK_FIELD]: (0, config_1.encodeKeyPart)(row.pk), [SK_FIELD]: (0, config_1.encodeKeyPart)(row.sk) };
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* The total UTF-8 bytes `metadata` costs against S3's 2 KB user-metadata cap.
|
|
553
|
+
*
|
|
554
|
+
* Accepts: any metadata map. Names and values both count, which is how S3
|
|
555
|
+
* measures it.
|
|
556
|
+
*
|
|
557
|
+
* Returns: the byte total.
|
|
558
|
+
*
|
|
559
|
+
* Throws: nothing.
|
|
560
|
+
*/
|
|
561
|
+
function metadataBytes(metadata) {
|
|
562
|
+
return Object.entries(metadata).reduce((total, [name, value]) => total + Buffer.byteLength(name, 'utf8') + Buffer.byteLength(value, 'utf8'), 0);
|
|
563
|
+
}
|
|
564
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
565
|
+
const BASE_DELAY_MS = 100;
|
|
566
|
+
/**
|
|
567
|
+
* Wait out one backoff window, cancellable via `release.signal`. Resolves `true`
|
|
568
|
+
* when the signal aborts the wait (so the caller stops retrying) and `false`
|
|
569
|
+
* otherwise — never rejects, preserving the best-effort, non-throwing contract.
|
|
570
|
+
*/
|
|
571
|
+
async function backoffSleep(delayMs, release) {
|
|
572
|
+
try {
|
|
573
|
+
await (0, retry_1.sleep)((0, retry_1.fullJitter)(delayMs, release.rng), release.signal);
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
catch {
|
|
577
|
+
return true;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
/** Drop every key outside the row's scope, reporting each one. */
|
|
581
|
+
function ownedOnly(offloader, keys, release, scope) {
|
|
582
|
+
return keys.filter((key) => {
|
|
583
|
+
if (offloader.ownsKey(key, scope))
|
|
584
|
+
return true;
|
|
585
|
+
(0, logger_1.absorbLoggerFailure)(() => release.logger.warn(`${release.operation}: refusing to delete an S3 object outside this row's scope`, {
|
|
586
|
+
key: (0, truncate_1.truncateForLog)(key),
|
|
587
|
+
}));
|
|
588
|
+
return false;
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
/** The non-empty keys, restricted to the row's scope when one is given. */
|
|
592
|
+
function selectOrphans(offloader, release) {
|
|
593
|
+
const present = release.keys.filter((key) => typeof key === 'string' && key.length > 0);
|
|
594
|
+
return release.scope ? ownedOnly(offloader, present, release, release.scope) : present;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Best-effort delete of S3 objects orphaned by a failed DynamoDB write.
|
|
598
|
+
*
|
|
599
|
+
* Accepts: `release.keys` — may hold `undefined` and empty entries, which are
|
|
600
|
+
* dropped; nothing left means no request. `release.operation` — names the
|
|
601
|
+
* operation in every log line. `release.scope` — the row's own leading key
|
|
602
|
+
* parts when `keys` came from a row: a key outside that path is reported and
|
|
603
|
+
* never deleted. Own uploads pass no scope. `release.maxAttempts` — attempts
|
|
604
|
+
* on a transient failure, default 3. `release.signal` — aborts the wait
|
|
605
|
+
* between attempts.
|
|
606
|
+
*
|
|
607
|
+
* Returns: nothing.
|
|
608
|
+
*
|
|
609
|
+
* Throws: **nothing**, ever. This is the library's sole non-throwing path: a
|
|
610
|
+
* cleanup failure must not mask the write failure that caused it. Every
|
|
611
|
+
* outcome that is not a clean delete is logged at `warn`, carrying counts and
|
|
612
|
+
* the failing error's *name* — never its message, which can hold a credential
|
|
613
|
+
* fragment. A `logger` that throws is one more thing this absorbs (see
|
|
614
|
+
* `absorbLoggerFailure`): the promise covers the caller's own code too,
|
|
615
|
+
* or every call site's `catch` would hand its caller the wrong error. Every
|
|
616
|
+
* logger reaching here through an adapter is already contained at
|
|
617
|
+
* `resolveLogger`; the guard stays because this promise is written without a
|
|
618
|
+
* precondition, and a `logger` is an argument.
|
|
619
|
+
*
|
|
620
|
+
* Guarantees: an object outside the row's scope is never deleted. Leftovers
|
|
621
|
+
* have no automatic backstop — an S3 lifecycle rule sweeps them only if one was
|
|
622
|
+
* provisioned through `ensureS3LifecycleRule()`, which is opt-in.
|
|
623
|
+
*/
|
|
624
|
+
async function cleanUpS3Orphans(offloader, release) {
|
|
625
|
+
const orphans = selectOrphans(offloader, release);
|
|
626
|
+
if (orphans.length === 0)
|
|
627
|
+
return;
|
|
628
|
+
const maxAttempts = release.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
629
|
+
let delay = BASE_DELAY_MS;
|
|
630
|
+
let lastError = new Error('S3 orphan cleanup did not run');
|
|
631
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
632
|
+
try {
|
|
633
|
+
const failed = await offloader.deleteBatch(orphans);
|
|
634
|
+
if (failed.length === 0)
|
|
635
|
+
return;
|
|
636
|
+
// Absorbed here and not by the `catch` below, which would read a broken
|
|
637
|
+
// logger as a failed delete: the delete succeeded, and only part of it
|
|
638
|
+
// could be reported.
|
|
639
|
+
(0, logger_1.absorbLoggerFailure)(() => release.logger.warn(`Some orphaned S3 objects could not be deleted after ${release.operation}; a lifecycle rule from ensureS3LifecycleRule() would sweep them, otherwise clean up manually`, { failedCount: failed.length }));
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
catch (error) {
|
|
643
|
+
lastError = error;
|
|
644
|
+
if (attempt >= maxAttempts || !(0, retry_1.isTransientS3Error)(lastError))
|
|
645
|
+
break;
|
|
646
|
+
if (await backoffSleep(delay, release))
|
|
647
|
+
break;
|
|
648
|
+
delay = (0, retry_1.nextBackoffDelay)(delay);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
// The error's *name*, never its message: an underlying failure can carry a
|
|
652
|
+
// credential fragment in its text, and this package promises that its logs
|
|
653
|
+
// hold identifiers and counts only. Bounded all the same — a name is an
|
|
654
|
+
// identifier this package did not length-check, and `message` is bounded
|
|
655
|
+
// where `redactedMessage` relays it, so bounding one and relaying the other
|
|
656
|
+
// whole would split what is one value.
|
|
657
|
+
(0, logger_1.absorbLoggerFailure)(() => release.logger.warn(`Failed to clean up orphaned S3 objects after ${release.operation}; a lifecycle rule from ensureS3LifecycleRule() would sweep them, otherwise clean up manually`, { reason: (0, truncate_1.truncateForLog)((0, base_error_1.failureLabel)(lastError)) }));
|
|
658
|
+
}
|