@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
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hides how an append of any size commits all-or-nothing, as its caller sees it.
|
|
4
|
+
*
|
|
5
|
+
* One `TransactWriteItems` carries at most a hundred items and four megabytes,
|
|
6
|
+
* so a large append is several transactions, each writing its messages and the
|
|
7
|
+
* SESSION row's update together. When one of them fails the append is rolled
|
|
8
|
+
* back: committed chunks are deleted and their effect on the SESSION row
|
|
9
|
+
* reverted, and each S3 object is released only once no row can name it. An
|
|
10
|
+
* ambiguous failure is read back before anything is undone. This is also the
|
|
11
|
+
* one module that writes a message row, which is what lets a delete pin a
|
|
12
|
+
* message row to the SESSION row's `writeId`.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.appendMessages = appendMessages;
|
|
16
|
+
exports.compensate = compensate;
|
|
17
|
+
exports.appendChunks = appendChunks;
|
|
18
|
+
exports.writeMessageChunk = writeMessageChunk;
|
|
19
|
+
exports.estimateRowBytes = estimateRowBytes;
|
|
20
|
+
exports.chunkBySize = chunkBySize;
|
|
21
|
+
const clock_1 = require("../../shared/clock");
|
|
22
|
+
const codec_1 = require("../../shared/codec/codec");
|
|
23
|
+
const offloader_1 = require("../../shared/codec/s3/offloader");
|
|
24
|
+
const batch_write_1 = require("../../shared/dynamodb/batch-write");
|
|
25
|
+
const cancellation_1 = require("../../shared/dynamodb/cancellation");
|
|
26
|
+
const idempotent_write_1 = require("../../shared/dynamodb/idempotent-write");
|
|
27
|
+
const table_schema_1 = require("../../shared/dynamodb/table-schema");
|
|
28
|
+
const base_error_1 = require("../../shared/errors/base-error");
|
|
29
|
+
const error_code_1 = require("../../shared/errors/error-code");
|
|
30
|
+
const errors_1 = require("../../shared/errors/errors");
|
|
31
|
+
const logger_1 = require("../../shared/logging/logger");
|
|
32
|
+
const rows_1 = require("./rows");
|
|
33
|
+
const session_1 = require("./session");
|
|
34
|
+
const setup_1 = require("./setup");
|
|
35
|
+
/** Message Puts per append transaction: the 100-item limit, less the metadata Update. */
|
|
36
|
+
const MAX_MESSAGES_PER_TRANSACTION = 99;
|
|
37
|
+
/**
|
|
38
|
+
* Aggregate byte budget per transaction. Held ~500 KB below DynamoDB's 4 MB
|
|
39
|
+
* `TransactWriteItems` ceiling so the conservative per-item estimate (see
|
|
40
|
+
* `ROW_OVERHEAD_BYTES`) cannot push a chunk over the real limit at commit time.
|
|
41
|
+
*/
|
|
42
|
+
const MAX_TRANSACTION_BYTES = 3_500_000;
|
|
43
|
+
/**
|
|
44
|
+
* Encode every message, cleaning up after itself if one fails partway.
|
|
45
|
+
*
|
|
46
|
+
* Offloaded messages upload sequentially here, *before* the append saga's
|
|
47
|
+
* compensation machinery is ever reached, so without the cleanup here a failure
|
|
48
|
+
* on message N would strand messages 1..N-1's already-uploaded S3 objects with
|
|
49
|
+
* nothing to delete them — the one gap in this subsystem's otherwise complete
|
|
50
|
+
* no-orphan guarantee.
|
|
51
|
+
* Nothing will ever reference those objects, so they are safe to delete
|
|
52
|
+
* unconditionally on the way out.
|
|
53
|
+
*/
|
|
54
|
+
async function buildMessageRows(context, request) {
|
|
55
|
+
const { sessionId, signal } = request;
|
|
56
|
+
const ttlTimestamp = request.anchor?.ttlTimestamp;
|
|
57
|
+
const items = [];
|
|
58
|
+
try {
|
|
59
|
+
for (const message of request.messages) {
|
|
60
|
+
items.push(await (0, rows_1.buildMessageRow)(context, { sessionId, messageId: context.ulid(), message, ttlTimestamp }, signal));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
if (context.offloader) {
|
|
65
|
+
await (0, offloader_1.cleanUpS3Orphans)(context.offloader, {
|
|
66
|
+
keys: (0, codec_1.collectS3Keys)(items.map((item) => item.message)),
|
|
67
|
+
operation: 'history.addMessages.encode',
|
|
68
|
+
logger: context.logger,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
return items;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Append messages to a session: encode them, cut them into transactions, and
|
|
77
|
+
* commit them all or undo what committed.
|
|
78
|
+
*
|
|
79
|
+
* Accepts: `request` — the parsed session and messages, the ttl anchor, and
|
|
80
|
+
* the caller's signal.
|
|
81
|
+
*
|
|
82
|
+
* Returns: nothing, once every chunk has committed.
|
|
83
|
+
*
|
|
84
|
+
* Throws: the first chunk's failure after the append is rolled back;
|
|
85
|
+
* `COMPENSATION_FAILED` when the rollback itself fails; whatever encoding a
|
|
86
|
+
* message throws, after this call's own uploads are released.
|
|
87
|
+
*/
|
|
88
|
+
async function appendMessages(context, request) {
|
|
89
|
+
const items = await buildMessageRows(context, request);
|
|
90
|
+
const chunks = chunkBySize(items, MAX_MESSAGES_PER_TRANSACTION, MAX_TRANSACTION_BYTES);
|
|
91
|
+
await appendChunks(context, {
|
|
92
|
+
sessionId: request.sessionId,
|
|
93
|
+
chunks,
|
|
94
|
+
fields: {
|
|
95
|
+
now: (0, clock_1.nowIso)(),
|
|
96
|
+
title: (0, session_1.deriveTitle)(request.messages),
|
|
97
|
+
ttlTimestamp: request.anchor?.ttlTimestamp,
|
|
98
|
+
forceTtlRefresh: request.anchor?.refresh,
|
|
99
|
+
},
|
|
100
|
+
signal: request.signal,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Say what the compensation is doing, and make sure the saying cannot stop it.
|
|
105
|
+
*
|
|
106
|
+
* The caller's `Logger` is consumer code, and both lines here are written from
|
|
107
|
+
* inside a rollback: the first is {@link compensate}'s opening statement, the
|
|
108
|
+
* second sits in the `catch` that builds `COMPENSATION_FAILED`. Unguarded, a
|
|
109
|
+
* throw out of either would take the rollback with it — the first skipping
|
|
110
|
+
* the S3 cleanup, every committed chunk's deletes, the count revert and the
|
|
111
|
+
* rethrow in one go; the second replacing the one error whose job is to say
|
|
112
|
+
* that `messageCount` drifted.
|
|
113
|
+
*
|
|
114
|
+
* The guard itself is {@link absorbLoggerFailure}, which this held an inline
|
|
115
|
+
* copy of while that helper belonged to another change. Swallowing is still
|
|
116
|
+
* the answer for the reason it gives: the only channel a report could use is
|
|
117
|
+
* the one that just broke. Guarded here rather than left to the seam the
|
|
118
|
+
* context's logger was resolved at, because this is the package's least
|
|
119
|
+
* forgiving path — it runs once per rolled-back append, and what it loses if
|
|
120
|
+
* it stops early is a caller's "all messages or none".
|
|
121
|
+
*/
|
|
122
|
+
function reportStep(context, level, message, { sessionId, committedChunks }) {
|
|
123
|
+
(0, logger_1.absorbLoggerFailure)(() => context.logger[level](message, { sessionId, committedChunks }));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Best-effort delete the offloaded S3 objects of the `chunks` slice given.
|
|
127
|
+
* {@link compensate} calls it once per commit status, never for the whole
|
|
128
|
+
* batch, so a committed chunk's objects arrive only once its rows are gone.
|
|
129
|
+
*/
|
|
130
|
+
async function cleanBatchS3(context, chunks) {
|
|
131
|
+
if (!context.offloader)
|
|
132
|
+
return;
|
|
133
|
+
const descriptors = chunks.flat().map((item) => item.message);
|
|
134
|
+
await (0, offloader_1.cleanUpS3Orphans)(context.offloader, {
|
|
135
|
+
keys: (0, codec_1.collectS3Keys)(descriptors),
|
|
136
|
+
operation: 'history.addMessages',
|
|
137
|
+
logger: context.logger,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Delete every committed chunk's items, then undo their effect on the session
|
|
142
|
+
* row — deleting it outright when this call created it (see
|
|
143
|
+
* {@link revertSessionCreation}), so a failed first append leaves no ghost
|
|
144
|
+
* session holding the rolled-back message's title. A *partial* delete is not a
|
|
145
|
+
* clean creation to undo, so that branch reverts only the count.
|
|
146
|
+
*/
|
|
147
|
+
async function rollbackCommitted(context, append, committed) {
|
|
148
|
+
const { sessionId } = append;
|
|
149
|
+
const { now, title } = append.fields;
|
|
150
|
+
const keys = committed.flatMap((chunk) => chunk.keys);
|
|
151
|
+
const total = committed.reduce((sum, chunk) => sum + chunk.count, 0);
|
|
152
|
+
if (keys.length === 0) {
|
|
153
|
+
await (0, session_1.revertSessionCreation)(context, sessionId, { total, createdAt: now, title });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
await (0, batch_write_1.batchWriteAll)(context.client, context.tableName, keys.map((Key) => ({ DeleteRequest: { Key } })), { retry: context.retry });
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
// `batchWriteAll` raises `BATCH_WRITE_INCOMPLETE` for every failure but a
|
|
161
|
+
// cancel, and this call passes no signal, so the cancel cannot arise here
|
|
162
|
+
// — asserted rather than narrowed, since the false branch is unreachable
|
|
163
|
+
// and this project enforces 100% branch coverage. A signal reaching this
|
|
164
|
+
// call would have to narrow instead.
|
|
165
|
+
const deleted = error.details
|
|
166
|
+
.succeededCount;
|
|
167
|
+
await (0, session_1.revertSessionCount)(context, sessionId, deleted, now);
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
await (0, session_1.revertSessionCreation)(context, sessionId, { total, createdAt: now, title });
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Undo a failed batch. Always throws. S3 cleanup is split by commit status so
|
|
174
|
+
* no live row is ever left pointing at a deleted object: the never-committed
|
|
175
|
+
* suffix is cleaned immediately, the committed prefix only after its rows are
|
|
176
|
+
* confirmed deleted. If the rollback itself fails, the committed chunks' S3
|
|
177
|
+
* objects are deliberately left in place (their rows may survive) and it
|
|
178
|
+
* raises `COMPENSATION_FAILED` carrying both the trigger and the
|
|
179
|
+
* rollback error; otherwise it rethrows the trigger.
|
|
180
|
+
*
|
|
181
|
+
* `failure.uncertain` marks the failed chunk
|
|
182
|
+
* (`append.chunks[failure.committed.length]`) as one whose outcome could not be
|
|
183
|
+
* verified: its rows may be live, so its objects are leaked rather than
|
|
184
|
+
* deleted, while the never-attempted chunks after it are still cleaned.
|
|
185
|
+
*
|
|
186
|
+
* Accepts: `append` — the append that failed: its `sessionId`, its `chunks` in
|
|
187
|
+
* order, and the `fields.now` and `fields.title` its first chunk stamped on
|
|
188
|
+
* the session row. `failure.committed` — the chunks known to have landed, in
|
|
189
|
+
* order; empty means the very first chunk failed, and then the only thing to
|
|
190
|
+
* undo is the session row this call may have created. `failure.trigger` — the
|
|
191
|
+
* failure that started this. `failure.uncertain` — see above.
|
|
192
|
+
*
|
|
193
|
+
* Returns: never; the declared `Promise<never>` is the contract.
|
|
194
|
+
*
|
|
195
|
+
* Throws: `failure.trigger` when the rollback succeeded, `COMPENSATION_FAILED`
|
|
196
|
+
* when it did not.
|
|
197
|
+
*
|
|
198
|
+
* Guarantees: an object is deleted only once no row can reference it — the
|
|
199
|
+
* never-committed suffix immediately, the committed prefix only after its rows
|
|
200
|
+
* are confirmed gone, and an unverified chunk never. Storage is leaked in
|
|
201
|
+
* preference to leaving a live row pointing at a deleted object.
|
|
202
|
+
*
|
|
203
|
+
* Neither of its two log lines can stop it: both go through
|
|
204
|
+
* {@link reportStep}. Otherwise a throw from the first would skip the S3
|
|
205
|
+
* cleanup, the rollback, the count revert and the rethrow all at once, leaving
|
|
206
|
+
* every committed chunk in the table with `messageCount` still counting it,
|
|
207
|
+
* and handing the caller the logger's own error in place of the failure that
|
|
208
|
+
* started this. Announcing the rollback is not the rollback.
|
|
209
|
+
*/
|
|
210
|
+
async function compensate(context, append, failure) {
|
|
211
|
+
const { sessionId, chunks } = append;
|
|
212
|
+
const { committed, trigger, uncertain } = failure;
|
|
213
|
+
if (committed.length > 0) {
|
|
214
|
+
reportStep(context, 'warn', 'history.addMessages compensating committed chunks after a chunk failed', { sessionId, committedChunks: committed.length });
|
|
215
|
+
}
|
|
216
|
+
// The never-attempted suffix never had a DynamoDB row, so it is safe to
|
|
217
|
+
// clean now; an uncertain failed chunk is skipped because its rows may live.
|
|
218
|
+
const firstDead = committed.length + (uncertain ? 1 : 0);
|
|
219
|
+
await cleanBatchS3(context, chunks.slice(firstDead));
|
|
220
|
+
try {
|
|
221
|
+
await rollbackCommitted(context, append, committed);
|
|
222
|
+
}
|
|
223
|
+
catch (rollbackError) {
|
|
224
|
+
reportStep(context, 'error', 'history.addMessages rollback failed; messageCount may have drifted', { sessionId, committedChunks: committed.length });
|
|
225
|
+
// Skip S3 cleanup here: rollback may have failed, so committed rows might still reference these objects.
|
|
226
|
+
throw (0, errors_1.compensationFailedError)(trigger, (0, base_error_1.toError)(rollbackError));
|
|
227
|
+
}
|
|
228
|
+
// Only now that committed rows are confirmed deleted is it safe to delete their S3 objects.
|
|
229
|
+
await cleanBatchS3(context, chunks.slice(0, committed.length));
|
|
230
|
+
throw trigger;
|
|
231
|
+
}
|
|
232
|
+
/** True for the one failure shape that leaves the outcome ambiguous. */
|
|
233
|
+
function isAmbiguous(error) {
|
|
234
|
+
return (0, base_error_1.hasErrorCode)(error, error_code_1.ErrorCode.RETRY_EXHAUSTED);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Read the chunk's first row back. A chunk commits atomically, so one row
|
|
238
|
+
* present means the whole chunk (and its count `ADD`) landed and only the
|
|
239
|
+
* response was lost.
|
|
240
|
+
*
|
|
241
|
+
* The row's own sort key is what identifies it: message sort keys are per-call
|
|
242
|
+
* ULIDs, so a row at that key can only be this call's own, and its presence is
|
|
243
|
+
* the whole question.
|
|
244
|
+
*/
|
|
245
|
+
async function verifyChunkLanded(context, chunk) {
|
|
246
|
+
const { verdict } = await (0, idempotent_write_1.verifyRow)(context, {
|
|
247
|
+
key: (0, table_schema_1.rowKeyOf)(chunk[0]),
|
|
248
|
+
kind: 'attribute',
|
|
249
|
+
attribute: table_schema_1.SORT_KEY_ATTRIBUTE,
|
|
250
|
+
expected: chunk[0].SK,
|
|
251
|
+
});
|
|
252
|
+
return verdict;
|
|
253
|
+
}
|
|
254
|
+
/** Run one chunk's transaction, returning its error instead of throwing. */
|
|
255
|
+
async function commitChunk(context, append, chunk) {
|
|
256
|
+
try {
|
|
257
|
+
await writeMessageChunk(context, chunk, { ...append.fields, sessionId: append.sessionId, count: chunk.length }, { signal: append.signal });
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
return (0, base_error_1.toError)(error);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function asCommitted(chunk) {
|
|
265
|
+
return { keys: chunk.map((item) => (0, table_schema_1.rowKeyOf)(item)), count: chunk.length };
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Append message chunks with caller-observed atomicity. Each chunk commits its
|
|
269
|
+
* messages and count in one transaction; if a later chunk fails, every
|
|
270
|
+
* already-committed chunk is deleted and its count reverted, and the batch's
|
|
271
|
+
* S3 objects are cleaned once their rows are gone, restoring the pre-call
|
|
272
|
+
* state before the error is rethrown. Except on a failed rollback, which
|
|
273
|
+
* surfaces as `COMPENSATION_FAILED` and deliberately leaves the
|
|
274
|
+
* committed chunks' S3 objects behind, since their rows may survive.
|
|
275
|
+
*
|
|
276
|
+
* A `RETRY_EXHAUSTED` error is ambiguous — the transaction may have committed
|
|
277
|
+
* and lost its response — so the chunk is read back first: present means it
|
|
278
|
+
* committed (continue), absent means it did not (compensate), and a failed
|
|
279
|
+
* read compensates but leaks that chunk's objects rather than delete objects
|
|
280
|
+
* its possibly-live rows reference.
|
|
281
|
+
*
|
|
282
|
+
* Accepts: `append.sessionId` — the session every chunk writes to.
|
|
283
|
+
* `append.chunks` — in order, each already within the transaction's limits;
|
|
284
|
+
* no chunks is no work and no write. `append.fields` — the session update every
|
|
285
|
+
* chunk carries. `append.signal` — aborts between chunks.
|
|
286
|
+
*
|
|
287
|
+
* Returns: nothing, and only when every chunk is known to have committed.
|
|
288
|
+
*
|
|
289
|
+
* Throws: the first chunk's failure, after the rollback has restored the
|
|
290
|
+
* pre-call state; or `COMPENSATION_FAILED` carrying both that failure
|
|
291
|
+
* and the rollback's own, when the rollback could not finish.
|
|
292
|
+
*
|
|
293
|
+
* Guarantees: each message's S3 key carries its own ULID, so no two rows of any
|
|
294
|
+
* call can address the same object and the rollback's cleanup can never delete
|
|
295
|
+
* an object a surviving row still points at. What a caller observes is
|
|
296
|
+
* all-or-nothing; what the table holds is all-or-nothing only until a rollback
|
|
297
|
+
* fails, which is why that case is a distinct error and not a rethrow.
|
|
298
|
+
*/
|
|
299
|
+
async function appendChunks(context, append) {
|
|
300
|
+
const committed = [];
|
|
301
|
+
for (const chunk of append.chunks) {
|
|
302
|
+
const failure = await commitChunk(context, append, chunk);
|
|
303
|
+
if (!failure) {
|
|
304
|
+
committed.push(asCommitted(chunk));
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
const verdict = isAmbiguous(failure)
|
|
308
|
+
? await verifyChunkLanded(context, chunk)
|
|
309
|
+
: 'not-landed';
|
|
310
|
+
if (verdict === 'landed') {
|
|
311
|
+
committed.push(asCommitted(chunk));
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
await compensate(context, append, {
|
|
315
|
+
committed,
|
|
316
|
+
trigger: failure,
|
|
317
|
+
uncertain: verdict === 'unverified',
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** True when a TransactWriteItems cancellation was caused solely by the SESSION update's ttl condition (always TransactItems index 0 — see attempt below), not by any message item. */
|
|
322
|
+
function isTtlConditionLoss(error) {
|
|
323
|
+
return (0, cancellation_1.conditionFailedAt)(error, 0) && (0, cancellation_1.conditionalCheckFailure)(error) !== undefined;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* One send of the chunk, drawing the token that makes its re-sends safe.
|
|
327
|
+
*
|
|
328
|
+
* The message rows are keyed by their own ULIDs, so putting one twice changes
|
|
329
|
+
* nothing; the session update is `ADD #count :n`, and that is the whole of the
|
|
330
|
+
* damage a re-send would do. Applied rather than deduplicated it adds the
|
|
331
|
+
* chunk's count a second time to a row whose messages are already there,
|
|
332
|
+
* nothing on this path reads the count back to notice, and
|
|
333
|
+
* `reconcileMessageCount` is the only repair.
|
|
334
|
+
*
|
|
335
|
+
* Drawn per send rather than per call, because the second send the ttl race
|
|
336
|
+
* triggers is a *different* request: it repeats the same chunk with
|
|
337
|
+
* `forceTtlRefresh: false`, which drops the session update's
|
|
338
|
+
* ConditionExpression, and the same token presented with changed parameters
|
|
339
|
+
* inside the service's window is refused outright. That race is also the one
|
|
340
|
+
* place the precondition on what a token guarantees shows here — the first
|
|
341
|
+
* send was cancelled by its condition, so it committed nothing, nothing was
|
|
342
|
+
* cached for its token, and the second send is a fresh evaluation rather than
|
|
343
|
+
* a replay. The deadline drawn beside the token is what keeps each send's
|
|
344
|
+
* retrying inside the window that send's token is honoured for; the token
|
|
345
|
+
* enforces no window of its own.
|
|
346
|
+
*
|
|
347
|
+
* What each call passes. The deadline is minted here, beside the token,
|
|
348
|
+
* rather than once per `writeMessageChunk`. The ttl race sends the chunk twice
|
|
349
|
+
* and each send draws its own token, so each send is honoured for its own ten
|
|
350
|
+
* minutes and is entitled to a full budget. One deadline per call would hand
|
|
351
|
+
* the second send whatever the first did not spend, silently halving the
|
|
352
|
+
* retrying that matters most — the send made after a race has already been
|
|
353
|
+
* lost. That deadline is a bound on the whole budget, never on the attempt
|
|
354
|
+
* count. `minAttempts` is the contention floor: a caller policy may raise the
|
|
355
|
+
* budget, never lower it. `retry.signal` aborts between attempts, and reaches
|
|
356
|
+
* the SDK request in flight as its `abortSignal`, so an attempt under way is
|
|
357
|
+
* cancelled rather than merely awaited; `retry.rng` replaces the backoff's
|
|
358
|
+
* jitter source.
|
|
359
|
+
*/
|
|
360
|
+
async function attempt(context, items, fields, retry) {
|
|
361
|
+
await (0, idempotent_write_1.transactIdempotently)(context, [
|
|
362
|
+
(0, session_1.buildSessionUpdate)(context.tableName, fields),
|
|
363
|
+
...items.map((item) => ({ Put: { TableName: context.tableName, Item: item } })),
|
|
364
|
+
], { signal: retry.signal, rng: retry.rng, minAttempts: setup_1.MESSAGE_APPEND_RETRY_MAX_ATTEMPTS });
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Atomically write a chunk of message items together with the session-metadata
|
|
368
|
+
* count update in one {@link https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html | TransactWriteItems}
|
|
369
|
+
* call, so `messageCount` can never disagree with the messages that landed. A
|
|
370
|
+
* single `ClientRequestToken` is used per attempt so a re-sent commit (e.g.
|
|
371
|
+
* after a lost response) is idempotent and never double-applies the count
|
|
372
|
+
* `ADD`. When `fields.forceTtlRefresh` is set, the session update carries a
|
|
373
|
+
* monotonic ConditionExpression (see session.ts); if — and only if —
|
|
374
|
+
* that specific condition loses a race against a concurrent caller who just
|
|
375
|
+
* healed the same anchor, this retries the identical chunk once with
|
|
376
|
+
* `forceTtlRefresh: false` (safe: `if_not_exists` then converges to whatever
|
|
377
|
+
* already won) rather than losing the message writes to a benign ttl race. A
|
|
378
|
+
* cancellation caused by any other item (a genuine message-row conflict) is
|
|
379
|
+
* not retried here — it propagates for the normal transient-conflict retry
|
|
380
|
+
* budget inside `withDynamoDBRetry` to handle, or to the caller otherwise.
|
|
381
|
+
*
|
|
382
|
+
* Accepts: `items` — one chunk, already within the transaction's limits.
|
|
383
|
+
* `fields` — the session-metadata update accompanying it; its `indexShards` and
|
|
384
|
+
* its `writeId` are taken from the adapter's context, never from the caller.
|
|
385
|
+
* `retry.signal` — aborts between attempts.
|
|
386
|
+
*
|
|
387
|
+
* Returns: nothing. The chunk and the count are committed together or not at
|
|
388
|
+
* all.
|
|
389
|
+
*
|
|
390
|
+
* Throws: whatever the transaction throws — including a
|
|
391
|
+
* `TransactionCanceledException` for a genuine conflict, after the retry budget
|
|
392
|
+
* is spent. The caller compensates; this function never partially succeeds.
|
|
393
|
+
*
|
|
394
|
+
* Guarantees: `messageCount` can never disagree with the messages that landed,
|
|
395
|
+
* because they land in one transaction. At most one extra attempt is spent on
|
|
396
|
+
* the benign ttl race, and it carries its own request token, so a retry can
|
|
397
|
+
* never double-apply the count — and its own deadline of
|
|
398
|
+
* `MAX_WRITE_LIFETIME_MS`, so the retrying stops while that token is
|
|
399
|
+
* still deduplicating rather than after it has expired. The SESSION row's
|
|
400
|
+
* `writeId` moves if and only if a message row was added: the update travels in
|
|
401
|
+
* the same transaction as the rows, and nothing else writes it.
|
|
402
|
+
*/
|
|
403
|
+
async function writeMessageChunk(context, items, fields, retry = {}) {
|
|
404
|
+
// The index shard comes from the adapter's context, not from the caller's
|
|
405
|
+
// fields, and the write id is drawn here — once per chunk, beside it. Drawn
|
|
406
|
+
// here rather than inside the builder, every attempt of this chunk carries
|
|
407
|
+
// one id, and no caller can supply or reuse one.
|
|
408
|
+
const withIndex = { ...fields, indexShards: context.indexShards, writeId: context.ulid() };
|
|
409
|
+
try {
|
|
410
|
+
await attempt(context, items, withIndex, retry);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
if (fields.forceTtlRefresh && isTtlConditionLoss(error)) {
|
|
414
|
+
await attempt(context, items, { ...withIndex, forceTtlRefresh: false }, retry);
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
throw error;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Per-item allowance added to the measured field bytes to cover what the size
|
|
422
|
+
* estimate does not count directly: DynamoDB attribute names, the document
|
|
423
|
+
* marshalling envelope, and descriptor scaffolding. Deliberately generous so the
|
|
424
|
+
* estimate stays at or above the real marshalled item size and chunks never
|
|
425
|
+
* overshoot the transaction byte limit.
|
|
426
|
+
*/
|
|
427
|
+
const ROW_OVERHEAD_BYTES = 256;
|
|
428
|
+
/**
|
|
429
|
+
* Byte length of a string as DynamoDB stores it. `String.length` counts UTF-16
|
|
430
|
+
* code units, which understates every non-ASCII character — the wrong
|
|
431
|
+
* direction for an estimate documented to sit at or above the real size.
|
|
432
|
+
*/
|
|
433
|
+
function utf8Bytes(value) {
|
|
434
|
+
return Buffer.byteLength(value, 'utf8');
|
|
435
|
+
}
|
|
436
|
+
function descriptorBytes(descriptor) {
|
|
437
|
+
const body = descriptor.location === codec_1.PayloadLocation.INLINE
|
|
438
|
+
? descriptor.bytes.length
|
|
439
|
+
: utf8Bytes(descriptor.s3Key);
|
|
440
|
+
return body + utf8Bytes(descriptor.serdeType);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Conservatively estimate a message row's stored size.
|
|
444
|
+
*
|
|
445
|
+
* Accepts: any message row, inline or offloaded — an offloaded one measures
|
|
446
|
+
* its S3 key, since that is what the row actually carries.
|
|
447
|
+
*
|
|
448
|
+
* Returns: an estimate at or above the real marshalled size. Erring high is the
|
|
449
|
+
* whole point: an underestimate builds a transaction DynamoDB refuses, and the
|
|
450
|
+
* cost of erring high is one extra transaction.
|
|
451
|
+
*
|
|
452
|
+
* Throws: nothing.
|
|
453
|
+
*/
|
|
454
|
+
function estimateRowBytes(item) {
|
|
455
|
+
return (utf8Bytes(item.PK) +
|
|
456
|
+
utf8Bytes(item.SK) +
|
|
457
|
+
utf8Bytes(item.sessionId) +
|
|
458
|
+
descriptorBytes(item.message) +
|
|
459
|
+
ROW_OVERHEAD_BYTES);
|
|
460
|
+
}
|
|
461
|
+
function shouldFlush(chunk, next, limits) {
|
|
462
|
+
if (chunk.count === 0)
|
|
463
|
+
return false;
|
|
464
|
+
return chunk.count >= limits.maxItems || chunk.bytes + next > limits.maxBytes;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Split message items into transaction-sized chunks.
|
|
468
|
+
*
|
|
469
|
+
* Accepts: `items` — in order; empty yields no chunks, so an append of nothing
|
|
470
|
+
* issues no write. `maxItems` and `maxBytes` — the transaction's two limits,
|
|
471
|
+
* both binding.
|
|
472
|
+
*
|
|
473
|
+
* Returns: the chunks, in order, each within both limits — except that a single
|
|
474
|
+
* item larger than `maxBytes` is placed alone rather than dropped: refusing it
|
|
475
|
+
* here would lose a message that DynamoDB might still accept, and if it does
|
|
476
|
+
* not, the transaction says so.
|
|
477
|
+
*
|
|
478
|
+
* Throws: nothing.
|
|
479
|
+
*
|
|
480
|
+
* Guarantees: order is preserved across chunks, so messages keep the order the
|
|
481
|
+
* caller wrote them in, which is the order their ULIDs already encode.
|
|
482
|
+
*/
|
|
483
|
+
function chunkBySize(items, maxItems, maxBytes) {
|
|
484
|
+
const chunks = [];
|
|
485
|
+
let current = [];
|
|
486
|
+
let currentBytes = 0;
|
|
487
|
+
for (const item of items) {
|
|
488
|
+
const size = estimateRowBytes(item);
|
|
489
|
+
if (shouldFlush({ count: current.length, bytes: currentBytes }, size, { maxItems, maxBytes })) {
|
|
490
|
+
chunks.push(current);
|
|
491
|
+
current = [];
|
|
492
|
+
currentBytes = 0;
|
|
493
|
+
}
|
|
494
|
+
current.push(item);
|
|
495
|
+
currentBytes += size;
|
|
496
|
+
}
|
|
497
|
+
if (current.length > 0)
|
|
498
|
+
chunks.push(current);
|
|
499
|
+
return chunks;
|
|
500
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hides how a session's message key space is read.
|
|
3
|
+
*
|
|
4
|
+
* A read admits only rows this package wrote as a message of this session,
|
|
5
|
+
* refusing any other row in the key space rather than skipping it, treats an
|
|
6
|
+
* expired row as absent however long the sweep lags, walks newest-first when
|
|
7
|
+
* it wants the latest few and restores chronological order, and counts the
|
|
8
|
+
* rows the same way it reads them, so a repaired count is the number a read
|
|
9
|
+
* returns.
|
|
10
|
+
*/
|
|
11
|
+
import type { ParsedWindow, SessionId } from './parse';
|
|
12
|
+
import { type MessageRow } from './rows';
|
|
13
|
+
import type { HistoryContext } from './setup';
|
|
14
|
+
/**
|
|
15
|
+
* Read the live message items a window selects, in chronological order.
|
|
16
|
+
*
|
|
17
|
+
* Without `limit` the query walks the session oldest-first. With it the query
|
|
18
|
+
* walks newest-first with a matching page cap and stops as soon as `limit`
|
|
19
|
+
* live items are in hand — rows past their TTL are skipped here, so a page can
|
|
20
|
+
* come back short and the walk simply continues — and the tail is then
|
|
21
|
+
* reversed back into chronological order. `before` becomes an exclusive upper
|
|
22
|
+
* sort-key bound: the message prefix plus the ULID time characters of that
|
|
23
|
+
* instant, which every message id from that millisecond onwards sorts after.
|
|
24
|
+
*
|
|
25
|
+
* Accepts: `window.limit` — absent asks for the whole session, which is what
|
|
26
|
+
* `getMessages()` with no arguments means; otherwise at least 1, which is why
|
|
27
|
+
* no zero case is handled below. `parseMessageWindow` refuses `0` for its
|
|
28
|
+
* own reason, and that refusal is also what keeps `Limit: 0` — which DynamoDB
|
|
29
|
+
* rejects outright with a raw `ValidationException` — out of the query built
|
|
30
|
+
* here. `window.before` — already parsed as a real date. `signal` — aborts
|
|
31
|
+
* between pages.
|
|
32
|
+
*
|
|
33
|
+
* Returns: the live messages in chronological order, oldest first, whichever
|
|
34
|
+
* direction the query walked.
|
|
35
|
+
*
|
|
36
|
+
* Throws: `FORMAT_UNSUPPORTED` for a row a newer version wrote — checked before
|
|
37
|
+
* the row's ttl, so the answer does not depend on the reading machine's clock;
|
|
38
|
+
* `VALIDATION` naming `message` for a row in this session's message key
|
|
39
|
+
* space that this adapter did not write, checked before the ttl for the same
|
|
40
|
+
* reason; `ABORTED`; whatever the query throws.
|
|
41
|
+
*
|
|
42
|
+
* Guarantees: strongly consistent, so the turn just appended is visible to the
|
|
43
|
+
* very next read. An unlimited window is deliberately uncapped — silently
|
|
44
|
+
* truncating a conversation is worse than a slow read, and a caller that wants
|
|
45
|
+
* a bound passes `limit` — so past {@link LIST_SCAN_WARN_THRESHOLD} messages
|
|
46
|
+
* the read still succeeds and an operator is told the session is unusually
|
|
47
|
+
* large.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readWindow(context: HistoryContext, sessionId: SessionId, window: ParsedWindow, signal?: AbortSignal): Promise<MessageRow[]>;
|
|
50
|
+
/**
|
|
51
|
+
* The number of message rows a session holds, counted the way the read path
|
|
52
|
+
* counts them.
|
|
53
|
+
*
|
|
54
|
+
* Only rows `getMessages` would return are counted: an expired message that
|
|
55
|
+
* DynamoDB's TTL sweep has not yet removed is invisible to every reader, so
|
|
56
|
+
* counting it would "repair" `messageCount` to a number nobody ever sees. The
|
|
57
|
+
* count is therefore a definition, not an implementation detail — it is what
|
|
58
|
+
* makes the repaired value agree with what the session returns. For the same
|
|
59
|
+
* reason a message a newer release wrote, and a row in the message key space
|
|
60
|
+
* that this adapter did not write, are both refused rather than counted:
|
|
61
|
+
* `getMessages` refuses them.
|
|
62
|
+
*
|
|
63
|
+
* Accepts: `sessionId` — validated by the caller. `signal` — aborts the reads.
|
|
64
|
+
*
|
|
65
|
+
* Returns: how many messages a reader would actually see right now.
|
|
66
|
+
*
|
|
67
|
+
* Throws: `FORMAT_UNSUPPORTED` for a message row a newer release wrote, checked
|
|
68
|
+
* before the row's ttl as `getMessages` checks it; `VALIDATION` naming
|
|
69
|
+
* `message` for a row in the message key space that is not one of this
|
|
70
|
+
* adapter's, which `getMessages` refuses too; whatever the query throws
|
|
71
|
+
* after retries; `ABORTED`.
|
|
72
|
+
*
|
|
73
|
+
* Guarantees: each row comes back projected to its identity, its format
|
|
74
|
+
* version and its ttl, so no message payload is transferred however large the
|
|
75
|
+
* session is — the descriptor is projected by the one nested path
|
|
76
|
+
* `message.location` that every descriptor this package has written carries,
|
|
77
|
+
* which proves the attribute is there and a map without reading the bytes it
|
|
78
|
+
* holds. Every check runs here rather than in a filter, because a filter would
|
|
79
|
+
* drop an expired row before its version could be checked. The pages are
|
|
80
|
+
* walked by the shared paginator with both of its caps lifted: a partial count
|
|
81
|
+
* is not a repair, it is a new and wrong number, so the count either completes
|
|
82
|
+
* or fails.
|
|
83
|
+
*/
|
|
84
|
+
export declare function countLiveMessages(context: HistoryContext, sessionId: SessionId, signal?: AbortSignal): Promise<number>;
|