@backblaze-labs/b2-sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -1
- package/README.md +20 -2
- package/dist/_virtual/_b2-sdk-version-json.cjs +1 -1
- package/dist/_virtual/_b2-sdk-version-json.js +1 -1
- package/dist/auth/file.cjs.map +1 -1
- package/dist/backup/client.cjs +206 -0
- package/dist/backup/client.cjs.map +1 -0
- package/dist/backup/client.d.cts +238 -0
- package/dist/backup/client.d.cts.map +1 -0
- package/dist/backup/client.d.ts +238 -0
- package/dist/backup/client.d.ts.map +1 -0
- package/dist/backup/client.js +206 -0
- package/dist/backup/client.js.map +1 -0
- package/dist/backup/index.cjs +15 -0
- package/dist/backup/index.d.cts +22 -0
- package/dist/backup/index.d.cts.map +1 -0
- package/dist/backup/index.d.ts +22 -0
- package/dist/backup/index.d.ts.map +1 -0
- package/dist/backup/index.js +7 -0
- package/dist/backup/raw.cjs +146 -0
- package/dist/backup/raw.cjs.map +1 -0
- package/dist/backup/raw.d.cts +122 -0
- package/dist/backup/raw.d.cts.map +1 -0
- package/dist/backup/raw.d.ts +122 -0
- package/dist/backup/raw.d.ts.map +1 -0
- package/dist/backup/raw.js +146 -0
- package/dist/backup/raw.js.map +1 -0
- package/dist/bucket.cjs +2 -1
- package/dist/bucket.cjs.map +1 -1
- package/dist/bucket.js +2 -1
- package/dist/bucket.js.map +1 -1
- package/dist/copy/large.cjs +5 -3
- package/dist/copy/large.cjs.map +1 -1
- package/dist/copy/large.d.cts.map +1 -1
- package/dist/copy/large.d.ts.map +1 -1
- package/dist/copy/large.js +5 -3
- package/dist/copy/large.js.map +1 -1
- package/dist/download/checksum.cjs +6 -2
- package/dist/download/checksum.cjs.map +1 -1
- package/dist/download/checksum.js +6 -2
- package/dist/download/checksum.js.map +1 -1
- package/dist/download/parallel.cjs +52 -15
- package/dist/download/parallel.cjs.map +1 -1
- package/dist/download/parallel.d.cts +8 -1
- package/dist/download/parallel.d.cts.map +1 -1
- package/dist/download/parallel.d.ts +8 -1
- package/dist/download/parallel.d.ts.map +1 -1
- package/dist/download/parallel.js +52 -15
- package/dist/download/parallel.js.map +1 -1
- package/dist/download/single.cjs +141 -6
- package/dist/download/single.cjs.map +1 -1
- package/dist/download/single.d.cts.map +1 -1
- package/dist/download/single.d.ts.map +1 -1
- package/dist/download/single.js +141 -6
- package/dist/download/single.js.map +1 -1
- package/dist/errors/index.cjs +173 -3
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +119 -0
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +119 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +161 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/http/retry.cjs +1 -1
- package/dist/http/retry.cjs.map +1 -1
- package/dist/http/retry.js +1 -1
- package/dist/http/retry.js.map +1 -1
- package/dist/http/transport.cjs +61 -23
- package/dist/http/transport.cjs.map +1 -1
- package/dist/http/transport.d.cts +25 -3
- package/dist/http/transport.d.cts.map +1 -1
- package/dist/http/transport.d.ts +25 -3
- package/dist/http/transport.d.ts.map +1 -1
- package/dist/http/transport.js +59 -22
- package/dist/http/transport.js.map +1 -1
- package/dist/index.cjs +25 -2
- package/dist/index.d.cts +6 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/object.cjs +1 -1
- package/dist/object.cjs.map +1 -1
- package/dist/object.d.cts +4 -2
- package/dist/object.d.cts.map +1 -1
- package/dist/object.d.ts +4 -2
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +1 -1
- package/dist/object.js.map +1 -1
- package/dist/partner/account-info.d.cts +43 -0
- package/dist/partner/account-info.d.cts.map +1 -0
- package/dist/partner/account-info.d.ts +43 -0
- package/dist/partner/account-info.d.ts.map +1 -0
- package/dist/partner/auth-clone.cjs +64 -0
- package/dist/partner/auth-clone.cjs.map +1 -0
- package/dist/partner/auth-clone.d.cts +27 -0
- package/dist/partner/auth-clone.d.cts.map +1 -0
- package/dist/partner/auth-clone.d.ts +27 -0
- package/dist/partner/auth-clone.d.ts.map +1 -0
- package/dist/partner/auth-clone.js +63 -0
- package/dist/partner/auth-clone.js.map +1 -0
- package/dist/partner/auth-core.cjs +175 -0
- package/dist/partner/auth-core.cjs.map +1 -0
- package/dist/partner/auth-core.d.cts +111 -0
- package/dist/partner/auth-core.d.cts.map +1 -0
- package/dist/partner/auth-core.d.ts +111 -0
- package/dist/partner/auth-core.d.ts.map +1 -0
- package/dist/partner/auth-core.js +175 -0
- package/dist/partner/auth-core.js.map +1 -0
- package/dist/partner/auth-shape.cjs +36 -0
- package/dist/partner/auth-shape.cjs.map +1 -0
- package/dist/partner/auth-shape.d.cts +12 -0
- package/dist/partner/auth-shape.d.cts.map +1 -0
- package/dist/partner/auth-shape.d.ts +12 -0
- package/dist/partner/auth-shape.d.ts.map +1 -0
- package/dist/partner/auth-shape.js +36 -0
- package/dist/partner/auth-shape.js.map +1 -0
- package/dist/partner/client.cjs +255 -0
- package/dist/partner/client.cjs.map +1 -0
- package/dist/partner/client.d.cts +335 -0
- package/dist/partner/client.d.cts.map +1 -0
- package/dist/partner/client.d.ts +335 -0
- package/dist/partner/client.d.ts.map +1 -0
- package/dist/partner/client.js +255 -0
- package/dist/partner/client.js.map +1 -0
- package/dist/partner/endpoint-url.cjs +108 -0
- package/dist/partner/endpoint-url.cjs.map +1 -0
- package/dist/partner/endpoint-url.d.cts +72 -0
- package/dist/partner/endpoint-url.d.cts.map +1 -0
- package/dist/partner/endpoint-url.d.ts +72 -0
- package/dist/partner/endpoint-url.d.ts.map +1 -0
- package/dist/partner/endpoint-url.js +105 -0
- package/dist/partner/endpoint-url.js.map +1 -0
- package/dist/partner/in-memory.cjs +135 -0
- package/dist/partner/in-memory.cjs.map +1 -0
- package/dist/partner/in-memory.d.cts +99 -0
- package/dist/partner/in-memory.d.cts.map +1 -0
- package/dist/partner/in-memory.d.ts +99 -0
- package/dist/partner/in-memory.d.ts.map +1 -0
- package/dist/partner/in-memory.js +135 -0
- package/dist/partner/in-memory.js.map +1 -0
- package/dist/partner/index.cjs +18 -0
- package/dist/partner/index.d.cts +23 -0
- package/dist/partner/index.d.cts.map +1 -0
- package/dist/partner/index.d.ts +23 -0
- package/dist/partner/index.d.ts.map +1 -0
- package/dist/partner/index.js +7 -0
- package/dist/partner/raw.cjs +392 -0
- package/dist/partner/raw.cjs.map +1 -0
- package/dist/partner/raw.d.cts +223 -0
- package/dist/partner/raw.d.cts.map +1 -0
- package/dist/partner/raw.d.ts +223 -0
- package/dist/partner/raw.d.ts.map +1 -0
- package/dist/partner/raw.js +390 -0
- package/dist/partner/raw.js.map +1 -0
- package/dist/partner/redaction-placeholders.cjs +10 -0
- package/dist/partner/redaction-placeholders.cjs.map +1 -0
- package/dist/partner/redaction-placeholders.d.cts +5 -0
- package/dist/partner/redaction-placeholders.d.cts.map +1 -0
- package/dist/partner/redaction-placeholders.d.ts +5 -0
- package/dist/partner/redaction-placeholders.d.ts.map +1 -0
- package/dist/partner/redaction-placeholders.js +9 -0
- package/dist/partner/redaction-placeholders.js.map +1 -0
- package/dist/partner/redaction.cjs +267 -0
- package/dist/partner/redaction.cjs.map +1 -0
- package/dist/partner/redaction.d.cts +124 -0
- package/dist/partner/redaction.d.cts.map +1 -0
- package/dist/partner/redaction.d.ts +124 -0
- package/dist/partner/redaction.d.ts.map +1 -0
- package/dist/partner/redaction.js +257 -0
- package/dist/partner/redaction.js.map +1 -0
- package/dist/raw/index.cjs +81 -19
- package/dist/raw/index.cjs.map +1 -1
- package/dist/raw/index.d.cts +6 -2
- package/dist/raw/index.d.cts.map +1 -1
- package/dist/raw/index.d.ts +6 -2
- package/dist/raw/index.d.ts.map +1 -1
- package/dist/raw/index.js +81 -19
- package/dist/raw/index.js.map +1 -1
- package/dist/raw/url.cjs +111 -0
- package/dist/raw/url.cjs.map +1 -0
- package/dist/raw/url.d.cts +41 -0
- package/dist/raw/url.d.cts.map +1 -0
- package/dist/raw/url.d.ts +41 -0
- package/dist/raw/url.d.ts.map +1 -0
- package/dist/raw/url.js +110 -0
- package/dist/raw/url.js.map +1 -0
- package/dist/s3/sigv4.cjs +15 -10
- package/dist/s3/sigv4.cjs.map +1 -1
- package/dist/s3/sigv4.js +15 -10
- package/dist/s3/sigv4.js.map +1 -1
- package/dist/simulator/index.cjs +1197 -206
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.d.cts +109 -11
- package/dist/simulator/index.d.cts.map +1 -1
- package/dist/simulator/index.d.ts +109 -11
- package/dist/simulator/index.d.ts.map +1 -1
- package/dist/simulator/index.js +1196 -209
- package/dist/simulator/index.js.map +1 -1
- package/dist/simulator/partner.cjs +719 -0
- package/dist/simulator/partner.cjs.map +1 -0
- package/dist/simulator/partner.d.cts +183 -0
- package/dist/simulator/partner.d.cts.map +1 -0
- package/dist/simulator/partner.d.ts +183 -0
- package/dist/simulator/partner.d.ts.map +1 -0
- package/dist/simulator/partner.js +718 -0
- package/dist/simulator/partner.js.map +1 -0
- package/dist/simulator/validation.cjs +577 -0
- package/dist/simulator/validation.cjs.map +1 -1
- package/dist/simulator/validation.d.cts +117 -0
- package/dist/simulator/validation.d.cts.map +1 -1
- package/dist/simulator/validation.d.ts +117 -0
- package/dist/simulator/validation.d.ts.map +1 -1
- package/dist/simulator/validation.js +563 -1
- package/dist/simulator/validation.js.map +1 -1
- package/dist/streams/file-source.cjs +1 -1
- package/dist/streams/file-source.cjs.map +1 -1
- package/dist/streams/file-source.js +1 -1
- package/dist/streams/file-source.js.map +1 -1
- package/dist/streams/index.cjs +1 -1
- package/dist/streams/index.js +1 -1
- package/dist/streams/source.cjs +2 -1
- package/dist/streams/source.cjs.map +1 -1
- package/dist/streams/source.js +2 -1
- package/dist/streams/source.js.map +1 -1
- package/dist/sync/b2-sha1-reader.cjs +6 -2
- package/dist/sync/b2-sha1-reader.cjs.map +1 -1
- package/dist/sync/b2-sha1-reader.d.cts.map +1 -1
- package/dist/sync/b2-sha1-reader.d.ts.map +1 -1
- package/dist/sync/b2-sha1-reader.js +6 -2
- package/dist/sync/b2-sha1-reader.js.map +1 -1
- package/dist/sync/download-staging.cjs +11 -10
- package/dist/sync/download-staging.cjs.map +1 -1
- package/dist/sync/download-staging.d.cts +2 -1
- package/dist/sync/download-staging.d.cts.map +1 -1
- package/dist/sync/download-staging.d.ts +2 -1
- package/dist/sync/download-staging.d.ts.map +1 -1
- package/dist/sync/download-staging.js +11 -10
- package/dist/sync/download-staging.js.map +1 -1
- package/dist/sync/filters.cjs.map +1 -1
- package/dist/sync/filters.js.map +1 -1
- package/dist/sync/local-file-io.cjs +11 -6
- package/dist/sync/local-file-io.cjs.map +1 -1
- package/dist/sync/local-file-io.d.cts +1 -0
- package/dist/sync/local-file-io.d.cts.map +1 -1
- package/dist/sync/local-file-io.d.ts +1 -0
- package/dist/sync/local-file-io.d.ts.map +1 -1
- package/dist/sync/local-file-io.js +11 -6
- package/dist/sync/local-file-io.js.map +1 -1
- package/dist/sync/local-sha1.cjs +6 -15
- package/dist/sync/local-sha1.cjs.map +1 -1
- package/dist/sync/local-sha1.d.cts +0 -8
- package/dist/sync/local-sha1.d.cts.map +1 -1
- package/dist/sync/local-sha1.d.ts +0 -8
- package/dist/sync/local-sha1.d.ts.map +1 -1
- package/dist/sync/local-sha1.js +7 -15
- package/dist/sync/local-sha1.js.map +1 -1
- package/dist/sync/policies/compare.cjs +5 -3
- package/dist/sync/policies/compare.cjs.map +1 -1
- package/dist/sync/policies/compare.d.cts.map +1 -1
- package/dist/sync/policies/compare.d.ts.map +1 -1
- package/dist/sync/policies/compare.js +6 -4
- package/dist/sync/policies/compare.js.map +1 -1
- package/dist/sync/policies/index.cjs +4 -10
- package/dist/sync/policies/index.cjs.map +1 -1
- package/dist/sync/policies/index.js +4 -10
- package/dist/sync/policies/index.js.map +1 -1
- package/dist/sync/scanners/b2.cjs +2 -2
- package/dist/sync/scanners/b2.cjs.map +1 -1
- package/dist/sync/scanners/b2.js +2 -2
- package/dist/sync/scanners/b2.js.map +1 -1
- package/dist/sync/scanners/local.cjs.map +1 -1
- package/dist/sync/scanners/local.js.map +1 -1
- package/dist/sync/synchronizer.cjs +3 -2
- package/dist/sync/synchronizer.cjs.map +1 -1
- package/dist/sync/synchronizer.js +3 -2
- package/dist/sync/synchronizer.js.map +1 -1
- package/dist/types/auth.cjs +4 -0
- package/dist/types/auth.cjs.map +1 -1
- package/dist/types/auth.d.cts +4 -0
- package/dist/types/auth.d.cts.map +1 -1
- package/dist/types/auth.d.ts +4 -0
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/auth.js +4 -0
- package/dist/types/auth.js.map +1 -1
- package/dist/types/backup.d.cts +80 -0
- package/dist/types/backup.d.cts.map +1 -0
- package/dist/types/backup.d.ts +80 -0
- package/dist/types/backup.d.ts.map +1 -0
- package/dist/types/bucket.cjs.map +1 -1
- package/dist/types/bucket.d.cts +4 -2
- package/dist/types/bucket.d.cts.map +1 -1
- package/dist/types/bucket.d.ts +4 -2
- package/dist/types/bucket.d.ts.map +1 -1
- package/dist/types/bucket.js.map +1 -1
- package/dist/types/download.cjs +62 -0
- package/dist/types/download.cjs.map +1 -0
- package/dist/types/download.d.cts +98 -0
- package/dist/types/download.d.cts.map +1 -1
- package/dist/types/download.d.ts +98 -0
- package/dist/types/download.d.ts.map +1 -1
- package/dist/types/download.js +61 -0
- package/dist/types/download.js.map +1 -0
- package/dist/types/encryption.cjs +58 -229
- package/dist/types/encryption.cjs.map +1 -1
- package/dist/types/encryption.d.cts +32 -0
- package/dist/types/encryption.d.cts.map +1 -1
- package/dist/types/encryption.d.ts +32 -0
- package/dist/types/encryption.d.ts.map +1 -1
- package/dist/types/encryption.js +56 -229
- package/dist/types/encryption.js.map +1 -1
- package/dist/types/errors.cjs +9 -0
- package/dist/types/errors.cjs.map +1 -1
- package/dist/types/errors.d.cts +1 -1
- package/dist/types/errors.d.cts.map +1 -1
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +9 -0
- package/dist/types/errors.js.map +1 -1
- package/dist/types/file.cjs.map +1 -1
- package/dist/types/file.d.cts +15 -2
- package/dist/types/file.d.cts.map +1 -1
- package/dist/types/file.d.ts +15 -2
- package/dist/types/file.d.ts.map +1 -1
- package/dist/types/file.js.map +1 -1
- package/dist/types/ids.cjs +30 -0
- package/dist/types/ids.cjs.map +1 -1
- package/dist/types/ids.d.cts +32 -0
- package/dist/types/ids.d.cts.map +1 -1
- package/dist/types/ids.d.ts +32 -0
- package/dist/types/ids.d.ts.map +1 -1
- package/dist/types/ids.js +28 -1
- package/dist/types/ids.js.map +1 -1
- package/dist/types/index.d.cts +10 -6
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +10 -6
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lock.cjs.map +1 -1
- package/dist/types/lock.d.cts +14 -0
- package/dist/types/lock.d.cts.map +1 -1
- package/dist/types/lock.d.ts +14 -0
- package/dist/types/lock.d.ts.map +1 -1
- package/dist/types/lock.js.map +1 -1
- package/dist/types/notifications.cjs +1 -1
- package/dist/types/notifications.cjs.map +1 -1
- package/dist/types/notifications.d.cts +5 -3
- package/dist/types/notifications.d.cts.map +1 -1
- package/dist/types/notifications.d.ts +5 -3
- package/dist/types/notifications.d.ts.map +1 -1
- package/dist/types/notifications.js +1 -1
- package/dist/types/notifications.js.map +1 -1
- package/dist/types/partner.cjs +32 -0
- package/dist/types/partner.cjs.map +1 -0
- package/dist/types/partner.d.cts +394 -0
- package/dist/types/partner.d.cts.map +1 -0
- package/dist/types/partner.d.ts +394 -0
- package/dist/types/partner.d.ts.map +1 -0
- package/dist/types/partner.js +29 -0
- package/dist/types/partner.js.map +1 -0
- package/dist/types/upload.d.cts +31 -18
- package/dist/types/upload.d.cts.map +1 -1
- package/dist/types/upload.d.ts +31 -18
- package/dist/types/upload.d.ts.map +1 -1
- package/dist/upload/abort-scope.cjs +0 -45
- package/dist/upload/abort-scope.cjs.map +1 -1
- package/dist/upload/abort-scope.d.cts +0 -22
- package/dist/upload/abort-scope.d.cts.map +1 -1
- package/dist/upload/abort-scope.d.ts +0 -22
- package/dist/upload/abort-scope.d.ts.map +1 -1
- package/dist/upload/abort-scope.js +1 -44
- package/dist/upload/abort-scope.js.map +1 -1
- package/dist/upload/finish.cjs +3 -8
- package/dist/upload/finish.cjs.map +1 -1
- package/dist/upload/finish.d.cts.map +1 -1
- package/dist/upload/finish.d.ts.map +1 -1
- package/dist/upload/finish.js +1 -6
- package/dist/upload/finish.js.map +1 -1
- package/dist/upload/large.cjs +5 -1
- package/dist/upload/large.cjs.map +1 -1
- package/dist/upload/large.d.cts +16 -2
- package/dist/upload/large.d.cts.map +1 -1
- package/dist/upload/large.d.ts +16 -2
- package/dist/upload/large.d.ts.map +1 -1
- package/dist/upload/large.js +5 -1
- package/dist/upload/large.js.map +1 -1
- package/dist/upload/options.cjs.map +1 -1
- package/dist/upload/options.d.cts +12 -0
- package/dist/upload/options.d.cts.map +1 -1
- package/dist/upload/options.d.ts +12 -0
- package/dist/upload/options.d.ts.map +1 -1
- package/dist/upload/options.js.map +1 -1
- package/dist/upload/resume.cjs +2 -1
- package/dist/upload/resume.cjs.map +1 -1
- package/dist/upload/resume.d.cts +3 -1
- package/dist/upload/resume.d.cts.map +1 -1
- package/dist/upload/resume.d.ts +3 -1
- package/dist/upload/resume.d.ts.map +1 -1
- package/dist/upload/resume.js +2 -1
- package/dist/upload/resume.js.map +1 -1
- package/dist/upload/retry.cjs +8 -3
- package/dist/upload/retry.cjs.map +1 -1
- package/dist/upload/retry.js +8 -3
- package/dist/upload/retry.js.map +1 -1
- package/dist/upload/single.cjs +2 -1
- package/dist/upload/single.cjs.map +1 -1
- package/dist/upload/single.d.cts +6 -0
- package/dist/upload/single.d.cts.map +1 -1
- package/dist/upload/single.d.ts +6 -0
- package/dist/upload/single.d.ts.map +1 -1
- package/dist/upload/single.js +2 -1
- package/dist/upload/single.js.map +1 -1
- package/dist/upload/stream.cjs +6 -4
- package/dist/upload/stream.cjs.map +1 -1
- package/dist/upload/stream.d.cts +6 -0
- package/dist/upload/stream.d.cts.map +1 -1
- package/dist/upload/stream.d.ts +6 -0
- package/dist/upload/stream.d.ts.map +1 -1
- package/dist/upload/stream.js +3 -1
- package/dist/upload/stream.js.map +1 -1
- package/dist/util/abort.cjs +114 -0
- package/dist/util/abort.cjs.map +1 -0
- package/dist/util/abort.d.cts +65 -0
- package/dist/util/abort.d.cts.map +1 -0
- package/dist/util/abort.d.ts +65 -0
- package/dist/util/abort.d.ts.map +1 -0
- package/dist/util/abort.js +109 -0
- package/dist/util/abort.js.map +1 -0
- package/dist/util/defaults.cjs +3 -0
- package/dist/util/defaults.cjs.map +1 -1
- package/dist/util/defaults.d.cts +2 -0
- package/dist/util/defaults.d.cts.map +1 -1
- package/dist/util/defaults.d.ts +2 -0
- package/dist/util/defaults.d.ts.map +1 -1
- package/dist/util/defaults.js +3 -1
- package/dist/util/defaults.js.map +1 -1
- package/dist/util/md5.cjs +211 -0
- package/dist/util/md5.cjs.map +1 -0
- package/dist/util/md5.d.cts +43 -0
- package/dist/util/md5.d.cts.map +1 -0
- package/dist/util/md5.d.ts +43 -0
- package/dist/util/md5.d.ts.map +1 -0
- package/dist/util/md5.js +209 -0
- package/dist/util/md5.js.map +1 -0
- package/dist/util/normalize.cjs +5 -5
- package/dist/util/normalize.cjs.map +1 -1
- package/dist/util/normalize.d.cts +7 -7
- package/dist/util/normalize.d.cts.map +1 -1
- package/dist/util/normalize.d.ts +7 -7
- package/dist/util/normalize.d.ts.map +1 -1
- package/dist/util/normalize.js +5 -5
- package/dist/util/normalize.js.map +1 -1
- package/dist/util/paginator.cjs +7 -1
- package/dist/util/paginator.cjs.map +1 -1
- package/dist/util/paginator.d.cts +3 -1
- package/dist/util/paginator.d.cts.map +1 -1
- package/dist/util/paginator.d.ts +3 -1
- package/dist/util/paginator.d.ts.map +1 -1
- package/dist/util/paginator.js +7 -1
- package/dist/util/paginator.js.map +1 -1
- package/package.json +38 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"options.js","names":[],"sources":["../../src/upload/options.ts"],"sourcesContent":["import type { ProgressListener } from '../streams/progress.ts'\nimport type { ContentSource } from '../streams/source.ts'\nimport type { EncryptionSetting } from '../types/encryption.ts'\nimport type { LargeFileId } from '../types/ids.ts'\nimport type { FileRetentionValue, LegalHoldValue } from '../types/lock.ts'\nimport type { CleanupFailureOptions } from './cancel.ts'\nimport type { ResumePartReusedListener } from './large.ts'\nimport type { ResumeCandidateRejectedListener } from './resume.ts'\nimport type { UploadRetryOptions } from './retry.ts'\n\n/** Shared options for high-level file uploads. */\nexport interface HighLevelUploadOptions extends UploadRetryOptions, CleanupFailureOptions {\n /**\n * Data source to upload. Use {@link BufferSource}, {@link BlobSource}, {@link FileSource},\n * {@link StreamSource}, or {@link toContentSource} for async iterables and Node Readables.\n */\n readonly source: ContentSource\n /** MIME type. Defaults to `\"b2/x-auto\"` (auto-detected by B2). */\n readonly contentType?: string\n /** Custom key-value metadata stored with the file. */\n readonly fileInfo?: Record<string, string>\n /** Server-side encryption settings. */\n readonly serverSideEncryption?: EncryptionSetting\n /** File retention policy (requires file lock on the bucket). */\n readonly fileRetention?: FileRetentionValue\n /** Legal hold status for the file. */\n readonly legalHold?: LegalHoldValue\n /** Last-modified timestamp in milliseconds since epoch. */\n readonly lastModifiedMillis?: number\n /** Part size override for multipart uploads, in bytes. */\n readonly partSize?: number\n /** Number of concurrent part uploads for large files. */\n readonly concurrency?: number\n /** Callback invoked with upload progress events. */\n readonly onProgress?: ProgressListener\n /** Abort signal for cancelling the upload. */\n readonly signal?: AbortSignal\n /** Enable bounded same-name multipart resume discovery. Ignored on the small-file path. */\n readonly resume?: boolean\n /** Optional aggregate SDK-enforced timeout for resume discovery. */\n readonly resumeDiscoveryTimeoutMs?: number\n /** Maximum `b2_list_unfinished_large_files` pages inspected before upload starts. */\n readonly resumeMaxListPages?: number\n /** Maximum metadata-compatible candidates whose parts may be listed before upload starts. */\n readonly resumeMaxPartCandidates?: number\n /** Maximum `b2_list_parts` pages inspected per metadata-compatible candidate. */\n readonly resumeMaxPartPages?: number\n /**\n * Explicit unfinished large-file ID to verify and resume. Only supported on\n * the large-file path; small-file uploads throw.\n */\n readonly resumeFileId?: LargeFileId\n /** Diagnostic callback invoked when resume discovery rejects a candidate. */\n readonly onResumeCandidateRejected?: ResumeCandidateRejectedListener\n /** Diagnostic callback invoked when resume reuses an already-uploaded part. */\n readonly onResumePartReused?: ResumePartReusedListener\n}\n\n/** Backwards-compatible name for high-level upload options. */\nexport type UploadOptions = HighLevelUploadOptions\n\n/** Options accepted by {@link Bucket.upload}. */\nexport interface BucketUploadOptions extends HighLevelUploadOptions {\n /** Destination file name (path) in the bucket. */\n readonly fileName: string\n}\n\n/** Options accepted by {@link B2Object.upload}. */\nexport type B2ObjectUploadOptions = HighLevelUploadOptions\n\n/** Options accepted by {@link B2Object.createWriteStream}. */\nexport interface B2ObjectWriteStreamOptions extends UploadRetryOptions, CleanupFailureOptions {\n /** MIME type. Defaults to `\"b2/x-auto\"`. */\n readonly contentType?: string\n /** Custom key-value metadata stored with the file. */\n readonly fileInfo?: Record<string, string>\n /** Server-side encryption applied to each part. */\n readonly serverSideEncryption?: EncryptionSetting\n /** Target part size in bytes. Defaults to the account's recommended part size. */\n readonly partSize?: number\n /** Maximum number of parts uploaded in parallel. Defaults to 4. */\n readonly concurrency?: number\n /** Callback invoked with upload progress events. */\n readonly onProgress?: ProgressListener\n /** Abort signal that cancels the upload and the unfinished large file. */\n readonly signal?: AbortSignal\n}\n\ntype ResumeOnlyUploadOptions = Pick<\n HighLevelUploadOptions,\n | 'resume'\n | 'resumeFileId'\n | 'onResumeCandidateRejected'\n | 'onResumePartReused'\n | 'resumeDiscoveryTimeoutMs'\n | 'resumeMaxListPages'\n | 'resumeMaxPartCandidates'\n | 'resumeMaxPartPages'\n>\n\n/** High-level upload options after resume-only settings have been removed. */\nexport type SmallUploadOptions<T extends HighLevelUploadOptions> = Omit<\n T,\n keyof ResumeOnlyUploadOptions\n>\n\n/**\n * Explicit resume targets are multipart-only and must fail closed on small uploads.\n *\n * @param options - High-level upload options.\n * @param caller - Public method name used in the thrown error.\n *\n * @throws Error when an explicit resume target is supplied for a small upload.\n */\nexport function rejectSmallResumeFileId(options: HighLevelUploadOptions, caller: string): void {\n if (options.resumeFileId !== undefined) {\n throw new Error(`${caller}: resumeFileId is only supported for multipart uploads.`)\n }\n}\n\n/**\n * Removes resume-only options before forwarding to the small-file upload path.\n *\n * @param options - High-level upload options.\n *\n * @returns Options accepted by the single-request upload implementation.\n */\nexport function stripResumeOnlyOptions<T extends HighLevelUploadOptions>(\n options: T,\n): SmallUploadOptions<T> {\n const {\n resume: _resume,\n resumeFileId: _resumeFileId,\n onResumeCandidateRejected: _onResumeCandidateRejected,\n onResumePartReused: _onResumePartReused,\n resumeDiscoveryTimeoutMs: _resumeDiscoveryTimeoutMs,\n resumeMaxListPages: _resumeMaxListPages,\n resumeMaxPartCandidates: _resumeMaxPartCandidates,\n resumeMaxPartPages: _resumeMaxPartPages,\n ...smallOptions\n } = options\n return smallOptions\n}\n"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"options.js","names":[],"sources":["../../src/upload/options.ts"],"sourcesContent":["import type { ProgressListener } from '../streams/progress.ts'\nimport type { ContentSource } from '../streams/source.ts'\nimport type { EncryptionSetting } from '../types/encryption.ts'\nimport type { LargeFileId } from '../types/ids.ts'\nimport type { FileRetentionValue, LegalHoldValue } from '../types/lock.ts'\nimport type { CleanupFailureOptions } from './cancel.ts'\nimport type { ResumePartReusedListener } from './large.ts'\nimport type { ResumeCandidateRejectedListener } from './resume.ts'\nimport type { UploadRetryOptions } from './retry.ts'\n\n/** Shared options for high-level file uploads. */\nexport interface HighLevelUploadOptions extends UploadRetryOptions, CleanupFailureOptions {\n /**\n * Data source to upload. Use {@link BufferSource}, {@link BlobSource}, {@link FileSource},\n * {@link StreamSource}, or {@link toContentSource} for async iterables and Node Readables.\n */\n readonly source: ContentSource\n /** MIME type. Defaults to `\"b2/x-auto\"` (auto-detected by B2). */\n readonly contentType?: string\n /** Custom key-value metadata stored with the file. */\n readonly fileInfo?: Record<string, string>\n /** Server-side encryption settings. */\n readonly serverSideEncryption?: EncryptionSetting\n /** File retention policy (requires file lock on the bucket). */\n readonly fileRetention?: FileRetentionValue\n /** Legal hold status for the file. */\n readonly legalHold?: LegalHoldValue\n /** Last-modified timestamp in milliseconds since epoch. */\n readonly lastModifiedMillis?: number\n /**\n * B2 upload timestamp override in milliseconds since epoch.\n * Sets the file version `uploadTimestamp`; distinct from `lastModifiedMillis` metadata.\n * Requires B2 account enablement for Custom Upload Timestamp.\n */\n readonly customUploadTimestamp?: number\n /** Part size override for multipart uploads, in bytes. */\n readonly partSize?: number\n /** Number of concurrent part uploads for large files. */\n readonly concurrency?: number\n /** Callback invoked with upload progress events. */\n readonly onProgress?: ProgressListener\n /** Abort signal for cancelling the upload. */\n readonly signal?: AbortSignal\n /** Enable bounded same-name multipart resume discovery. Ignored on the small-file path. */\n readonly resume?: boolean\n /** Optional aggregate SDK-enforced timeout for resume discovery. */\n readonly resumeDiscoveryTimeoutMs?: number\n /** Maximum `b2_list_unfinished_large_files` pages inspected before upload starts. */\n readonly resumeMaxListPages?: number\n /** Maximum metadata-compatible candidates whose parts may be listed before upload starts. */\n readonly resumeMaxPartCandidates?: number\n /** Maximum `b2_list_parts` pages inspected per metadata-compatible candidate. */\n readonly resumeMaxPartPages?: number\n /**\n * Explicit unfinished large-file ID to verify and resume. Only supported on\n * the large-file path; small-file uploads throw.\n */\n readonly resumeFileId?: LargeFileId\n /** Diagnostic callback invoked when resume discovery rejects a candidate. */\n readonly onResumeCandidateRejected?: ResumeCandidateRejectedListener\n /** Diagnostic callback invoked when resume reuses an already-uploaded part. */\n readonly onResumePartReused?: ResumePartReusedListener\n}\n\n/** Backwards-compatible name for high-level upload options. */\nexport type UploadOptions = HighLevelUploadOptions\n\n/** Options accepted by {@link Bucket.upload}. */\nexport interface BucketUploadOptions extends HighLevelUploadOptions {\n /** Destination file name (path) in the bucket. */\n readonly fileName: string\n}\n\n/** Options accepted by {@link B2Object.upload}. */\nexport type B2ObjectUploadOptions = HighLevelUploadOptions\n\n/** Options accepted by {@link B2Object.createWriteStream}. */\nexport interface B2ObjectWriteStreamOptions extends UploadRetryOptions, CleanupFailureOptions {\n /** MIME type. Defaults to `\"b2/x-auto\"`. */\n readonly contentType?: string\n /** Custom key-value metadata stored with the file. */\n readonly fileInfo?: Record<string, string>\n /** Server-side encryption applied to each part. */\n readonly serverSideEncryption?: EncryptionSetting\n /**\n * B2 upload timestamp override in milliseconds since epoch.\n * Sets the file version `uploadTimestamp`; distinct from `src_last_modified_millis` metadata.\n * Requires B2 account enablement for Custom Upload Timestamp.\n */\n readonly customUploadTimestamp?: number\n /** Target part size in bytes. Defaults to the account's recommended part size. */\n readonly partSize?: number\n /** Maximum number of parts uploaded in parallel. Defaults to 4. */\n readonly concurrency?: number\n /** Callback invoked with upload progress events. */\n readonly onProgress?: ProgressListener\n /** Abort signal that cancels the upload and the unfinished large file. */\n readonly signal?: AbortSignal\n}\n\ntype ResumeOnlyUploadOptions = Pick<\n HighLevelUploadOptions,\n | 'resume'\n | 'resumeFileId'\n | 'onResumeCandidateRejected'\n | 'onResumePartReused'\n | 'resumeDiscoveryTimeoutMs'\n | 'resumeMaxListPages'\n | 'resumeMaxPartCandidates'\n | 'resumeMaxPartPages'\n>\n\n/** High-level upload options after resume-only settings have been removed. */\nexport type SmallUploadOptions<T extends HighLevelUploadOptions> = Omit<\n T,\n keyof ResumeOnlyUploadOptions\n>\n\n/**\n * Explicit resume targets are multipart-only and must fail closed on small uploads.\n *\n * @param options - High-level upload options.\n * @param caller - Public method name used in the thrown error.\n *\n * @throws Error when an explicit resume target is supplied for a small upload.\n */\nexport function rejectSmallResumeFileId(options: HighLevelUploadOptions, caller: string): void {\n if (options.resumeFileId !== undefined) {\n throw new Error(`${caller}: resumeFileId is only supported for multipart uploads.`)\n }\n}\n\n/**\n * Removes resume-only options before forwarding to the small-file upload path.\n *\n * @param options - High-level upload options.\n *\n * @returns Options accepted by the single-request upload implementation.\n */\nexport function stripResumeOnlyOptions<T extends HighLevelUploadOptions>(\n options: T,\n): SmallUploadOptions<T> {\n const {\n resume: _resume,\n resumeFileId: _resumeFileId,\n onResumeCandidateRejected: _onResumeCandidateRejected,\n onResumePartReused: _onResumePartReused,\n resumeDiscoveryTimeoutMs: _resumeDiscoveryTimeoutMs,\n resumeMaxListPages: _resumeMaxListPages,\n resumeMaxPartCandidates: _resumeMaxPartCandidates,\n resumeMaxPartPages: _resumeMaxPartPages,\n ...smallOptions\n } = options\n return smallOptions\n}\n"],"mappings":";;;;;;;;;AA8HA,SAAgB,wBAAwB,SAAiC,QAAsB;CAC7F,IAAI,QAAQ,iBAAiB,KAAA,GAC3B,MAAM,IAAI,MAAM,GAAG,OAAO,wDAAwD;AAEtF;;;;;;;;AASA,SAAgB,uBACd,SACuB;CACvB,MAAM,EACJ,QAAQ,SACR,cAAc,eACd,2BAA2B,4BAC3B,oBAAoB,qBACpB,0BAA0B,2BAC1B,oBAAoB,qBACpB,yBAAyB,0BACzB,oBAAoB,qBACpB,GAAG,iBACD;CACJ,OAAO;AACT"}
|
package/dist/upload/resume.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_ids = require("../types/ids.cjs");
|
|
2
2
|
const require_errors_index = require("../errors/index.cjs");
|
|
3
|
-
const require_bucket = require("../types/bucket.cjs");
|
|
4
3
|
const require_encryption = require("../types/encryption.cjs");
|
|
4
|
+
const require_bucket = require("../types/bucket.cjs");
|
|
5
5
|
//#region src/upload/resume.ts
|
|
6
6
|
/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */
|
|
7
7
|
var RESUME_SOURCE_SIZE_INFO_KEY = "b2_sdk_resume_source_size";
|
|
@@ -152,6 +152,7 @@ function candidateMetadataRejectReason(candidate, fileName, criteria) {
|
|
|
152
152
|
if (!recordEquals(candidateInfo.fileInfo, criteria.fileInfo)) return "file-info-mismatch";
|
|
153
153
|
if (candidateInfo.sourceSize !== void 0 && candidateInfo.sourceSize !== String(criteria.sourceSize)) return "source-size-mismatch";
|
|
154
154
|
if (candidateInfo.partSize !== void 0 && candidateInfo.partSize !== String(criteria.partSize)) return "part-size-mismatch";
|
|
155
|
+
if (criteria.customUploadTimestamp !== void 0 && candidate.uploadTimestamp !== criteria.customUploadTimestamp) return "upload-timestamp-mismatch";
|
|
155
156
|
const encryptionRejectReason = serverSideEncryptionRejectReason(candidate.serverSideEncryption, criteria.serverSideEncryption);
|
|
156
157
|
if (encryptionRejectReason !== null) return encryptionRejectReason;
|
|
157
158
|
if (!fileRetentionMatches(candidate.fileRetention, criteria.fileRetention, criteria.defaultFileRetention, criteria.defaultFileRetentionUnreadable === true, candidate.uploadTimestamp)) return "retention-mismatch";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume.cjs","names":[],"sources":["../../src/upload/resume.ts"],"sourcesContent":["import type { AccountInfo } from '../auth/account-info.ts'\nimport { ResumeFileIdMismatchError } from '../errors/index.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport { BucketRetentionMode, type BucketRetentionPolicy } from '../types/bucket.ts'\nimport {\n EncryptionMode,\n type EncryptionSetting,\n type PublicEncryptionSetting,\n} from '../types/encryption.ts'\nimport type { BucketId, LargeFileId } from '../types/ids.ts'\nimport { largeFileId as largeFileIdOf } from '../types/ids.ts'\nimport type { FileRetentionValue, LegalHoldValue } from '../types/lock.ts'\nimport type {\n ReadableFileRetention,\n ReadableLegalHold,\n UnfinishedLargeFile,\n} from '../types/upload.ts'\n\nexport { ResumeFileIdMismatchError }\n\n/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */\nexport const RESUME_SOURCE_SIZE_INFO_KEY = 'b2_sdk_resume_source_size'\n\n/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */\nexport const RESUME_PART_SIZE_INFO_KEY = 'b2_sdk_resume_part_size'\n\nconst DEFAULT_MAX_RESUME_LIST_PAGES = 10\nconst DEFAULT_MAX_RESUME_PART_CANDIDATES = 25\nconst DEFAULT_MAX_RESUME_PART_PAGES = 10\n\n/** One planned part from the local upload source. */\nexport interface ResumePartPlan {\n /** 1-based B2 part number. */\n readonly partNumber: number\n /** Expected byte length for this part. */\n readonly length: number\n}\n\n/** Reason an unfinished same-name large file was not reused for resume. */\nexport type ResumeCandidateRejectedReason =\n | 'search-truncated'\n | 'candidate-limit'\n | 'file-name-mismatch'\n | 'content-type-mismatch'\n | 'file-info-mismatch'\n | 'source-size-mismatch'\n | 'part-size-mismatch'\n | 'encryption-mismatch'\n | 'sse-c-unsupported'\n | 'retention-mismatch'\n | 'legal-hold-mismatch'\n | 'part-length-mismatch'\n\n/** Diagnostic event emitted when resume discovery declines a candidate. */\nexport interface ResumeCandidateRejectedEvent {\n /** ID of the unfinished large file, when the event is candidate-specific. */\n readonly fileId?: LargeFileId\n /** Requested destination file name. */\n readonly requestedFileName: string\n /** Actual candidate file name, when the event is candidate-specific. */\n readonly candidateFileName?: string\n /** Machine-readable rejection reason. */\n readonly reason: ResumeCandidateRejectedReason\n}\n\n/** Diagnostic callback for declined resume candidates. */\nexport type ResumeCandidateRejectedListener = (event: ResumeCandidateRejectedEvent) => void\n\n/** Compatibility requirements an unfinished large file must satisfy before reuse. */\nexport interface ResumeCandidateCriteria {\n /** Effective MIME type for the upload, including SDK defaults. */\n readonly contentType: string\n /** Caller-owned file info. Legacy SDK resume keys on candidates are ignored separately. */\n readonly fileInfo: Record<string, string>\n /** Source size in bytes, checked against legacy SDK metadata when a candidate carries it. */\n readonly sourceSize: number\n /** Effective multipart part size, checked against legacy SDK metadata when present. */\n readonly partSize: number\n /** Planned local parts used to verify already-uploaded server part lengths. */\n readonly parts: readonly ResumePartPlan[]\n /** Explicit server-side encryption option, if configured by the caller. */\n readonly serverSideEncryption?: EncryptionSetting\n /** Explicit Object Lock retention option, if configured by the caller. */\n readonly fileRetention?: FileRetentionValue\n /** Effective readable bucket default retention when the caller omits fileRetention. */\n readonly defaultFileRetention?: BucketRetentionPolicy\n /** Whether bucket default retention exists but cannot be read by the caller. */\n readonly defaultFileRetentionUnreadable?: boolean\n /** Explicit legal hold option, if configured by the caller. */\n readonly legalHold?: LegalHoldValue\n /** Explicit unfinished large-file ID to verify before reuse. */\n readonly resumeFileId?: LargeFileId\n /** Abort signal used for B2 list requests and checked between pages. */\n readonly signal?: AbortSignal\n /** Optional diagnostic callback for candidates that are found but not reused. */\n readonly onCandidateRejected?: ResumeCandidateRejectedListener\n /** Maximum `b2_list_unfinished_large_files` pages to inspect. */\n readonly maxListPages?: number\n /** Maximum metadata-compatible candidates whose parts may be listed. */\n readonly maxPartCandidates?: number\n /** Maximum `b2_list_parts` pages to inspect for each metadata-compatible candidate. */\n readonly maxPartPages?: number\n /** Caller-supplied aggregate resume discovery timeout. */\n readonly discoveryTimeoutMs?: number\n}\n\n/** Uploaded part metadata used by the resume planner. */\nexport interface ResumePartInfo {\n /** SHA-1 stored by B2 for this uploaded part. */\n readonly contentSha1: string\n /** Byte length stored by B2 for this uploaded part. */\n readonly contentLength: number\n}\n\nconst LIST_PARTS_PAGE_SIZE = 100\n\n/** Information about an unfinished large file eligible for resume. */\nexport interface ResumeCandidate {\n /** ID of the unfinished large file. */\n readonly fileId: LargeFileId\n /** SHA-1 of each part already uploaded, indexed by 1-based part number. */\n readonly uploadedPartSha1s: ReadonlyMap<number, string>\n}\n\n/**\n * Finds an unfinished large file matching the given bucket and file name.\n * Returns `null` when no compatible candidate exists.\n *\n * With criteria, the newest compatible candidate is selected; incompatible\n * same-name uploads are ignored and optionally reported via\n * `onCandidateRejected`.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param bucketId - Target bucket of the upload.\n * @param fileName - Destination file name of the upload.\n * @param criteria - Upload identity and option checks.\n *\n * @returns A {@link ResumeCandidate} describing the candidate and its uploaded parts, or `null`.\n */\nexport async function findResumeCandidate(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n): Promise<ResumeCandidate | null> {\n const discoverySignal = createResumeDiscoverySignal(criteria)\n try {\n return await findResumeCandidateWithSignal(\n raw,\n accountInfo,\n bucketId,\n fileName,\n criteria,\n discoverySignal.signal,\n )\n } finally {\n discoverySignal.dispose()\n }\n}\n\nasync function findResumeCandidateWithSignal(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n signal: AbortSignal | undefined,\n): Promise<ResumeCandidate | null> {\n const matches: Array<{ file: UnfinishedLargeFile; sequence: number }> = []\n const maxListPages = criteria.maxListPages ?? DEFAULT_MAX_RESUME_LIST_PAGES\n const maxPartCandidates = criteria.maxPartCandidates ?? DEFAULT_MAX_RESUME_PART_CANDIDATES\n let sequence = 0\n let pageCount = 0\n const explicitResumeFileId = criteria.resumeFileId\n let startFileId: LargeFileId | undefined = explicitResumeFileId\n let truncated = false\n\n while (pageCount < maxListPages) {\n signal?.throwIfAborted()\n const unfinished = await abortableRequest(\n raw.listUnfinishedLargeFiles(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n bucketId,\n maxFileCount: explicitResumeFileId !== undefined ? 1 : 100,\n namePrefix: fileName,\n ...(startFileId !== undefined ? { startFileId } : {}),\n },\n signal !== undefined ? { signal } : undefined,\n ),\n signal,\n )\n pageCount++\n\n for (const file of unfinished.files) {\n const isMatch =\n explicitResumeFileId !== undefined\n ? file.fileId === explicitResumeFileId\n : file.fileName === fileName\n if (isMatch) {\n matches.push({ file, sequence })\n }\n sequence++\n }\n\n if (explicitResumeFileId !== undefined) break\n if (unfinished.nextFileId === null) break\n startFileId = unfinished.nextFileId\n truncated = pageCount >= maxListPages\n }\n\n if (truncated) {\n emitCandidateRejected(criteria, {\n requestedFileName: fileName,\n reason: 'search-truncated',\n })\n }\n\n matches.sort(compareNewestFirst)\n\n let partCandidatesInspected = 0\n for (const match of matches) {\n signal?.throwIfAborted()\n const rejection = candidateMetadataRejectReason(match.file, fileName, criteria)\n if (rejection !== null) {\n notifyCandidateRejected(criteria, match.file, fileName, rejection)\n continue\n }\n\n if (partCandidatesInspected >= maxPartCandidates) {\n notifyCandidateRejected(criteria, match.file, fileName, 'candidate-limit')\n break\n }\n\n partCandidatesInspected++\n const fileId = largeFileIdOf(match.file.fileId)\n const uploadedPartsResult = await collectResumePartInfo(raw, accountInfo, fileId, {\n maxPages: criteria.maxPartPages ?? DEFAULT_MAX_RESUME_PART_PAGES,\n maxParts: criteria.parts.length,\n ...(signal !== undefined ? { signal } : {}),\n })\n const uploadedParts = uploadedPartsResult.parts\n if (uploadedPartsResult.truncated || !uploadedPartsMatchPlan(uploadedParts, criteria.parts)) {\n notifyCandidateRejected(criteria, match.file, fileName, 'part-length-mismatch')\n continue\n }\n\n return { fileId, uploadedPartSha1s: partInfoToSha1s(uploadedParts) }\n }\n\n return null\n}\n\ninterface CollectResumePartInfoOptions {\n readonly signal?: AbortSignal\n readonly maxPages?: number\n readonly maxParts?: number\n}\n\nasync function collectResumePartInfo(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: LargeFileId,\n options: CollectResumePartInfoOptions,\n): Promise<{ readonly parts: Map<number, ResumePartInfo>; readonly truncated: boolean }> {\n const parts = new Map<number, ResumePartInfo>()\n const maxPages = options.maxPages ?? Number.POSITIVE_INFINITY\n const maxParts = options.maxParts ?? Number.POSITIVE_INFINITY\n let startPartNumber: number | undefined\n let pageCount = 0\n\n while (pageCount < maxPages) {\n options.signal?.throwIfAborted()\n // Fetch one part beyond the local plan so a candidate with extra uploaded\n // parts is detected and rejected instead of silently reused.\n const remainingParts =\n maxParts === Number.POSITIVE_INFINITY\n ? undefined\n : Math.max(1, Math.min(LIST_PARTS_PAGE_SIZE, maxParts - parts.size + 1))\n const page = await abortableRequest(\n raw.listParts(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n fileId,\n ...(startPartNumber !== undefined ? { startPartNumber } : {}),\n ...(remainingParts !== undefined ? { maxPartCount: remainingParts } : {}),\n },\n options.signal !== undefined ? { signal: options.signal } : undefined,\n ),\n options.signal,\n )\n pageCount++\n for (const part of page.parts) {\n parts.set(part.partNumber, {\n contentSha1: part.contentSha1,\n contentLength: part.contentLength,\n })\n if (parts.size > maxParts) return { parts, truncated: true }\n }\n if (page.nextPartNumber === null) return { parts, truncated: false }\n assertAdvancingPartCursor(fileId, startPartNumber, page.nextPartNumber)\n startPartNumber = page.nextPartNumber\n }\n\n return { parts, truncated: true }\n}\n\nfunction compareNewestFirst(\n a: { file: UnfinishedLargeFile; sequence: number },\n b: { file: UnfinishedLargeFile; sequence: number },\n): number {\n const aTime = a.file.uploadTimestamp ?? Number.NEGATIVE_INFINITY\n const bTime = b.file.uploadTimestamp ?? Number.NEGATIVE_INFINITY\n if (aTime !== bTime) return bTime - aTime\n return b.sequence - a.sequence\n}\n\nfunction candidateMetadataRejectReason(\n candidate: UnfinishedLargeFile,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n): ResumeCandidateRejectedReason | null {\n if (candidate.fileName !== fileName) return 'file-name-mismatch'\n if (\n criteria.contentType === 'b2/x-auto' &&\n criteria.resumeFileId === undefined &&\n candidate.contentType !== 'b2/x-auto'\n ) {\n return 'content-type-mismatch'\n }\n if (criteria.contentType !== 'b2/x-auto' && candidate.contentType !== criteria.contentType) {\n return 'content-type-mismatch'\n }\n\n const candidateInfo = splitResumeFileInfo(candidate.fileInfo ?? {})\n if (!recordEquals(candidateInfo.fileInfo, criteria.fileInfo)) return 'file-info-mismatch'\n if (\n candidateInfo.sourceSize !== undefined &&\n candidateInfo.sourceSize !== String(criteria.sourceSize)\n ) {\n return 'source-size-mismatch'\n }\n if (\n candidateInfo.partSize !== undefined &&\n candidateInfo.partSize !== String(criteria.partSize)\n ) {\n return 'part-size-mismatch'\n }\n\n const encryptionRejectReason = serverSideEncryptionRejectReason(\n candidate.serverSideEncryption,\n criteria.serverSideEncryption,\n )\n if (encryptionRejectReason !== null) return encryptionRejectReason\n if (\n !fileRetentionMatches(\n candidate.fileRetention,\n criteria.fileRetention,\n criteria.defaultFileRetention,\n criteria.defaultFileRetentionUnreadable === true,\n candidate.uploadTimestamp,\n )\n ) {\n return 'retention-mismatch'\n }\n if (!legalHoldMatches(candidate.legalHold, criteria.legalHold)) {\n return 'legal-hold-mismatch'\n }\n\n return null\n}\n\nfunction notifyCandidateRejected(\n criteria: ResumeCandidateCriteria,\n candidate: UnfinishedLargeFile,\n requestedFileName: string,\n reason: ResumeCandidateRejectedReason,\n): void {\n emitCandidateRejected(criteria, {\n fileId: largeFileIdOf(candidate.fileId),\n requestedFileName,\n candidateFileName: candidate.fileName,\n reason,\n })\n}\n\nfunction emitCandidateRejected(\n criteria: ResumeCandidateCriteria,\n event: ResumeCandidateRejectedEvent,\n): void {\n try {\n criteria.onCandidateRejected?.(event)\n } catch {\n // Diagnostic listeners should not make an otherwise valid upload fail.\n }\n}\n\nfunction uploadedPartsMatchPlan(\n uploadedParts: ReadonlyMap<number, ResumePartInfo>,\n plans: readonly ResumePartPlan[],\n): boolean {\n for (const [partNumber, part] of uploadedParts) {\n const planned = plans[partNumber - 1]\n if (planned === undefined) return false\n if (planned.partNumber !== partNumber) return false\n if (planned.length !== part.contentLength) return false\n }\n return true\n}\n\nfunction partInfoToSha1s(parts: ReadonlyMap<number, ResumePartInfo>): Map<number, string> {\n const sha1s = new Map<number, string>()\n for (const [partNumber, part] of parts) {\n sha1s.set(partNumber, part.contentSha1)\n }\n return sha1s\n}\n\nfunction recordEquals(a: Record<string, string>, b: Record<string, string>): boolean {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (a[key] !== b[key]) return false\n }\n return true\n}\n\ninterface SplitResumeFileInfo {\n readonly fileInfo: Record<string, string>\n readonly sourceSize?: string\n readonly partSize?: string\n}\n\nfunction splitResumeFileInfo(fileInfo: Record<string, string>): SplitResumeFileInfo {\n const userFileInfo: Record<string, string> = Object.create(null) as Record<string, string>\n let sourceSize: string | undefined\n let partSize: string | undefined\n for (const [key, value] of Object.entries(fileInfo)) {\n if (key === RESUME_SOURCE_SIZE_INFO_KEY) {\n sourceSize = value\n } else if (key === RESUME_PART_SIZE_INFO_KEY) {\n partSize = value\n } else {\n userFileInfo[key] = value\n }\n }\n return {\n fileInfo: userFileInfo,\n ...(sourceSize !== undefined ? { sourceSize } : {}),\n ...(partSize !== undefined ? { partSize } : {}),\n }\n}\n\nfunction fileRetentionMatches(\n candidate: ReadableFileRetention | undefined,\n expected: FileRetentionValue | undefined,\n defaultExpected: BucketRetentionPolicy | undefined,\n defaultUnreadable: boolean,\n uploadTimestamp: number | undefined,\n): boolean {\n if (expected === undefined && defaultUnreadable) return false\n if (expected === undefined && defaultExpected !== undefined) {\n if (defaultExpected.mode === BucketRetentionMode.None) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, null)\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueMatchesBucketDefault(candidate.value, defaultExpected, uploadTimestamp)\n }\n if (expected === undefined) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, null)\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, expected)\n}\n\nfunction fileRetentionValueMatchesBucketDefault(\n candidate: FileRetentionValue | null,\n expected: BucketRetentionPolicy,\n uploadTimestamp: number | undefined,\n): boolean {\n if (expected.period === null) return false\n if (candidate?.mode !== expected.mode || candidate.retainUntilTimestamp === null) return false\n if (uploadTimestamp === undefined) return false\n return candidate.retainUntilTimestamp === uploadTimestamp + retentionPeriodMillis(expected.period)\n}\n\nfunction retentionPeriodMillis(period: BucketRetentionPolicy['period']): number {\n if (period === null) return 0\n const days = period.unit === 'days' ? period.duration : period.duration * 365\n return days * 24 * 60 * 60 * 1000\n}\n\nfunction fileRetentionValueEquals(\n a: FileRetentionValue | null,\n b: FileRetentionValue | null,\n): boolean {\n return (\n (a?.mode ?? null) === (b?.mode ?? null) &&\n (a?.retainUntilTimestamp ?? null) === (b?.retainUntilTimestamp ?? null)\n )\n}\n\nfunction legalHoldMatches(\n candidate: ReadableLegalHold | undefined,\n expected: LegalHoldValue | undefined,\n): boolean {\n if (expected === undefined) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return candidate.value === null || candidate.value === 'off'\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return candidate.value === expected\n}\n\ninterface ResumeDiscoverySignal {\n readonly signal?: AbortSignal\n dispose(): void\n}\n\nfunction createResumeDiscoverySignal(criteria: ResumeCandidateCriteria): ResumeDiscoverySignal {\n if (criteria.signal !== undefined) {\n return {\n signal: criteria.signal,\n dispose() {},\n }\n }\n\n if (criteria.discoveryTimeoutMs === undefined) {\n return {\n dispose() {},\n }\n }\n\n const timeoutMs = criteria.discoveryTimeoutMs\n if (timeoutMs === Number.POSITIVE_INFINITY) {\n return {\n dispose() {},\n }\n }\n\n const controller = new AbortController()\n const timeout = setTimeout(\n () => {\n controller.abort(resumeDiscoveryTimeoutReason(timeoutMs))\n },\n Math.max(0, timeoutMs),\n )\n\n return {\n signal: controller.signal,\n dispose() {\n clearTimeout(timeout)\n },\n }\n}\n\nasync function abortableRequest<T>(\n request: Promise<T>,\n signal: AbortSignal | undefined,\n): Promise<T> {\n if (signal === undefined) return request\n if (signal.aborted) throw resumeAbortReason(signal)\n\n let removeAbortListener: (() => void) | undefined\n const aborted = new Promise<never>((_resolve, reject) => {\n const onAbort = () => reject(resumeAbortReason(signal))\n signal.addEventListener('abort', onAbort, { once: true })\n removeAbortListener = () => signal.removeEventListener('abort', onAbort)\n })\n\n try {\n return await Promise.race([request, aborted])\n } finally {\n removeAbortListener?.()\n void request.catch(() => {})\n }\n}\n\nfunction resumeAbortReason(signal: AbortSignal): unknown {\n return signal.reason ?? resumeAbortFallbackReason()\n}\n\nfunction resumeAbortFallbackReason(): Error {\n if (typeof DOMException === 'function') {\n return new DOMException('Resume discovery aborted', 'AbortError')\n }\n const error = new Error('Resume discovery aborted')\n error.name = 'AbortError'\n return error\n}\n\nfunction resumeDiscoveryTimeoutReason(timeoutMs: number): Error {\n if (typeof DOMException === 'function') {\n return new DOMException(`Resume discovery timed out after ${timeoutMs} ms`, 'TimeoutError')\n }\n const error = new Error(`Resume discovery timed out after ${timeoutMs} ms`)\n error.name = 'TimeoutError'\n return error\n}\n\ntype ListedEncryption = PublicEncryptionSetting | undefined\n\nfunction serverSideEncryptionRejectReason(\n candidate: ListedEncryption,\n expected: EncryptionSetting | undefined,\n): 'encryption-mismatch' | 'sse-c-unsupported' | null {\n if (expected?.mode === EncryptionMode.SseC) return 'sse-c-unsupported'\n\n const actual = normalizeEncryption(candidate)\n if (expected === undefined) {\n if (candidate === undefined) return null\n if (actual === undefined) return 'encryption-mismatch'\n if (actual.mode === EncryptionMode.None) return null\n if (actual.mode === EncryptionMode.SseB2) return null\n return actual.mode === EncryptionMode.SseC ? 'sse-c-unsupported' : 'encryption-mismatch'\n }\n\n const normalizedExpected = normalizeEncryption(expected)\n if (normalizedExpected?.mode === EncryptionMode.None && candidate === undefined) return null\n if (actual === undefined || normalizedExpected === undefined) return 'encryption-mismatch'\n if (actual.mode !== normalizedExpected.mode) return 'encryption-mismatch'\n if (actual.mode === EncryptionMode.None) return null\n return actual.algorithm === normalizedExpected.algorithm ? null : 'encryption-mismatch'\n}\n\ninterface NormalizedEncryption {\n readonly mode: EncryptionMode\n readonly algorithm?: string\n}\n\nfunction normalizeEncryption(\n encryption: EncryptionSetting | ListedEncryption,\n): NormalizedEncryption | undefined {\n if (encryption === undefined) return undefined\n // Real B2 responses may spell no encryption as `{ mode: null, algorithm: null }`.\n if (encryption.mode === null || encryption.mode === EncryptionMode.None) {\n return { mode: EncryptionMode.None }\n }\n if (encryption.mode !== EncryptionMode.SseB2 && encryption.mode !== EncryptionMode.SseC) {\n return undefined\n }\n return {\n mode: encryption.mode,\n algorithm: encryption.algorithm,\n }\n}\n\nfunction assertAdvancingPartCursor(\n fileId: LargeFileId,\n previous: number | undefined,\n next: number,\n): void {\n if (!Number.isInteger(next) || next < 1 || (previous !== undefined && next <= previous)) {\n throw new Error(\n `uploadLargeFile: listParts returned a non-advancing nextPartNumber for ${fileId}; aborting resume.`,\n )\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resume.cjs","names":[],"sources":["../../src/upload/resume.ts"],"sourcesContent":["import type { AccountInfo } from '../auth/account-info.ts'\nimport { ResumeFileIdMismatchError } from '../errors/index.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport { BucketRetentionMode, type BucketRetentionPolicy } from '../types/bucket.ts'\nimport {\n EncryptionMode,\n type EncryptionSetting,\n type PublicEncryptionSetting,\n} from '../types/encryption.ts'\nimport type { BucketId, LargeFileId } from '../types/ids.ts'\nimport { largeFileId as largeFileIdOf } from '../types/ids.ts'\nimport type { FileRetentionValue, LegalHoldValue } from '../types/lock.ts'\nimport type {\n ReadableFileRetention,\n ReadableLegalHold,\n UnfinishedLargeFile,\n} from '../types/upload.ts'\n\nexport { ResumeFileIdMismatchError }\n\n/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */\nexport const RESUME_SOURCE_SIZE_INFO_KEY = 'b2_sdk_resume_source_size'\n\n/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */\nexport const RESUME_PART_SIZE_INFO_KEY = 'b2_sdk_resume_part_size'\n\nconst DEFAULT_MAX_RESUME_LIST_PAGES = 10\nconst DEFAULT_MAX_RESUME_PART_CANDIDATES = 25\nconst DEFAULT_MAX_RESUME_PART_PAGES = 10\n\n/** One planned part from the local upload source. */\nexport interface ResumePartPlan {\n /** 1-based B2 part number. */\n readonly partNumber: number\n /** Expected byte length for this part. */\n readonly length: number\n}\n\n/** Reason an unfinished same-name large file was not reused for resume. */\nexport type ResumeCandidateRejectedReason =\n | 'search-truncated'\n | 'candidate-limit'\n | 'file-name-mismatch'\n | 'content-type-mismatch'\n | 'file-info-mismatch'\n | 'source-size-mismatch'\n | 'part-size-mismatch'\n | 'upload-timestamp-mismatch'\n | 'encryption-mismatch'\n | 'sse-c-unsupported'\n | 'retention-mismatch'\n | 'legal-hold-mismatch'\n | 'part-length-mismatch'\n\n/** Diagnostic event emitted when resume discovery declines a candidate. */\nexport interface ResumeCandidateRejectedEvent {\n /** ID of the unfinished large file, when the event is candidate-specific. */\n readonly fileId?: LargeFileId\n /** Requested destination file name. */\n readonly requestedFileName: string\n /** Actual candidate file name, when the event is candidate-specific. */\n readonly candidateFileName?: string\n /** Machine-readable rejection reason. */\n readonly reason: ResumeCandidateRejectedReason\n}\n\n/** Diagnostic callback for declined resume candidates. */\nexport type ResumeCandidateRejectedListener = (event: ResumeCandidateRejectedEvent) => void\n\n/** Compatibility requirements an unfinished large file must satisfy before reuse. */\nexport interface ResumeCandidateCriteria {\n /** Effective MIME type for the upload, including SDK defaults. */\n readonly contentType: string\n /** Caller-owned file info. Legacy SDK resume keys on candidates are ignored separately. */\n readonly fileInfo: Record<string, string>\n /** Source size in bytes, checked against legacy SDK metadata when a candidate carries it. */\n readonly sourceSize: number\n /** Effective multipart part size, checked against legacy SDK metadata when present. */\n readonly partSize: number\n /** Planned local parts used to verify already-uploaded server part lengths. */\n readonly parts: readonly ResumePartPlan[]\n /** Requested B2 upload timestamp override, if configured by the caller. */\n readonly customUploadTimestamp?: number\n /** Explicit server-side encryption option, if configured by the caller. */\n readonly serverSideEncryption?: EncryptionSetting\n /** Explicit Object Lock retention option, if configured by the caller. */\n readonly fileRetention?: FileRetentionValue\n /** Effective readable bucket default retention when the caller omits fileRetention. */\n readonly defaultFileRetention?: BucketRetentionPolicy\n /** Whether bucket default retention exists but cannot be read by the caller. */\n readonly defaultFileRetentionUnreadable?: boolean\n /** Explicit legal hold option, if configured by the caller. */\n readonly legalHold?: LegalHoldValue\n /** Explicit unfinished large-file ID to verify before reuse. */\n readonly resumeFileId?: LargeFileId\n /** Abort signal used for B2 list requests and checked between pages. */\n readonly signal?: AbortSignal\n /** Optional diagnostic callback for candidates that are found but not reused. */\n readonly onCandidateRejected?: ResumeCandidateRejectedListener\n /** Maximum `b2_list_unfinished_large_files` pages to inspect. */\n readonly maxListPages?: number\n /** Maximum metadata-compatible candidates whose parts may be listed. */\n readonly maxPartCandidates?: number\n /** Maximum `b2_list_parts` pages to inspect for each metadata-compatible candidate. */\n readonly maxPartPages?: number\n /** Caller-supplied aggregate resume discovery timeout. */\n readonly discoveryTimeoutMs?: number\n}\n\n/** Uploaded part metadata used by the resume planner. */\nexport interface ResumePartInfo {\n /** SHA-1 stored by B2 for this uploaded part. */\n readonly contentSha1: string\n /** Byte length stored by B2 for this uploaded part. */\n readonly contentLength: number\n}\n\nconst LIST_PARTS_PAGE_SIZE = 100\n\n/** Information about an unfinished large file eligible for resume. */\nexport interface ResumeCandidate {\n /** ID of the unfinished large file. */\n readonly fileId: LargeFileId\n /** SHA-1 of each part already uploaded, indexed by 1-based part number. */\n readonly uploadedPartSha1s: ReadonlyMap<number, string>\n}\n\n/**\n * Finds an unfinished large file matching the given bucket and file name.\n * Returns `null` when no compatible candidate exists.\n *\n * With criteria, the newest compatible candidate is selected; incompatible\n * same-name uploads are ignored and optionally reported via\n * `onCandidateRejected`.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param bucketId - Target bucket of the upload.\n * @param fileName - Destination file name of the upload.\n * @param criteria - Upload identity and option checks.\n *\n * @returns A {@link ResumeCandidate} describing the candidate and its uploaded parts, or `null`.\n */\nexport async function findResumeCandidate(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n): Promise<ResumeCandidate | null> {\n const discoverySignal = createResumeDiscoverySignal(criteria)\n try {\n return await findResumeCandidateWithSignal(\n raw,\n accountInfo,\n bucketId,\n fileName,\n criteria,\n discoverySignal.signal,\n )\n } finally {\n discoverySignal.dispose()\n }\n}\n\nasync function findResumeCandidateWithSignal(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n signal: AbortSignal | undefined,\n): Promise<ResumeCandidate | null> {\n const matches: Array<{ file: UnfinishedLargeFile; sequence: number }> = []\n const maxListPages = criteria.maxListPages ?? DEFAULT_MAX_RESUME_LIST_PAGES\n const maxPartCandidates = criteria.maxPartCandidates ?? DEFAULT_MAX_RESUME_PART_CANDIDATES\n let sequence = 0\n let pageCount = 0\n const explicitResumeFileId = criteria.resumeFileId\n let startFileId: LargeFileId | undefined = explicitResumeFileId\n let truncated = false\n\n while (pageCount < maxListPages) {\n signal?.throwIfAborted()\n const unfinished = await abortableRequest(\n raw.listUnfinishedLargeFiles(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n bucketId,\n maxFileCount: explicitResumeFileId !== undefined ? 1 : 100,\n namePrefix: fileName,\n ...(startFileId !== undefined ? { startFileId } : {}),\n },\n signal !== undefined ? { signal } : undefined,\n ),\n signal,\n )\n pageCount++\n\n for (const file of unfinished.files) {\n const isMatch =\n explicitResumeFileId !== undefined\n ? file.fileId === explicitResumeFileId\n : file.fileName === fileName\n if (isMatch) {\n matches.push({ file, sequence })\n }\n sequence++\n }\n\n if (explicitResumeFileId !== undefined) break\n if (unfinished.nextFileId === null) break\n startFileId = unfinished.nextFileId\n truncated = pageCount >= maxListPages\n }\n\n if (truncated) {\n emitCandidateRejected(criteria, {\n requestedFileName: fileName,\n reason: 'search-truncated',\n })\n }\n\n matches.sort(compareNewestFirst)\n\n let partCandidatesInspected = 0\n for (const match of matches) {\n signal?.throwIfAborted()\n const rejection = candidateMetadataRejectReason(match.file, fileName, criteria)\n if (rejection !== null) {\n notifyCandidateRejected(criteria, match.file, fileName, rejection)\n continue\n }\n\n if (partCandidatesInspected >= maxPartCandidates) {\n notifyCandidateRejected(criteria, match.file, fileName, 'candidate-limit')\n break\n }\n\n partCandidatesInspected++\n const fileId = largeFileIdOf(match.file.fileId)\n const uploadedPartsResult = await collectResumePartInfo(raw, accountInfo, fileId, {\n maxPages: criteria.maxPartPages ?? DEFAULT_MAX_RESUME_PART_PAGES,\n maxParts: criteria.parts.length,\n ...(signal !== undefined ? { signal } : {}),\n })\n const uploadedParts = uploadedPartsResult.parts\n if (uploadedPartsResult.truncated || !uploadedPartsMatchPlan(uploadedParts, criteria.parts)) {\n notifyCandidateRejected(criteria, match.file, fileName, 'part-length-mismatch')\n continue\n }\n\n return { fileId, uploadedPartSha1s: partInfoToSha1s(uploadedParts) }\n }\n\n return null\n}\n\ninterface CollectResumePartInfoOptions {\n readonly signal?: AbortSignal\n readonly maxPages?: number\n readonly maxParts?: number\n}\n\nasync function collectResumePartInfo(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: LargeFileId,\n options: CollectResumePartInfoOptions,\n): Promise<{ readonly parts: Map<number, ResumePartInfo>; readonly truncated: boolean }> {\n const parts = new Map<number, ResumePartInfo>()\n const maxPages = options.maxPages ?? Number.POSITIVE_INFINITY\n const maxParts = options.maxParts ?? Number.POSITIVE_INFINITY\n let startPartNumber: number | undefined\n let pageCount = 0\n\n while (pageCount < maxPages) {\n options.signal?.throwIfAborted()\n // Fetch one part beyond the local plan so a candidate with extra uploaded\n // parts is detected and rejected instead of silently reused.\n const remainingParts =\n maxParts === Number.POSITIVE_INFINITY\n ? undefined\n : Math.max(1, Math.min(LIST_PARTS_PAGE_SIZE, maxParts - parts.size + 1))\n const page = await abortableRequest(\n raw.listParts(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n fileId,\n ...(startPartNumber !== undefined ? { startPartNumber } : {}),\n ...(remainingParts !== undefined ? { maxPartCount: remainingParts } : {}),\n },\n options.signal !== undefined ? { signal: options.signal } : undefined,\n ),\n options.signal,\n )\n pageCount++\n for (const part of page.parts) {\n parts.set(part.partNumber, {\n contentSha1: part.contentSha1,\n contentLength: part.contentLength,\n })\n if (parts.size > maxParts) return { parts, truncated: true }\n }\n if (page.nextPartNumber === null) return { parts, truncated: false }\n assertAdvancingPartCursor(fileId, startPartNumber, page.nextPartNumber)\n startPartNumber = page.nextPartNumber\n }\n\n return { parts, truncated: true }\n}\n\nfunction compareNewestFirst(\n a: { file: UnfinishedLargeFile; sequence: number },\n b: { file: UnfinishedLargeFile; sequence: number },\n): number {\n const aTime = a.file.uploadTimestamp ?? Number.NEGATIVE_INFINITY\n const bTime = b.file.uploadTimestamp ?? Number.NEGATIVE_INFINITY\n if (aTime !== bTime) return bTime - aTime\n return b.sequence - a.sequence\n}\n\nfunction candidateMetadataRejectReason(\n candidate: UnfinishedLargeFile,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n): ResumeCandidateRejectedReason | null {\n if (candidate.fileName !== fileName) return 'file-name-mismatch'\n if (\n criteria.contentType === 'b2/x-auto' &&\n criteria.resumeFileId === undefined &&\n candidate.contentType !== 'b2/x-auto'\n ) {\n return 'content-type-mismatch'\n }\n if (criteria.contentType !== 'b2/x-auto' && candidate.contentType !== criteria.contentType) {\n return 'content-type-mismatch'\n }\n\n const candidateInfo = splitResumeFileInfo(candidate.fileInfo ?? {})\n if (!recordEquals(candidateInfo.fileInfo, criteria.fileInfo)) return 'file-info-mismatch'\n if (\n candidateInfo.sourceSize !== undefined &&\n candidateInfo.sourceSize !== String(criteria.sourceSize)\n ) {\n return 'source-size-mismatch'\n }\n if (\n candidateInfo.partSize !== undefined &&\n candidateInfo.partSize !== String(criteria.partSize)\n ) {\n return 'part-size-mismatch'\n }\n if (\n criteria.customUploadTimestamp !== undefined &&\n candidate.uploadTimestamp !== criteria.customUploadTimestamp\n ) {\n return 'upload-timestamp-mismatch'\n }\n\n const encryptionRejectReason = serverSideEncryptionRejectReason(\n candidate.serverSideEncryption,\n criteria.serverSideEncryption,\n )\n if (encryptionRejectReason !== null) return encryptionRejectReason\n if (\n !fileRetentionMatches(\n candidate.fileRetention,\n criteria.fileRetention,\n criteria.defaultFileRetention,\n criteria.defaultFileRetentionUnreadable === true,\n candidate.uploadTimestamp,\n )\n ) {\n return 'retention-mismatch'\n }\n if (!legalHoldMatches(candidate.legalHold, criteria.legalHold)) {\n return 'legal-hold-mismatch'\n }\n\n return null\n}\n\nfunction notifyCandidateRejected(\n criteria: ResumeCandidateCriteria,\n candidate: UnfinishedLargeFile,\n requestedFileName: string,\n reason: ResumeCandidateRejectedReason,\n): void {\n emitCandidateRejected(criteria, {\n fileId: largeFileIdOf(candidate.fileId),\n requestedFileName,\n candidateFileName: candidate.fileName,\n reason,\n })\n}\n\nfunction emitCandidateRejected(\n criteria: ResumeCandidateCriteria,\n event: ResumeCandidateRejectedEvent,\n): void {\n try {\n criteria.onCandidateRejected?.(event)\n } catch {\n // Diagnostic listeners should not make an otherwise valid upload fail.\n }\n}\n\nfunction uploadedPartsMatchPlan(\n uploadedParts: ReadonlyMap<number, ResumePartInfo>,\n plans: readonly ResumePartPlan[],\n): boolean {\n for (const [partNumber, part] of uploadedParts) {\n const planned = plans[partNumber - 1]\n if (planned === undefined) return false\n if (planned.partNumber !== partNumber) return false\n if (planned.length !== part.contentLength) return false\n }\n return true\n}\n\nfunction partInfoToSha1s(parts: ReadonlyMap<number, ResumePartInfo>): Map<number, string> {\n const sha1s = new Map<number, string>()\n for (const [partNumber, part] of parts) {\n sha1s.set(partNumber, part.contentSha1)\n }\n return sha1s\n}\n\nfunction recordEquals(a: Record<string, string>, b: Record<string, string>): boolean {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (a[key] !== b[key]) return false\n }\n return true\n}\n\ninterface SplitResumeFileInfo {\n readonly fileInfo: Record<string, string>\n readonly sourceSize?: string\n readonly partSize?: string\n}\n\nfunction splitResumeFileInfo(fileInfo: Record<string, string>): SplitResumeFileInfo {\n const userFileInfo: Record<string, string> = Object.create(null) as Record<string, string>\n let sourceSize: string | undefined\n let partSize: string | undefined\n for (const [key, value] of Object.entries(fileInfo)) {\n if (key === RESUME_SOURCE_SIZE_INFO_KEY) {\n sourceSize = value\n } else if (key === RESUME_PART_SIZE_INFO_KEY) {\n partSize = value\n } else {\n userFileInfo[key] = value\n }\n }\n return {\n fileInfo: userFileInfo,\n ...(sourceSize !== undefined ? { sourceSize } : {}),\n ...(partSize !== undefined ? { partSize } : {}),\n }\n}\n\nfunction fileRetentionMatches(\n candidate: ReadableFileRetention | undefined,\n expected: FileRetentionValue | undefined,\n defaultExpected: BucketRetentionPolicy | undefined,\n defaultUnreadable: boolean,\n uploadTimestamp: number | undefined,\n): boolean {\n if (expected === undefined && defaultUnreadable) return false\n if (expected === undefined && defaultExpected !== undefined) {\n if (defaultExpected.mode === BucketRetentionMode.None) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, null)\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueMatchesBucketDefault(candidate.value, defaultExpected, uploadTimestamp)\n }\n if (expected === undefined) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, null)\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, expected)\n}\n\nfunction fileRetentionValueMatchesBucketDefault(\n candidate: FileRetentionValue | null,\n expected: BucketRetentionPolicy,\n uploadTimestamp: number | undefined,\n): boolean {\n if (expected.period === null) return false\n if (candidate?.mode !== expected.mode || candidate.retainUntilTimestamp === null) return false\n if (uploadTimestamp === undefined) return false\n return candidate.retainUntilTimestamp === uploadTimestamp + retentionPeriodMillis(expected.period)\n}\n\nfunction retentionPeriodMillis(period: BucketRetentionPolicy['period']): number {\n if (period === null) return 0\n const days = period.unit === 'days' ? period.duration : period.duration * 365\n return days * 24 * 60 * 60 * 1000\n}\n\nfunction fileRetentionValueEquals(\n a: FileRetentionValue | null,\n b: FileRetentionValue | null,\n): boolean {\n return (\n (a?.mode ?? null) === (b?.mode ?? null) &&\n (a?.retainUntilTimestamp ?? null) === (b?.retainUntilTimestamp ?? null)\n )\n}\n\nfunction legalHoldMatches(\n candidate: ReadableLegalHold | undefined,\n expected: LegalHoldValue | undefined,\n): boolean {\n if (expected === undefined) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return candidate.value === null || candidate.value === 'off'\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return candidate.value === expected\n}\n\ninterface ResumeDiscoverySignal {\n readonly signal?: AbortSignal\n dispose(): void\n}\n\nfunction createResumeDiscoverySignal(criteria: ResumeCandidateCriteria): ResumeDiscoverySignal {\n if (criteria.signal !== undefined) {\n return {\n signal: criteria.signal,\n dispose() {},\n }\n }\n\n if (criteria.discoveryTimeoutMs === undefined) {\n return {\n dispose() {},\n }\n }\n\n const timeoutMs = criteria.discoveryTimeoutMs\n if (timeoutMs === Number.POSITIVE_INFINITY) {\n return {\n dispose() {},\n }\n }\n\n const controller = new AbortController()\n const timeout = setTimeout(\n () => {\n controller.abort(resumeDiscoveryTimeoutReason(timeoutMs))\n },\n Math.max(0, timeoutMs),\n )\n\n return {\n signal: controller.signal,\n dispose() {\n clearTimeout(timeout)\n },\n }\n}\n\nasync function abortableRequest<T>(\n request: Promise<T>,\n signal: AbortSignal | undefined,\n): Promise<T> {\n if (signal === undefined) return request\n if (signal.aborted) throw resumeAbortReason(signal)\n\n let removeAbortListener: (() => void) | undefined\n const aborted = new Promise<never>((_resolve, reject) => {\n const onAbort = () => reject(resumeAbortReason(signal))\n signal.addEventListener('abort', onAbort, { once: true })\n removeAbortListener = () => signal.removeEventListener('abort', onAbort)\n })\n\n try {\n return await Promise.race([request, aborted])\n } finally {\n removeAbortListener?.()\n void request.catch(() => {})\n }\n}\n\nfunction resumeAbortReason(signal: AbortSignal): unknown {\n return signal.reason ?? resumeAbortFallbackReason()\n}\n\nfunction resumeAbortFallbackReason(): Error {\n if (typeof DOMException === 'function') {\n return new DOMException('Resume discovery aborted', 'AbortError')\n }\n const error = new Error('Resume discovery aborted')\n error.name = 'AbortError'\n return error\n}\n\nfunction resumeDiscoveryTimeoutReason(timeoutMs: number): Error {\n if (typeof DOMException === 'function') {\n return new DOMException(`Resume discovery timed out after ${timeoutMs} ms`, 'TimeoutError')\n }\n const error = new Error(`Resume discovery timed out after ${timeoutMs} ms`)\n error.name = 'TimeoutError'\n return error\n}\n\ntype ListedEncryption = PublicEncryptionSetting | undefined\n\nfunction serverSideEncryptionRejectReason(\n candidate: ListedEncryption,\n expected: EncryptionSetting | undefined,\n): 'encryption-mismatch' | 'sse-c-unsupported' | null {\n if (expected?.mode === EncryptionMode.SseC) return 'sse-c-unsupported'\n\n const actual = normalizeEncryption(candidate)\n if (expected === undefined) {\n if (candidate === undefined) return null\n if (actual === undefined) return 'encryption-mismatch'\n if (actual.mode === EncryptionMode.None) return null\n if (actual.mode === EncryptionMode.SseB2) return null\n return actual.mode === EncryptionMode.SseC ? 'sse-c-unsupported' : 'encryption-mismatch'\n }\n\n const normalizedExpected = normalizeEncryption(expected)\n if (normalizedExpected?.mode === EncryptionMode.None && candidate === undefined) return null\n if (actual === undefined || normalizedExpected === undefined) return 'encryption-mismatch'\n if (actual.mode !== normalizedExpected.mode) return 'encryption-mismatch'\n if (actual.mode === EncryptionMode.None) return null\n return actual.algorithm === normalizedExpected.algorithm ? null : 'encryption-mismatch'\n}\n\ninterface NormalizedEncryption {\n readonly mode: EncryptionMode\n readonly algorithm?: string\n}\n\nfunction normalizeEncryption(\n encryption: EncryptionSetting | ListedEncryption,\n): NormalizedEncryption | undefined {\n if (encryption === undefined) return undefined\n // Real B2 responses may spell no encryption as `{ mode: null, algorithm: null }`.\n if (encryption.mode === null || encryption.mode === EncryptionMode.None) {\n return { mode: EncryptionMode.None }\n }\n if (encryption.mode !== EncryptionMode.SseB2 && encryption.mode !== EncryptionMode.SseC) {\n return undefined\n }\n return {\n mode: encryption.mode,\n algorithm: encryption.algorithm,\n }\n}\n\nfunction assertAdvancingPartCursor(\n fileId: LargeFileId,\n previous: number | undefined,\n next: number,\n): void {\n if (!Number.isInteger(next) || next < 1 || (previous !== undefined && next <= previous)) {\n throw new Error(\n `uploadLargeFile: listParts returned a non-advancing nextPartNumber for ${fileId}; aborting resume.`,\n )\n }\n}\n"],"mappings":"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"}
|
package/dist/upload/resume.d.cts
CHANGED
|
@@ -18,7 +18,7 @@ export interface ResumePartPlan {
|
|
|
18
18
|
readonly length: number;
|
|
19
19
|
}
|
|
20
20
|
/** Reason an unfinished same-name large file was not reused for resume. */
|
|
21
|
-
export type ResumeCandidateRejectedReason = 'search-truncated' | 'candidate-limit' | 'file-name-mismatch' | 'content-type-mismatch' | 'file-info-mismatch' | 'source-size-mismatch' | 'part-size-mismatch' | 'encryption-mismatch' | 'sse-c-unsupported' | 'retention-mismatch' | 'legal-hold-mismatch' | 'part-length-mismatch';
|
|
21
|
+
export type ResumeCandidateRejectedReason = 'search-truncated' | 'candidate-limit' | 'file-name-mismatch' | 'content-type-mismatch' | 'file-info-mismatch' | 'source-size-mismatch' | 'part-size-mismatch' | 'upload-timestamp-mismatch' | 'encryption-mismatch' | 'sse-c-unsupported' | 'retention-mismatch' | 'legal-hold-mismatch' | 'part-length-mismatch';
|
|
22
22
|
/** Diagnostic event emitted when resume discovery declines a candidate. */
|
|
23
23
|
export interface ResumeCandidateRejectedEvent {
|
|
24
24
|
/** ID of the unfinished large file, when the event is candidate-specific. */
|
|
@@ -44,6 +44,8 @@ export interface ResumeCandidateCriteria {
|
|
|
44
44
|
readonly partSize: number;
|
|
45
45
|
/** Planned local parts used to verify already-uploaded server part lengths. */
|
|
46
46
|
readonly parts: readonly ResumePartPlan[];
|
|
47
|
+
/** Requested B2 upload timestamp override, if configured by the caller. */
|
|
48
|
+
readonly customUploadTimestamp?: number;
|
|
47
49
|
/** Explicit server-side encryption option, if configured by the caller. */
|
|
48
50
|
readonly serverSideEncryption?: EncryptionSetting;
|
|
49
51
|
/** Explicit Object Lock retention option, if configured by the caller. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/upload/resume.js"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAA;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAuB,KAAK,qBAAqB,EAAE,MAAM,oBAAoB,CAAA;AACpF,OAAO,EAEL,KAAK,iBAAiB,EAEvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAE5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAO1E,OAAO,EAAE,yBAAyB,EAAE,CAAA;AAEpC,yGAAyG;AACzG,eAAO,MAAM,2BAA2B,8BAA8B,CAAA;AAEtE,yGAAyG;AACzG,eAAO,MAAM,yBAAyB,4BAA4B,CAAA;AAMlE,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,8BAA8B;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,2EAA2E;AAC3E,MAAM,MAAM,6BAA6B,GACrC,kBAAkB,GAClB,iBAAiB,GACjB,oBAAoB,GACpB,uBAAuB,GACvB,oBAAoB,GACpB,sBAAsB,GACtB,oBAAoB,GACpB,qBAAqB,GACrB,mBAAmB,GACnB,oBAAoB,GACpB,qBAAqB,GACrB,sBAAsB,CAAA;AAE1B,2EAA2E;AAC3E,MAAM,WAAW,4BAA4B;IAC3C,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;IAC7B,uCAAuC;IACvC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;IAClC,wEAAwE;IACxE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE,6BAA6B,CAAA;CAC/C;AAED,0DAA0D;AAC1D,MAAM,MAAM,+BAA+B,GAAG,CAAC,KAAK,EAAE,4BAA4B,KAAK,IAAI,CAAA;AAE3F,qFAAqF;AACrF,MAAM,WAAW,uBAAuB;IACtC,kEAAkE;IAClE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC,6FAA6F;IAC7F,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,uFAAuF;IACvF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAA;IACzC,2EAA2E;IAC3E,QAAQ,CAAC,oBAAoB,CAAC,EAAE,iBAAiB,CAAA;IACjD,0EAA0E;IAC1E,QAAQ,CAAC,aAAa,CAAC,EAAE,kBAAkB,CAAA;IAC3C,uFAAuF;IACvF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,qBAAqB,CAAA;IACrD,gFAAgF;IAChF,QAAQ,CAAC,8BAA8B,CAAC,EAAE,OAAO,CAAA;IACjD,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,CAAA;IACnC,gEAAgE;IAChE,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAA;IACnC,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,mBAAmB,CAAC,EAAE,+BAA+B,CAAA;IAC9D,iEAAiE;IACjE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IACnC,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;CACrC;AAED,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAC/B;AAID,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,uCAAuC;IACvC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,iBAAiB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxD;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,SAAS,EACd,WAAW,EAAE,WAAW,EACxB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,uBAAuB,GAChC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAcjC"}
|
|
1
|
+
{"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/upload/resume.js"],"names":[],"mappings":"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"}
|
package/dist/upload/resume.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface ResumePartPlan {
|
|
|
18
18
|
readonly length: number;
|
|
19
19
|
}
|
|
20
20
|
/** Reason an unfinished same-name large file was not reused for resume. */
|
|
21
|
-
export type ResumeCandidateRejectedReason = 'search-truncated' | 'candidate-limit' | 'file-name-mismatch' | 'content-type-mismatch' | 'file-info-mismatch' | 'source-size-mismatch' | 'part-size-mismatch' | 'encryption-mismatch' | 'sse-c-unsupported' | 'retention-mismatch' | 'legal-hold-mismatch' | 'part-length-mismatch';
|
|
21
|
+
export type ResumeCandidateRejectedReason = 'search-truncated' | 'candidate-limit' | 'file-name-mismatch' | 'content-type-mismatch' | 'file-info-mismatch' | 'source-size-mismatch' | 'part-size-mismatch' | 'upload-timestamp-mismatch' | 'encryption-mismatch' | 'sse-c-unsupported' | 'retention-mismatch' | 'legal-hold-mismatch' | 'part-length-mismatch';
|
|
22
22
|
/** Diagnostic event emitted when resume discovery declines a candidate. */
|
|
23
23
|
export interface ResumeCandidateRejectedEvent {
|
|
24
24
|
/** ID of the unfinished large file, when the event is candidate-specific. */
|
|
@@ -44,6 +44,8 @@ export interface ResumeCandidateCriteria {
|
|
|
44
44
|
readonly partSize: number;
|
|
45
45
|
/** Planned local parts used to verify already-uploaded server part lengths. */
|
|
46
46
|
readonly parts: readonly ResumePartPlan[];
|
|
47
|
+
/** Requested B2 upload timestamp override, if configured by the caller. */
|
|
48
|
+
readonly customUploadTimestamp?: number;
|
|
47
49
|
/** Explicit server-side encryption option, if configured by the caller. */
|
|
48
50
|
readonly serverSideEncryption?: EncryptionSetting;
|
|
49
51
|
/** Explicit Object Lock retention option, if configured by the caller. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/upload/resume.js"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resume.d.ts","sourceRoot":"","sources":["../../src/upload/resume.js"],"names":[],"mappings":"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"}
|
package/dist/upload/resume.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { largeFileId } from "../types/ids.js";
|
|
2
2
|
import { ResumeFileIdMismatchError } from "../errors/index.js";
|
|
3
|
-
import { BucketRetentionMode } from "../types/bucket.js";
|
|
4
3
|
import { EncryptionMode } from "../types/encryption.js";
|
|
4
|
+
import { BucketRetentionMode } from "../types/bucket.js";
|
|
5
5
|
//#region src/upload/resume.ts
|
|
6
6
|
/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */
|
|
7
7
|
var RESUME_SOURCE_SIZE_INFO_KEY = "b2_sdk_resume_source_size";
|
|
@@ -152,6 +152,7 @@ function candidateMetadataRejectReason(candidate, fileName, criteria) {
|
|
|
152
152
|
if (!recordEquals(candidateInfo.fileInfo, criteria.fileInfo)) return "file-info-mismatch";
|
|
153
153
|
if (candidateInfo.sourceSize !== void 0 && candidateInfo.sourceSize !== String(criteria.sourceSize)) return "source-size-mismatch";
|
|
154
154
|
if (candidateInfo.partSize !== void 0 && candidateInfo.partSize !== String(criteria.partSize)) return "part-size-mismatch";
|
|
155
|
+
if (criteria.customUploadTimestamp !== void 0 && candidate.uploadTimestamp !== criteria.customUploadTimestamp) return "upload-timestamp-mismatch";
|
|
155
156
|
const encryptionRejectReason = serverSideEncryptionRejectReason(candidate.serverSideEncryption, criteria.serverSideEncryption);
|
|
156
157
|
if (encryptionRejectReason !== null) return encryptionRejectReason;
|
|
157
158
|
if (!fileRetentionMatches(candidate.fileRetention, criteria.fileRetention, criteria.defaultFileRetention, criteria.defaultFileRetentionUnreadable === true, candidate.uploadTimestamp)) return "retention-mismatch";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resume.js","names":[],"sources":["../../src/upload/resume.ts"],"sourcesContent":["import type { AccountInfo } from '../auth/account-info.ts'\nimport { ResumeFileIdMismatchError } from '../errors/index.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport { BucketRetentionMode, type BucketRetentionPolicy } from '../types/bucket.ts'\nimport {\n EncryptionMode,\n type EncryptionSetting,\n type PublicEncryptionSetting,\n} from '../types/encryption.ts'\nimport type { BucketId, LargeFileId } from '../types/ids.ts'\nimport { largeFileId as largeFileIdOf } from '../types/ids.ts'\nimport type { FileRetentionValue, LegalHoldValue } from '../types/lock.ts'\nimport type {\n ReadableFileRetention,\n ReadableLegalHold,\n UnfinishedLargeFile,\n} from '../types/upload.ts'\n\nexport { ResumeFileIdMismatchError }\n\n/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */\nexport const RESUME_SOURCE_SIZE_INFO_KEY = 'b2_sdk_resume_source_size'\n\n/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */\nexport const RESUME_PART_SIZE_INFO_KEY = 'b2_sdk_resume_part_size'\n\nconst DEFAULT_MAX_RESUME_LIST_PAGES = 10\nconst DEFAULT_MAX_RESUME_PART_CANDIDATES = 25\nconst DEFAULT_MAX_RESUME_PART_PAGES = 10\n\n/** One planned part from the local upload source. */\nexport interface ResumePartPlan {\n /** 1-based B2 part number. */\n readonly partNumber: number\n /** Expected byte length for this part. */\n readonly length: number\n}\n\n/** Reason an unfinished same-name large file was not reused for resume. */\nexport type ResumeCandidateRejectedReason =\n | 'search-truncated'\n | 'candidate-limit'\n | 'file-name-mismatch'\n | 'content-type-mismatch'\n | 'file-info-mismatch'\n | 'source-size-mismatch'\n | 'part-size-mismatch'\n | 'encryption-mismatch'\n | 'sse-c-unsupported'\n | 'retention-mismatch'\n | 'legal-hold-mismatch'\n | 'part-length-mismatch'\n\n/** Diagnostic event emitted when resume discovery declines a candidate. */\nexport interface ResumeCandidateRejectedEvent {\n /** ID of the unfinished large file, when the event is candidate-specific. */\n readonly fileId?: LargeFileId\n /** Requested destination file name. */\n readonly requestedFileName: string\n /** Actual candidate file name, when the event is candidate-specific. */\n readonly candidateFileName?: string\n /** Machine-readable rejection reason. */\n readonly reason: ResumeCandidateRejectedReason\n}\n\n/** Diagnostic callback for declined resume candidates. */\nexport type ResumeCandidateRejectedListener = (event: ResumeCandidateRejectedEvent) => void\n\n/** Compatibility requirements an unfinished large file must satisfy before reuse. */\nexport interface ResumeCandidateCriteria {\n /** Effective MIME type for the upload, including SDK defaults. */\n readonly contentType: string\n /** Caller-owned file info. Legacy SDK resume keys on candidates are ignored separately. */\n readonly fileInfo: Record<string, string>\n /** Source size in bytes, checked against legacy SDK metadata when a candidate carries it. */\n readonly sourceSize: number\n /** Effective multipart part size, checked against legacy SDK metadata when present. */\n readonly partSize: number\n /** Planned local parts used to verify already-uploaded server part lengths. */\n readonly parts: readonly ResumePartPlan[]\n /** Explicit server-side encryption option, if configured by the caller. */\n readonly serverSideEncryption?: EncryptionSetting\n /** Explicit Object Lock retention option, if configured by the caller. */\n readonly fileRetention?: FileRetentionValue\n /** Effective readable bucket default retention when the caller omits fileRetention. */\n readonly defaultFileRetention?: BucketRetentionPolicy\n /** Whether bucket default retention exists but cannot be read by the caller. */\n readonly defaultFileRetentionUnreadable?: boolean\n /** Explicit legal hold option, if configured by the caller. */\n readonly legalHold?: LegalHoldValue\n /** Explicit unfinished large-file ID to verify before reuse. */\n readonly resumeFileId?: LargeFileId\n /** Abort signal used for B2 list requests and checked between pages. */\n readonly signal?: AbortSignal\n /** Optional diagnostic callback for candidates that are found but not reused. */\n readonly onCandidateRejected?: ResumeCandidateRejectedListener\n /** Maximum `b2_list_unfinished_large_files` pages to inspect. */\n readonly maxListPages?: number\n /** Maximum metadata-compatible candidates whose parts may be listed. */\n readonly maxPartCandidates?: number\n /** Maximum `b2_list_parts` pages to inspect for each metadata-compatible candidate. */\n readonly maxPartPages?: number\n /** Caller-supplied aggregate resume discovery timeout. */\n readonly discoveryTimeoutMs?: number\n}\n\n/** Uploaded part metadata used by the resume planner. */\nexport interface ResumePartInfo {\n /** SHA-1 stored by B2 for this uploaded part. */\n readonly contentSha1: string\n /** Byte length stored by B2 for this uploaded part. */\n readonly contentLength: number\n}\n\nconst LIST_PARTS_PAGE_SIZE = 100\n\n/** Information about an unfinished large file eligible for resume. */\nexport interface ResumeCandidate {\n /** ID of the unfinished large file. */\n readonly fileId: LargeFileId\n /** SHA-1 of each part already uploaded, indexed by 1-based part number. */\n readonly uploadedPartSha1s: ReadonlyMap<number, string>\n}\n\n/**\n * Finds an unfinished large file matching the given bucket and file name.\n * Returns `null` when no compatible candidate exists.\n *\n * With criteria, the newest compatible candidate is selected; incompatible\n * same-name uploads are ignored and optionally reported via\n * `onCandidateRejected`.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param bucketId - Target bucket of the upload.\n * @param fileName - Destination file name of the upload.\n * @param criteria - Upload identity and option checks.\n *\n * @returns A {@link ResumeCandidate} describing the candidate and its uploaded parts, or `null`.\n */\nexport async function findResumeCandidate(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n): Promise<ResumeCandidate | null> {\n const discoverySignal = createResumeDiscoverySignal(criteria)\n try {\n return await findResumeCandidateWithSignal(\n raw,\n accountInfo,\n bucketId,\n fileName,\n criteria,\n discoverySignal.signal,\n )\n } finally {\n discoverySignal.dispose()\n }\n}\n\nasync function findResumeCandidateWithSignal(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n signal: AbortSignal | undefined,\n): Promise<ResumeCandidate | null> {\n const matches: Array<{ file: UnfinishedLargeFile; sequence: number }> = []\n const maxListPages = criteria.maxListPages ?? DEFAULT_MAX_RESUME_LIST_PAGES\n const maxPartCandidates = criteria.maxPartCandidates ?? DEFAULT_MAX_RESUME_PART_CANDIDATES\n let sequence = 0\n let pageCount = 0\n const explicitResumeFileId = criteria.resumeFileId\n let startFileId: LargeFileId | undefined = explicitResumeFileId\n let truncated = false\n\n while (pageCount < maxListPages) {\n signal?.throwIfAborted()\n const unfinished = await abortableRequest(\n raw.listUnfinishedLargeFiles(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n bucketId,\n maxFileCount: explicitResumeFileId !== undefined ? 1 : 100,\n namePrefix: fileName,\n ...(startFileId !== undefined ? { startFileId } : {}),\n },\n signal !== undefined ? { signal } : undefined,\n ),\n signal,\n )\n pageCount++\n\n for (const file of unfinished.files) {\n const isMatch =\n explicitResumeFileId !== undefined\n ? file.fileId === explicitResumeFileId\n : file.fileName === fileName\n if (isMatch) {\n matches.push({ file, sequence })\n }\n sequence++\n }\n\n if (explicitResumeFileId !== undefined) break\n if (unfinished.nextFileId === null) break\n startFileId = unfinished.nextFileId\n truncated = pageCount >= maxListPages\n }\n\n if (truncated) {\n emitCandidateRejected(criteria, {\n requestedFileName: fileName,\n reason: 'search-truncated',\n })\n }\n\n matches.sort(compareNewestFirst)\n\n let partCandidatesInspected = 0\n for (const match of matches) {\n signal?.throwIfAborted()\n const rejection = candidateMetadataRejectReason(match.file, fileName, criteria)\n if (rejection !== null) {\n notifyCandidateRejected(criteria, match.file, fileName, rejection)\n continue\n }\n\n if (partCandidatesInspected >= maxPartCandidates) {\n notifyCandidateRejected(criteria, match.file, fileName, 'candidate-limit')\n break\n }\n\n partCandidatesInspected++\n const fileId = largeFileIdOf(match.file.fileId)\n const uploadedPartsResult = await collectResumePartInfo(raw, accountInfo, fileId, {\n maxPages: criteria.maxPartPages ?? DEFAULT_MAX_RESUME_PART_PAGES,\n maxParts: criteria.parts.length,\n ...(signal !== undefined ? { signal } : {}),\n })\n const uploadedParts = uploadedPartsResult.parts\n if (uploadedPartsResult.truncated || !uploadedPartsMatchPlan(uploadedParts, criteria.parts)) {\n notifyCandidateRejected(criteria, match.file, fileName, 'part-length-mismatch')\n continue\n }\n\n return { fileId, uploadedPartSha1s: partInfoToSha1s(uploadedParts) }\n }\n\n return null\n}\n\ninterface CollectResumePartInfoOptions {\n readonly signal?: AbortSignal\n readonly maxPages?: number\n readonly maxParts?: number\n}\n\nasync function collectResumePartInfo(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: LargeFileId,\n options: CollectResumePartInfoOptions,\n): Promise<{ readonly parts: Map<number, ResumePartInfo>; readonly truncated: boolean }> {\n const parts = new Map<number, ResumePartInfo>()\n const maxPages = options.maxPages ?? Number.POSITIVE_INFINITY\n const maxParts = options.maxParts ?? Number.POSITIVE_INFINITY\n let startPartNumber: number | undefined\n let pageCount = 0\n\n while (pageCount < maxPages) {\n options.signal?.throwIfAborted()\n // Fetch one part beyond the local plan so a candidate with extra uploaded\n // parts is detected and rejected instead of silently reused.\n const remainingParts =\n maxParts === Number.POSITIVE_INFINITY\n ? undefined\n : Math.max(1, Math.min(LIST_PARTS_PAGE_SIZE, maxParts - parts.size + 1))\n const page = await abortableRequest(\n raw.listParts(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n fileId,\n ...(startPartNumber !== undefined ? { startPartNumber } : {}),\n ...(remainingParts !== undefined ? { maxPartCount: remainingParts } : {}),\n },\n options.signal !== undefined ? { signal: options.signal } : undefined,\n ),\n options.signal,\n )\n pageCount++\n for (const part of page.parts) {\n parts.set(part.partNumber, {\n contentSha1: part.contentSha1,\n contentLength: part.contentLength,\n })\n if (parts.size > maxParts) return { parts, truncated: true }\n }\n if (page.nextPartNumber === null) return { parts, truncated: false }\n assertAdvancingPartCursor(fileId, startPartNumber, page.nextPartNumber)\n startPartNumber = page.nextPartNumber\n }\n\n return { parts, truncated: true }\n}\n\nfunction compareNewestFirst(\n a: { file: UnfinishedLargeFile; sequence: number },\n b: { file: UnfinishedLargeFile; sequence: number },\n): number {\n const aTime = a.file.uploadTimestamp ?? Number.NEGATIVE_INFINITY\n const bTime = b.file.uploadTimestamp ?? Number.NEGATIVE_INFINITY\n if (aTime !== bTime) return bTime - aTime\n return b.sequence - a.sequence\n}\n\nfunction candidateMetadataRejectReason(\n candidate: UnfinishedLargeFile,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n): ResumeCandidateRejectedReason | null {\n if (candidate.fileName !== fileName) return 'file-name-mismatch'\n if (\n criteria.contentType === 'b2/x-auto' &&\n criteria.resumeFileId === undefined &&\n candidate.contentType !== 'b2/x-auto'\n ) {\n return 'content-type-mismatch'\n }\n if (criteria.contentType !== 'b2/x-auto' && candidate.contentType !== criteria.contentType) {\n return 'content-type-mismatch'\n }\n\n const candidateInfo = splitResumeFileInfo(candidate.fileInfo ?? {})\n if (!recordEquals(candidateInfo.fileInfo, criteria.fileInfo)) return 'file-info-mismatch'\n if (\n candidateInfo.sourceSize !== undefined &&\n candidateInfo.sourceSize !== String(criteria.sourceSize)\n ) {\n return 'source-size-mismatch'\n }\n if (\n candidateInfo.partSize !== undefined &&\n candidateInfo.partSize !== String(criteria.partSize)\n ) {\n return 'part-size-mismatch'\n }\n\n const encryptionRejectReason = serverSideEncryptionRejectReason(\n candidate.serverSideEncryption,\n criteria.serverSideEncryption,\n )\n if (encryptionRejectReason !== null) return encryptionRejectReason\n if (\n !fileRetentionMatches(\n candidate.fileRetention,\n criteria.fileRetention,\n criteria.defaultFileRetention,\n criteria.defaultFileRetentionUnreadable === true,\n candidate.uploadTimestamp,\n )\n ) {\n return 'retention-mismatch'\n }\n if (!legalHoldMatches(candidate.legalHold, criteria.legalHold)) {\n return 'legal-hold-mismatch'\n }\n\n return null\n}\n\nfunction notifyCandidateRejected(\n criteria: ResumeCandidateCriteria,\n candidate: UnfinishedLargeFile,\n requestedFileName: string,\n reason: ResumeCandidateRejectedReason,\n): void {\n emitCandidateRejected(criteria, {\n fileId: largeFileIdOf(candidate.fileId),\n requestedFileName,\n candidateFileName: candidate.fileName,\n reason,\n })\n}\n\nfunction emitCandidateRejected(\n criteria: ResumeCandidateCriteria,\n event: ResumeCandidateRejectedEvent,\n): void {\n try {\n criteria.onCandidateRejected?.(event)\n } catch {\n // Diagnostic listeners should not make an otherwise valid upload fail.\n }\n}\n\nfunction uploadedPartsMatchPlan(\n uploadedParts: ReadonlyMap<number, ResumePartInfo>,\n plans: readonly ResumePartPlan[],\n): boolean {\n for (const [partNumber, part] of uploadedParts) {\n const planned = plans[partNumber - 1]\n if (planned === undefined) return false\n if (planned.partNumber !== partNumber) return false\n if (planned.length !== part.contentLength) return false\n }\n return true\n}\n\nfunction partInfoToSha1s(parts: ReadonlyMap<number, ResumePartInfo>): Map<number, string> {\n const sha1s = new Map<number, string>()\n for (const [partNumber, part] of parts) {\n sha1s.set(partNumber, part.contentSha1)\n }\n return sha1s\n}\n\nfunction recordEquals(a: Record<string, string>, b: Record<string, string>): boolean {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (a[key] !== b[key]) return false\n }\n return true\n}\n\ninterface SplitResumeFileInfo {\n readonly fileInfo: Record<string, string>\n readonly sourceSize?: string\n readonly partSize?: string\n}\n\nfunction splitResumeFileInfo(fileInfo: Record<string, string>): SplitResumeFileInfo {\n const userFileInfo: Record<string, string> = Object.create(null) as Record<string, string>\n let sourceSize: string | undefined\n let partSize: string | undefined\n for (const [key, value] of Object.entries(fileInfo)) {\n if (key === RESUME_SOURCE_SIZE_INFO_KEY) {\n sourceSize = value\n } else if (key === RESUME_PART_SIZE_INFO_KEY) {\n partSize = value\n } else {\n userFileInfo[key] = value\n }\n }\n return {\n fileInfo: userFileInfo,\n ...(sourceSize !== undefined ? { sourceSize } : {}),\n ...(partSize !== undefined ? { partSize } : {}),\n }\n}\n\nfunction fileRetentionMatches(\n candidate: ReadableFileRetention | undefined,\n expected: FileRetentionValue | undefined,\n defaultExpected: BucketRetentionPolicy | undefined,\n defaultUnreadable: boolean,\n uploadTimestamp: number | undefined,\n): boolean {\n if (expected === undefined && defaultUnreadable) return false\n if (expected === undefined && defaultExpected !== undefined) {\n if (defaultExpected.mode === BucketRetentionMode.None) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, null)\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueMatchesBucketDefault(candidate.value, defaultExpected, uploadTimestamp)\n }\n if (expected === undefined) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, null)\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, expected)\n}\n\nfunction fileRetentionValueMatchesBucketDefault(\n candidate: FileRetentionValue | null,\n expected: BucketRetentionPolicy,\n uploadTimestamp: number | undefined,\n): boolean {\n if (expected.period === null) return false\n if (candidate?.mode !== expected.mode || candidate.retainUntilTimestamp === null) return false\n if (uploadTimestamp === undefined) return false\n return candidate.retainUntilTimestamp === uploadTimestamp + retentionPeriodMillis(expected.period)\n}\n\nfunction retentionPeriodMillis(period: BucketRetentionPolicy['period']): number {\n if (period === null) return 0\n const days = period.unit === 'days' ? period.duration : period.duration * 365\n return days * 24 * 60 * 60 * 1000\n}\n\nfunction fileRetentionValueEquals(\n a: FileRetentionValue | null,\n b: FileRetentionValue | null,\n): boolean {\n return (\n (a?.mode ?? null) === (b?.mode ?? null) &&\n (a?.retainUntilTimestamp ?? null) === (b?.retainUntilTimestamp ?? null)\n )\n}\n\nfunction legalHoldMatches(\n candidate: ReadableLegalHold | undefined,\n expected: LegalHoldValue | undefined,\n): boolean {\n if (expected === undefined) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return candidate.value === null || candidate.value === 'off'\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return candidate.value === expected\n}\n\ninterface ResumeDiscoverySignal {\n readonly signal?: AbortSignal\n dispose(): void\n}\n\nfunction createResumeDiscoverySignal(criteria: ResumeCandidateCriteria): ResumeDiscoverySignal {\n if (criteria.signal !== undefined) {\n return {\n signal: criteria.signal,\n dispose() {},\n }\n }\n\n if (criteria.discoveryTimeoutMs === undefined) {\n return {\n dispose() {},\n }\n }\n\n const timeoutMs = criteria.discoveryTimeoutMs\n if (timeoutMs === Number.POSITIVE_INFINITY) {\n return {\n dispose() {},\n }\n }\n\n const controller = new AbortController()\n const timeout = setTimeout(\n () => {\n controller.abort(resumeDiscoveryTimeoutReason(timeoutMs))\n },\n Math.max(0, timeoutMs),\n )\n\n return {\n signal: controller.signal,\n dispose() {\n clearTimeout(timeout)\n },\n }\n}\n\nasync function abortableRequest<T>(\n request: Promise<T>,\n signal: AbortSignal | undefined,\n): Promise<T> {\n if (signal === undefined) return request\n if (signal.aborted) throw resumeAbortReason(signal)\n\n let removeAbortListener: (() => void) | undefined\n const aborted = new Promise<never>((_resolve, reject) => {\n const onAbort = () => reject(resumeAbortReason(signal))\n signal.addEventListener('abort', onAbort, { once: true })\n removeAbortListener = () => signal.removeEventListener('abort', onAbort)\n })\n\n try {\n return await Promise.race([request, aborted])\n } finally {\n removeAbortListener?.()\n void request.catch(() => {})\n }\n}\n\nfunction resumeAbortReason(signal: AbortSignal): unknown {\n return signal.reason ?? resumeAbortFallbackReason()\n}\n\nfunction resumeAbortFallbackReason(): Error {\n if (typeof DOMException === 'function') {\n return new DOMException('Resume discovery aborted', 'AbortError')\n }\n const error = new Error('Resume discovery aborted')\n error.name = 'AbortError'\n return error\n}\n\nfunction resumeDiscoveryTimeoutReason(timeoutMs: number): Error {\n if (typeof DOMException === 'function') {\n return new DOMException(`Resume discovery timed out after ${timeoutMs} ms`, 'TimeoutError')\n }\n const error = new Error(`Resume discovery timed out after ${timeoutMs} ms`)\n error.name = 'TimeoutError'\n return error\n}\n\ntype ListedEncryption = PublicEncryptionSetting | undefined\n\nfunction serverSideEncryptionRejectReason(\n candidate: ListedEncryption,\n expected: EncryptionSetting | undefined,\n): 'encryption-mismatch' | 'sse-c-unsupported' | null {\n if (expected?.mode === EncryptionMode.SseC) return 'sse-c-unsupported'\n\n const actual = normalizeEncryption(candidate)\n if (expected === undefined) {\n if (candidate === undefined) return null\n if (actual === undefined) return 'encryption-mismatch'\n if (actual.mode === EncryptionMode.None) return null\n if (actual.mode === EncryptionMode.SseB2) return null\n return actual.mode === EncryptionMode.SseC ? 'sse-c-unsupported' : 'encryption-mismatch'\n }\n\n const normalizedExpected = normalizeEncryption(expected)\n if (normalizedExpected?.mode === EncryptionMode.None && candidate === undefined) return null\n if (actual === undefined || normalizedExpected === undefined) return 'encryption-mismatch'\n if (actual.mode !== normalizedExpected.mode) return 'encryption-mismatch'\n if (actual.mode === EncryptionMode.None) return null\n return actual.algorithm === normalizedExpected.algorithm ? null : 'encryption-mismatch'\n}\n\ninterface NormalizedEncryption {\n readonly mode: EncryptionMode\n readonly algorithm?: string\n}\n\nfunction normalizeEncryption(\n encryption: EncryptionSetting | ListedEncryption,\n): NormalizedEncryption | undefined {\n if (encryption === undefined) return undefined\n // Real B2 responses may spell no encryption as `{ mode: null, algorithm: null }`.\n if (encryption.mode === null || encryption.mode === EncryptionMode.None) {\n return { mode: EncryptionMode.None }\n }\n if (encryption.mode !== EncryptionMode.SseB2 && encryption.mode !== EncryptionMode.SseC) {\n return undefined\n }\n return {\n mode: encryption.mode,\n algorithm: encryption.algorithm,\n }\n}\n\nfunction assertAdvancingPartCursor(\n fileId: LargeFileId,\n previous: number | undefined,\n next: number,\n): void {\n if (!Number.isInteger(next) || next < 1 || (previous !== undefined && next <= previous)) {\n throw new Error(\n `uploadLargeFile: listParts returned a non-advancing nextPartNumber for ${fileId}; aborting resume.`,\n )\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resume.js","names":[],"sources":["../../src/upload/resume.ts"],"sourcesContent":["import type { AccountInfo } from '../auth/account-info.ts'\nimport { ResumeFileIdMismatchError } from '../errors/index.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport { BucketRetentionMode, type BucketRetentionPolicy } from '../types/bucket.ts'\nimport {\n EncryptionMode,\n type EncryptionSetting,\n type PublicEncryptionSetting,\n} from '../types/encryption.ts'\nimport type { BucketId, LargeFileId } from '../types/ids.ts'\nimport { largeFileId as largeFileIdOf } from '../types/ids.ts'\nimport type { FileRetentionValue, LegalHoldValue } from '../types/lock.ts'\nimport type {\n ReadableFileRetention,\n ReadableLegalHold,\n UnfinishedLargeFile,\n} from '../types/upload.ts'\n\nexport { ResumeFileIdMismatchError }\n\n/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */\nexport const RESUME_SOURCE_SIZE_INFO_KEY = 'b2_sdk_resume_source_size'\n\n/** Compatibility-only file-info key read from legacy unfinished uploads; new uploads do not write it. */\nexport const RESUME_PART_SIZE_INFO_KEY = 'b2_sdk_resume_part_size'\n\nconst DEFAULT_MAX_RESUME_LIST_PAGES = 10\nconst DEFAULT_MAX_RESUME_PART_CANDIDATES = 25\nconst DEFAULT_MAX_RESUME_PART_PAGES = 10\n\n/** One planned part from the local upload source. */\nexport interface ResumePartPlan {\n /** 1-based B2 part number. */\n readonly partNumber: number\n /** Expected byte length for this part. */\n readonly length: number\n}\n\n/** Reason an unfinished same-name large file was not reused for resume. */\nexport type ResumeCandidateRejectedReason =\n | 'search-truncated'\n | 'candidate-limit'\n | 'file-name-mismatch'\n | 'content-type-mismatch'\n | 'file-info-mismatch'\n | 'source-size-mismatch'\n | 'part-size-mismatch'\n | 'upload-timestamp-mismatch'\n | 'encryption-mismatch'\n | 'sse-c-unsupported'\n | 'retention-mismatch'\n | 'legal-hold-mismatch'\n | 'part-length-mismatch'\n\n/** Diagnostic event emitted when resume discovery declines a candidate. */\nexport interface ResumeCandidateRejectedEvent {\n /** ID of the unfinished large file, when the event is candidate-specific. */\n readonly fileId?: LargeFileId\n /** Requested destination file name. */\n readonly requestedFileName: string\n /** Actual candidate file name, when the event is candidate-specific. */\n readonly candidateFileName?: string\n /** Machine-readable rejection reason. */\n readonly reason: ResumeCandidateRejectedReason\n}\n\n/** Diagnostic callback for declined resume candidates. */\nexport type ResumeCandidateRejectedListener = (event: ResumeCandidateRejectedEvent) => void\n\n/** Compatibility requirements an unfinished large file must satisfy before reuse. */\nexport interface ResumeCandidateCriteria {\n /** Effective MIME type for the upload, including SDK defaults. */\n readonly contentType: string\n /** Caller-owned file info. Legacy SDK resume keys on candidates are ignored separately. */\n readonly fileInfo: Record<string, string>\n /** Source size in bytes, checked against legacy SDK metadata when a candidate carries it. */\n readonly sourceSize: number\n /** Effective multipart part size, checked against legacy SDK metadata when present. */\n readonly partSize: number\n /** Planned local parts used to verify already-uploaded server part lengths. */\n readonly parts: readonly ResumePartPlan[]\n /** Requested B2 upload timestamp override, if configured by the caller. */\n readonly customUploadTimestamp?: number\n /** Explicit server-side encryption option, if configured by the caller. */\n readonly serverSideEncryption?: EncryptionSetting\n /** Explicit Object Lock retention option, if configured by the caller. */\n readonly fileRetention?: FileRetentionValue\n /** Effective readable bucket default retention when the caller omits fileRetention. */\n readonly defaultFileRetention?: BucketRetentionPolicy\n /** Whether bucket default retention exists but cannot be read by the caller. */\n readonly defaultFileRetentionUnreadable?: boolean\n /** Explicit legal hold option, if configured by the caller. */\n readonly legalHold?: LegalHoldValue\n /** Explicit unfinished large-file ID to verify before reuse. */\n readonly resumeFileId?: LargeFileId\n /** Abort signal used for B2 list requests and checked between pages. */\n readonly signal?: AbortSignal\n /** Optional diagnostic callback for candidates that are found but not reused. */\n readonly onCandidateRejected?: ResumeCandidateRejectedListener\n /** Maximum `b2_list_unfinished_large_files` pages to inspect. */\n readonly maxListPages?: number\n /** Maximum metadata-compatible candidates whose parts may be listed. */\n readonly maxPartCandidates?: number\n /** Maximum `b2_list_parts` pages to inspect for each metadata-compatible candidate. */\n readonly maxPartPages?: number\n /** Caller-supplied aggregate resume discovery timeout. */\n readonly discoveryTimeoutMs?: number\n}\n\n/** Uploaded part metadata used by the resume planner. */\nexport interface ResumePartInfo {\n /** SHA-1 stored by B2 for this uploaded part. */\n readonly contentSha1: string\n /** Byte length stored by B2 for this uploaded part. */\n readonly contentLength: number\n}\n\nconst LIST_PARTS_PAGE_SIZE = 100\n\n/** Information about an unfinished large file eligible for resume. */\nexport interface ResumeCandidate {\n /** ID of the unfinished large file. */\n readonly fileId: LargeFileId\n /** SHA-1 of each part already uploaded, indexed by 1-based part number. */\n readonly uploadedPartSha1s: ReadonlyMap<number, string>\n}\n\n/**\n * Finds an unfinished large file matching the given bucket and file name.\n * Returns `null` when no compatible candidate exists.\n *\n * With criteria, the newest compatible candidate is selected; incompatible\n * same-name uploads are ignored and optionally reported via\n * `onCandidateRejected`.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param bucketId - Target bucket of the upload.\n * @param fileName - Destination file name of the upload.\n * @param criteria - Upload identity and option checks.\n *\n * @returns A {@link ResumeCandidate} describing the candidate and its uploaded parts, or `null`.\n */\nexport async function findResumeCandidate(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n): Promise<ResumeCandidate | null> {\n const discoverySignal = createResumeDiscoverySignal(criteria)\n try {\n return await findResumeCandidateWithSignal(\n raw,\n accountInfo,\n bucketId,\n fileName,\n criteria,\n discoverySignal.signal,\n )\n } finally {\n discoverySignal.dispose()\n }\n}\n\nasync function findResumeCandidateWithSignal(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n signal: AbortSignal | undefined,\n): Promise<ResumeCandidate | null> {\n const matches: Array<{ file: UnfinishedLargeFile; sequence: number }> = []\n const maxListPages = criteria.maxListPages ?? DEFAULT_MAX_RESUME_LIST_PAGES\n const maxPartCandidates = criteria.maxPartCandidates ?? DEFAULT_MAX_RESUME_PART_CANDIDATES\n let sequence = 0\n let pageCount = 0\n const explicitResumeFileId = criteria.resumeFileId\n let startFileId: LargeFileId | undefined = explicitResumeFileId\n let truncated = false\n\n while (pageCount < maxListPages) {\n signal?.throwIfAborted()\n const unfinished = await abortableRequest(\n raw.listUnfinishedLargeFiles(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n bucketId,\n maxFileCount: explicitResumeFileId !== undefined ? 1 : 100,\n namePrefix: fileName,\n ...(startFileId !== undefined ? { startFileId } : {}),\n },\n signal !== undefined ? { signal } : undefined,\n ),\n signal,\n )\n pageCount++\n\n for (const file of unfinished.files) {\n const isMatch =\n explicitResumeFileId !== undefined\n ? file.fileId === explicitResumeFileId\n : file.fileName === fileName\n if (isMatch) {\n matches.push({ file, sequence })\n }\n sequence++\n }\n\n if (explicitResumeFileId !== undefined) break\n if (unfinished.nextFileId === null) break\n startFileId = unfinished.nextFileId\n truncated = pageCount >= maxListPages\n }\n\n if (truncated) {\n emitCandidateRejected(criteria, {\n requestedFileName: fileName,\n reason: 'search-truncated',\n })\n }\n\n matches.sort(compareNewestFirst)\n\n let partCandidatesInspected = 0\n for (const match of matches) {\n signal?.throwIfAborted()\n const rejection = candidateMetadataRejectReason(match.file, fileName, criteria)\n if (rejection !== null) {\n notifyCandidateRejected(criteria, match.file, fileName, rejection)\n continue\n }\n\n if (partCandidatesInspected >= maxPartCandidates) {\n notifyCandidateRejected(criteria, match.file, fileName, 'candidate-limit')\n break\n }\n\n partCandidatesInspected++\n const fileId = largeFileIdOf(match.file.fileId)\n const uploadedPartsResult = await collectResumePartInfo(raw, accountInfo, fileId, {\n maxPages: criteria.maxPartPages ?? DEFAULT_MAX_RESUME_PART_PAGES,\n maxParts: criteria.parts.length,\n ...(signal !== undefined ? { signal } : {}),\n })\n const uploadedParts = uploadedPartsResult.parts\n if (uploadedPartsResult.truncated || !uploadedPartsMatchPlan(uploadedParts, criteria.parts)) {\n notifyCandidateRejected(criteria, match.file, fileName, 'part-length-mismatch')\n continue\n }\n\n return { fileId, uploadedPartSha1s: partInfoToSha1s(uploadedParts) }\n }\n\n return null\n}\n\ninterface CollectResumePartInfoOptions {\n readonly signal?: AbortSignal\n readonly maxPages?: number\n readonly maxParts?: number\n}\n\nasync function collectResumePartInfo(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: LargeFileId,\n options: CollectResumePartInfoOptions,\n): Promise<{ readonly parts: Map<number, ResumePartInfo>; readonly truncated: boolean }> {\n const parts = new Map<number, ResumePartInfo>()\n const maxPages = options.maxPages ?? Number.POSITIVE_INFINITY\n const maxParts = options.maxParts ?? Number.POSITIVE_INFINITY\n let startPartNumber: number | undefined\n let pageCount = 0\n\n while (pageCount < maxPages) {\n options.signal?.throwIfAborted()\n // Fetch one part beyond the local plan so a candidate with extra uploaded\n // parts is detected and rejected instead of silently reused.\n const remainingParts =\n maxParts === Number.POSITIVE_INFINITY\n ? undefined\n : Math.max(1, Math.min(LIST_PARTS_PAGE_SIZE, maxParts - parts.size + 1))\n const page = await abortableRequest(\n raw.listParts(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n fileId,\n ...(startPartNumber !== undefined ? { startPartNumber } : {}),\n ...(remainingParts !== undefined ? { maxPartCount: remainingParts } : {}),\n },\n options.signal !== undefined ? { signal: options.signal } : undefined,\n ),\n options.signal,\n )\n pageCount++\n for (const part of page.parts) {\n parts.set(part.partNumber, {\n contentSha1: part.contentSha1,\n contentLength: part.contentLength,\n })\n if (parts.size > maxParts) return { parts, truncated: true }\n }\n if (page.nextPartNumber === null) return { parts, truncated: false }\n assertAdvancingPartCursor(fileId, startPartNumber, page.nextPartNumber)\n startPartNumber = page.nextPartNumber\n }\n\n return { parts, truncated: true }\n}\n\nfunction compareNewestFirst(\n a: { file: UnfinishedLargeFile; sequence: number },\n b: { file: UnfinishedLargeFile; sequence: number },\n): number {\n const aTime = a.file.uploadTimestamp ?? Number.NEGATIVE_INFINITY\n const bTime = b.file.uploadTimestamp ?? Number.NEGATIVE_INFINITY\n if (aTime !== bTime) return bTime - aTime\n return b.sequence - a.sequence\n}\n\nfunction candidateMetadataRejectReason(\n candidate: UnfinishedLargeFile,\n fileName: string,\n criteria: ResumeCandidateCriteria,\n): ResumeCandidateRejectedReason | null {\n if (candidate.fileName !== fileName) return 'file-name-mismatch'\n if (\n criteria.contentType === 'b2/x-auto' &&\n criteria.resumeFileId === undefined &&\n candidate.contentType !== 'b2/x-auto'\n ) {\n return 'content-type-mismatch'\n }\n if (criteria.contentType !== 'b2/x-auto' && candidate.contentType !== criteria.contentType) {\n return 'content-type-mismatch'\n }\n\n const candidateInfo = splitResumeFileInfo(candidate.fileInfo ?? {})\n if (!recordEquals(candidateInfo.fileInfo, criteria.fileInfo)) return 'file-info-mismatch'\n if (\n candidateInfo.sourceSize !== undefined &&\n candidateInfo.sourceSize !== String(criteria.sourceSize)\n ) {\n return 'source-size-mismatch'\n }\n if (\n candidateInfo.partSize !== undefined &&\n candidateInfo.partSize !== String(criteria.partSize)\n ) {\n return 'part-size-mismatch'\n }\n if (\n criteria.customUploadTimestamp !== undefined &&\n candidate.uploadTimestamp !== criteria.customUploadTimestamp\n ) {\n return 'upload-timestamp-mismatch'\n }\n\n const encryptionRejectReason = serverSideEncryptionRejectReason(\n candidate.serverSideEncryption,\n criteria.serverSideEncryption,\n )\n if (encryptionRejectReason !== null) return encryptionRejectReason\n if (\n !fileRetentionMatches(\n candidate.fileRetention,\n criteria.fileRetention,\n criteria.defaultFileRetention,\n criteria.defaultFileRetentionUnreadable === true,\n candidate.uploadTimestamp,\n )\n ) {\n return 'retention-mismatch'\n }\n if (!legalHoldMatches(candidate.legalHold, criteria.legalHold)) {\n return 'legal-hold-mismatch'\n }\n\n return null\n}\n\nfunction notifyCandidateRejected(\n criteria: ResumeCandidateCriteria,\n candidate: UnfinishedLargeFile,\n requestedFileName: string,\n reason: ResumeCandidateRejectedReason,\n): void {\n emitCandidateRejected(criteria, {\n fileId: largeFileIdOf(candidate.fileId),\n requestedFileName,\n candidateFileName: candidate.fileName,\n reason,\n })\n}\n\nfunction emitCandidateRejected(\n criteria: ResumeCandidateCriteria,\n event: ResumeCandidateRejectedEvent,\n): void {\n try {\n criteria.onCandidateRejected?.(event)\n } catch {\n // Diagnostic listeners should not make an otherwise valid upload fail.\n }\n}\n\nfunction uploadedPartsMatchPlan(\n uploadedParts: ReadonlyMap<number, ResumePartInfo>,\n plans: readonly ResumePartPlan[],\n): boolean {\n for (const [partNumber, part] of uploadedParts) {\n const planned = plans[partNumber - 1]\n if (planned === undefined) return false\n if (planned.partNumber !== partNumber) return false\n if (planned.length !== part.contentLength) return false\n }\n return true\n}\n\nfunction partInfoToSha1s(parts: ReadonlyMap<number, ResumePartInfo>): Map<number, string> {\n const sha1s = new Map<number, string>()\n for (const [partNumber, part] of parts) {\n sha1s.set(partNumber, part.contentSha1)\n }\n return sha1s\n}\n\nfunction recordEquals(a: Record<string, string>, b: Record<string, string>): boolean {\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (a[key] !== b[key]) return false\n }\n return true\n}\n\ninterface SplitResumeFileInfo {\n readonly fileInfo: Record<string, string>\n readonly sourceSize?: string\n readonly partSize?: string\n}\n\nfunction splitResumeFileInfo(fileInfo: Record<string, string>): SplitResumeFileInfo {\n const userFileInfo: Record<string, string> = Object.create(null) as Record<string, string>\n let sourceSize: string | undefined\n let partSize: string | undefined\n for (const [key, value] of Object.entries(fileInfo)) {\n if (key === RESUME_SOURCE_SIZE_INFO_KEY) {\n sourceSize = value\n } else if (key === RESUME_PART_SIZE_INFO_KEY) {\n partSize = value\n } else {\n userFileInfo[key] = value\n }\n }\n return {\n fileInfo: userFileInfo,\n ...(sourceSize !== undefined ? { sourceSize } : {}),\n ...(partSize !== undefined ? { partSize } : {}),\n }\n}\n\nfunction fileRetentionMatches(\n candidate: ReadableFileRetention | undefined,\n expected: FileRetentionValue | undefined,\n defaultExpected: BucketRetentionPolicy | undefined,\n defaultUnreadable: boolean,\n uploadTimestamp: number | undefined,\n): boolean {\n if (expected === undefined && defaultUnreadable) return false\n if (expected === undefined && defaultExpected !== undefined) {\n if (defaultExpected.mode === BucketRetentionMode.None) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, null)\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueMatchesBucketDefault(candidate.value, defaultExpected, uploadTimestamp)\n }\n if (expected === undefined) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, null)\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return fileRetentionValueEquals(candidate.value, expected)\n}\n\nfunction fileRetentionValueMatchesBucketDefault(\n candidate: FileRetentionValue | null,\n expected: BucketRetentionPolicy,\n uploadTimestamp: number | undefined,\n): boolean {\n if (expected.period === null) return false\n if (candidate?.mode !== expected.mode || candidate.retainUntilTimestamp === null) return false\n if (uploadTimestamp === undefined) return false\n return candidate.retainUntilTimestamp === uploadTimestamp + retentionPeriodMillis(expected.period)\n}\n\nfunction retentionPeriodMillis(period: BucketRetentionPolicy['period']): number {\n if (period === null) return 0\n const days = period.unit === 'days' ? period.duration : period.duration * 365\n return days * 24 * 60 * 60 * 1000\n}\n\nfunction fileRetentionValueEquals(\n a: FileRetentionValue | null,\n b: FileRetentionValue | null,\n): boolean {\n return (\n (a?.mode ?? null) === (b?.mode ?? null) &&\n (a?.retainUntilTimestamp ?? null) === (b?.retainUntilTimestamp ?? null)\n )\n}\n\nfunction legalHoldMatches(\n candidate: ReadableLegalHold | undefined,\n expected: LegalHoldValue | undefined,\n): boolean {\n if (expected === undefined) {\n if (candidate === undefined) return true\n if (!candidate.isClientAuthorizedToRead) return false\n return candidate.value === null || candidate.value === 'off'\n }\n if (candidate === undefined || !candidate.isClientAuthorizedToRead) return false\n return candidate.value === expected\n}\n\ninterface ResumeDiscoverySignal {\n readonly signal?: AbortSignal\n dispose(): void\n}\n\nfunction createResumeDiscoverySignal(criteria: ResumeCandidateCriteria): ResumeDiscoverySignal {\n if (criteria.signal !== undefined) {\n return {\n signal: criteria.signal,\n dispose() {},\n }\n }\n\n if (criteria.discoveryTimeoutMs === undefined) {\n return {\n dispose() {},\n }\n }\n\n const timeoutMs = criteria.discoveryTimeoutMs\n if (timeoutMs === Number.POSITIVE_INFINITY) {\n return {\n dispose() {},\n }\n }\n\n const controller = new AbortController()\n const timeout = setTimeout(\n () => {\n controller.abort(resumeDiscoveryTimeoutReason(timeoutMs))\n },\n Math.max(0, timeoutMs),\n )\n\n return {\n signal: controller.signal,\n dispose() {\n clearTimeout(timeout)\n },\n }\n}\n\nasync function abortableRequest<T>(\n request: Promise<T>,\n signal: AbortSignal | undefined,\n): Promise<T> {\n if (signal === undefined) return request\n if (signal.aborted) throw resumeAbortReason(signal)\n\n let removeAbortListener: (() => void) | undefined\n const aborted = new Promise<never>((_resolve, reject) => {\n const onAbort = () => reject(resumeAbortReason(signal))\n signal.addEventListener('abort', onAbort, { once: true })\n removeAbortListener = () => signal.removeEventListener('abort', onAbort)\n })\n\n try {\n return await Promise.race([request, aborted])\n } finally {\n removeAbortListener?.()\n void request.catch(() => {})\n }\n}\n\nfunction resumeAbortReason(signal: AbortSignal): unknown {\n return signal.reason ?? resumeAbortFallbackReason()\n}\n\nfunction resumeAbortFallbackReason(): Error {\n if (typeof DOMException === 'function') {\n return new DOMException('Resume discovery aborted', 'AbortError')\n }\n const error = new Error('Resume discovery aborted')\n error.name = 'AbortError'\n return error\n}\n\nfunction resumeDiscoveryTimeoutReason(timeoutMs: number): Error {\n if (typeof DOMException === 'function') {\n return new DOMException(`Resume discovery timed out after ${timeoutMs} ms`, 'TimeoutError')\n }\n const error = new Error(`Resume discovery timed out after ${timeoutMs} ms`)\n error.name = 'TimeoutError'\n return error\n}\n\ntype ListedEncryption = PublicEncryptionSetting | undefined\n\nfunction serverSideEncryptionRejectReason(\n candidate: ListedEncryption,\n expected: EncryptionSetting | undefined,\n): 'encryption-mismatch' | 'sse-c-unsupported' | null {\n if (expected?.mode === EncryptionMode.SseC) return 'sse-c-unsupported'\n\n const actual = normalizeEncryption(candidate)\n if (expected === undefined) {\n if (candidate === undefined) return null\n if (actual === undefined) return 'encryption-mismatch'\n if (actual.mode === EncryptionMode.None) return null\n if (actual.mode === EncryptionMode.SseB2) return null\n return actual.mode === EncryptionMode.SseC ? 'sse-c-unsupported' : 'encryption-mismatch'\n }\n\n const normalizedExpected = normalizeEncryption(expected)\n if (normalizedExpected?.mode === EncryptionMode.None && candidate === undefined) return null\n if (actual === undefined || normalizedExpected === undefined) return 'encryption-mismatch'\n if (actual.mode !== normalizedExpected.mode) return 'encryption-mismatch'\n if (actual.mode === EncryptionMode.None) return null\n return actual.algorithm === normalizedExpected.algorithm ? null : 'encryption-mismatch'\n}\n\ninterface NormalizedEncryption {\n readonly mode: EncryptionMode\n readonly algorithm?: string\n}\n\nfunction normalizeEncryption(\n encryption: EncryptionSetting | ListedEncryption,\n): NormalizedEncryption | undefined {\n if (encryption === undefined) return undefined\n // Real B2 responses may spell no encryption as `{ mode: null, algorithm: null }`.\n if (encryption.mode === null || encryption.mode === EncryptionMode.None) {\n return { mode: EncryptionMode.None }\n }\n if (encryption.mode !== EncryptionMode.SseB2 && encryption.mode !== EncryptionMode.SseC) {\n return undefined\n }\n return {\n mode: encryption.mode,\n algorithm: encryption.algorithm,\n }\n}\n\nfunction assertAdvancingPartCursor(\n fileId: LargeFileId,\n previous: number | undefined,\n next: number,\n): void {\n if (!Number.isInteger(next) || next < 1 || (previous !== undefined && next <= previous)) {\n throw new Error(\n `uploadLargeFile: listParts returned a non-advancing nextPartNumber for ${fileId}; aborting resume.`,\n )\n }\n}\n"],"mappings":"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"}
|
package/dist/upload/retry.cjs
CHANGED
|
@@ -119,8 +119,10 @@ async function withFreshUploadUrlRetry(options) {
|
|
|
119
119
|
return result;
|
|
120
120
|
} catch (err) {
|
|
121
121
|
const retryError = normalizeUploadRetryError(err, options);
|
|
122
|
-
if (uploadEntry !== void 0)
|
|
123
|
-
|
|
122
|
+
if (uploadEntry !== void 0) {
|
|
123
|
+
if (isUploadRateLimitError(retryError)) options.returnEntry(uploadEntry);
|
|
124
|
+
else options.evictEntry(uploadEntry);
|
|
125
|
+
}
|
|
124
126
|
if (options.signal?.aborted) throw err;
|
|
125
127
|
if (isUploadRateLimitError(retryError) && uploadEntry !== void 0) throw retryError;
|
|
126
128
|
if (!isUploadRetryable(retryError, {
|
|
@@ -169,7 +171,10 @@ function normalizeUploadRetryError(err, options) {
|
|
|
169
171
|
return new require_errors_index.NetworkError(err.message, err);
|
|
170
172
|
}
|
|
171
173
|
if (err instanceof DOMException && err.name === "AbortError") return err;
|
|
172
|
-
if (err instanceof TypeError || err instanceof SyntaxError || err instanceof DOMException)
|
|
174
|
+
if (err instanceof TypeError || err instanceof SyntaxError || err instanceof DOMException) {
|
|
175
|
+
const message = err instanceof Error ? err.message : "Upload response read failed";
|
|
176
|
+
return new require_errors_index.NetworkError(message, err);
|
|
177
|
+
}
|
|
173
178
|
return err;
|
|
174
179
|
}
|
|
175
180
|
function isUploadUrlInvalidationError(err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"retry.cjs","names":[],"sources":["../../src/upload/retry.ts"],"sourcesContent":["import type { AccountInfo, UploadUrlEntry } from '../auth/account-info.ts'\nimport {\n B2Error,\n B2SsrfError,\n BadAuthTokenError,\n BadRequestError,\n BadUploadUrlError,\n NetworkError,\n UploadResponseBodyError,\n} from '../errors/index.ts'\nimport { computeBackoff, DEFAULT_RETRY_OPTIONS, type RetryOptions, sleep } from '../http/retry.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport type { EncryptionSetting } from '../types/encryption.ts'\nimport type { BucketId, LargeFileId } from '../types/ids.ts'\nimport type { UploadPartResponse } from '../types/upload.ts'\n\n/** Event emitted before an upload is retried against a fresh upload URL. */\nexport interface UploadRetryEvent {\n /** File name being uploaded. */\n readonly fileName: string\n /** Multipart part number, or `null` for a single-request file upload. */\n readonly partNumber: number | null\n /** One-based retry attempt that is about to run. */\n readonly attempt: number\n /** Maximum number of retry attempts allowed for this upload operation. */\n readonly maxRetries: number\n /** Backoff delay in milliseconds before the fresh URL is fetched. */\n readonly delayMs: number\n /** Classified error that triggered the fresh-URL retry. */\n readonly error: B2Error | NetworkError\n}\n\n/** Callback invoked before an upload retry fetches a fresh upload URL. */\nexport type UploadRetryListener = (event: UploadRetryEvent) => void\n\n/** Shared public options for upload-layer fresh-URL retries. */\nexport interface UploadRetryOptions {\n /** Retry settings shared with the main transport retry configuration. */\n readonly retry?: Partial<RetryOptions> | undefined\n /** Callback invoked before a fresh-URL upload retry. */\n readonly onUploadRetry?: UploadRetryListener | undefined\n /**\n * Whether ambiguous failures after an upload POST should be retried,\n * including response-body read failures and transport errors where B2 may\n * have stored the payload before the success response was lost.\n * Defaults to false. Set true only when duplicate versions or parts are\n * acceptable.\n */\n readonly retryResponseBodyFailures?: boolean | undefined\n}\n\ninterface UploadLayerRetryOptions extends Omit<UploadRetryOptions, 'retryResponseBodyFailures'> {\n readonly retryResponseBodyFailures: boolean\n /** Abort signal for cancelling upload attempts and retry backoff sleeps. */\n readonly signal?: AbortSignal | undefined\n}\n\ninterface FreshUrlRetryOptions<T> extends UploadLayerRetryOptions {\n readonly fileName: string\n readonly partNumber: number | null\n readonly checkout: () => UploadUrlEntry | null\n readonly fetchFresh: () => Promise<UploadUrlEntry>\n readonly returnEntry: (entry: UploadUrlEntry) => void\n readonly evictEntry: (entry: UploadUrlEntry) => void\n readonly upload: (entry: UploadUrlEntry) => Promise<T>\n}\n\nconst freshUrlRetryOverride: Partial<RetryOptions> = { maxRetries: 0 }\n\n/**\n * Resolves the public default for `retryResponseBodyFailures`. Callers must\n * opt into replaying ambiguous upload POST failures for every upload mode.\n *\n * @param value - Caller-provided override, if any.\n *\n * @returns The boolean value passed to the upload retry helper.\n */\nexport function resolveRetryResponseBodyFailures(value: boolean | undefined): boolean {\n return value ?? false\n}\n\n/**\n * Fetches a small-file upload URL, bypassing the pool.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param bucketId - Bucket to upload into.\n * @param signal - Optional abort signal for the fresh URL request.\n *\n * @returns A fresh upload URL entry.\n */\nexport async function fetchFreshUploadUrl(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n signal?: AbortSignal,\n): Promise<UploadUrlEntry> {\n const resp = await raw.getUploadUrl(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n bucketId,\n },\n {\n ...(signal !== undefined ? { signal } : {}),\n retry: freshUrlRetryOverride,\n },\n )\n return { uploadUrl: resp.uploadUrl, authorizationToken: resp.authorizationToken }\n}\n\n/**\n * Fetches a large-file part upload URL, bypassing the pool.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param fileId - Large file to upload a part into.\n * @param signal - Optional abort signal for the fresh URL request.\n *\n * @returns A fresh part upload URL entry.\n */\nexport async function fetchFreshPartUploadUrl(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: LargeFileId,\n signal?: AbortSignal,\n): Promise<UploadUrlEntry> {\n const resp = await raw.getUploadPartUrl(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n fileId,\n },\n {\n ...(signal !== undefined ? { signal } : {}),\n retry: freshUrlRetryOverride,\n },\n )\n return { uploadUrl: resp.uploadUrl, authorizationToken: resp.authorizationToken }\n}\n\ninterface UploadPartWithFreshUrlOptions extends UploadLayerRetryOptions {\n /** File name associated with the large-file upload. */\n readonly fileName: string\n /** One-based part number. */\n readonly partNumber: number\n /** Part bytes to upload. */\n readonly data: BodyInit\n /** Number of bytes in the part. */\n readonly contentLength: number\n /** SHA-1 hex digest of the part bytes. */\n readonly contentSha1: string\n /** Server-side encryption settings for this part. */\n readonly serverSideEncryption?: EncryptionSetting | undefined\n}\n\n/**\n * Uploads one multipart part with fresh-URL retry.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param fileId - Large file to upload a part into.\n * @param options - Part upload parameters and retry settings.\n *\n * @returns The uploaded part response.\n */\nexport function uploadPartWithFreshUrl(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: LargeFileId,\n options: UploadPartWithFreshUrlOptions,\n): Promise<UploadPartResponse> {\n return withFreshUploadUrlRetry({\n fileName: options.fileName,\n partNumber: options.partNumber,\n retry: options.retry,\n signal: options.signal,\n onUploadRetry: options.onUploadRetry,\n retryResponseBodyFailures: options.retryResponseBodyFailures,\n checkout: () => accountInfo.checkoutPartUploadUrl(fileId),\n fetchFresh: () => fetchFreshPartUploadUrl(raw, accountInfo, fileId, options.signal),\n returnEntry: (entry) => accountInfo.returnPartUploadUrl(fileId, entry),\n evictEntry: (entry) => accountInfo.evictPartUploadUrl(fileId, entry),\n upload: (entry) =>\n raw.uploadPart(\n entry.uploadUrl,\n {\n authorization: entry.authorizationToken,\n partNumber: options.partNumber,\n contentLength: options.contentLength,\n contentSha1: options.contentSha1,\n ...(options.serverSideEncryption !== undefined\n ? { serverSideEncryption: options.serverSideEncryption }\n : {}),\n },\n options.data,\n {\n ...(options.signal !== undefined ? { signal: options.signal } : {}),\n ...(options.retry !== undefined ? { retry: options.retry } : {}),\n },\n ),\n })\n}\n\n/**\n * Runs an upload operation with B2's documented retry flow: evict the failed\n * upload URL, back off, fetch a fresh upload URL, and retry there.\n *\n * For single-request file uploads, sending the POST again after a lost success\n * response can create a duplicate file version. Multipart retries re-send the\n * same part number instead. Response-body retry defaults are caller-specific:\n * single-request uploads keep them off by default, while multipart callers can\n * opt in with `retryResponseBodyFailures: true` when replaying the same part\n * number is acceptable.\n *\n * @param options - URL checkout, upload, eviction, and retry callbacks.\n *\n * @returns The successful upload result.\n */\nexport async function withFreshUploadUrlRetry<T>(options: FreshUrlRetryOptions<T>): Promise<T> {\n const retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...options.retry }\n\n for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {\n let uploadEntry: UploadUrlEntry | undefined\n let uploadStarted = false\n\n try {\n options.signal?.throwIfAborted()\n uploadEntry =\n attempt === 0\n ? (options.checkout() ?? (await options.fetchFresh()))\n : await options.fetchFresh()\n\n uploadStarted = true\n const result = await options.upload(uploadEntry)\n options.returnEntry(uploadEntry)\n return result\n } catch (err) {\n const retryError = normalizeUploadRetryError(err, options)\n if (uploadEntry !== undefined) {\n if (isUploadRateLimitError(retryError)) {\n options.returnEntry(uploadEntry)\n } else {\n options.evictEntry(uploadEntry)\n }\n }\n if (options.signal?.aborted) {\n throw err\n }\n if (isUploadRateLimitError(retryError) && uploadEntry !== undefined) {\n throw retryError\n }\n if (\n !isUploadRetryable(retryError, { ...options, uploadStarted }) ||\n attempt === retryOptions.maxRetries\n ) {\n throw retryError\n }\n\n const retryAttempt = attempt + 1\n const retryAfter = retryError instanceof B2Error ? retryError.retryAfter : undefined\n const delayMs = computeBackoff(attempt, retryOptions, retryAfter)\n notifyUploadRetry(options, {\n fileName: options.fileName,\n partNumber: options.partNumber,\n attempt: retryAttempt,\n maxRetries: retryOptions.maxRetries,\n delayMs,\n error: retryError,\n })\n await sleep(delayMs, options.signal)\n }\n }\n\n // Unreachable at runtime: the loop returns on success or throws from the\n // final failed attempt. This satisfies TypeScript's return-path analysis.\n /* v8 ignore next -- defensive return-path guard. */\n throw new NetworkError('Upload retry budget exhausted')\n}\n\nfunction notifyUploadRetry(options: UploadLayerRetryOptions, event: UploadRetryEvent): void {\n try {\n options.onUploadRetry?.(event)\n } catch {\n // Telemetry observers must not replace the underlying upload failure or\n // prevent a retry that can recover with a fresh upload URL.\n }\n}\n\nfunction isUploadRetryable(\n err: unknown,\n options: UploadLayerRetryOptions & {\n readonly partNumber: number | null\n readonly uploadStarted: boolean\n },\n): err is B2Error | NetworkError {\n if (err instanceof NetworkError) {\n if (err.cause instanceof B2SsrfError) return false\n if (!options.uploadStarted) return true\n return options.retryResponseBodyFailures === true\n }\n if (err instanceof BadAuthTokenError) return true\n if (isUploadUrlInvalidationError(err)) return true\n return err instanceof B2Error && err.retryable\n}\n\nfunction isUploadRateLimitError(err: unknown): err is B2Error {\n return err instanceof B2Error && err.status === 429\n}\n\nfunction normalizeUploadRetryError(err: unknown, options: UploadLayerRetryOptions): unknown {\n if (err instanceof B2Error || err instanceof NetworkError) return err\n if (err instanceof UploadResponseBodyError) {\n if (!options.retryResponseBodyFailures) return err\n return new NetworkError(err.message, err)\n }\n if (err instanceof DOMException && err.name === 'AbortError') return err\n if (err instanceof TypeError || err instanceof SyntaxError || err instanceof DOMException) {\n const message = err instanceof Error ? err.message : 'Upload response read failed'\n return new NetworkError(message, err)\n }\n return err\n}\n\nfunction isUploadUrlInvalidationError(err: unknown): boolean {\n if (err instanceof BadUploadUrlError) return true\n return err instanceof BadRequestError && /upload url/i.test(err.message)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"retry.cjs","names":[],"sources":["../../src/upload/retry.ts"],"sourcesContent":["import type { AccountInfo, UploadUrlEntry } from '../auth/account-info.ts'\nimport {\n B2Error,\n B2SsrfError,\n BadAuthTokenError,\n BadRequestError,\n BadUploadUrlError,\n NetworkError,\n UploadResponseBodyError,\n} from '../errors/index.ts'\nimport { computeBackoff, DEFAULT_RETRY_OPTIONS, type RetryOptions, sleep } from '../http/retry.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport type { EncryptionSetting } from '../types/encryption.ts'\nimport type { BucketId, LargeFileId } from '../types/ids.ts'\nimport type { UploadPartResponse } from '../types/upload.ts'\n\n/** Event emitted before an upload is retried against a fresh upload URL. */\nexport interface UploadRetryEvent {\n /** File name being uploaded. */\n readonly fileName: string\n /** Multipart part number, or `null` for a single-request file upload. */\n readonly partNumber: number | null\n /** One-based retry attempt that is about to run. */\n readonly attempt: number\n /** Maximum number of retry attempts allowed for this upload operation. */\n readonly maxRetries: number\n /** Backoff delay in milliseconds before the fresh URL is fetched. */\n readonly delayMs: number\n /** Classified error that triggered the fresh-URL retry. */\n readonly error: B2Error | NetworkError\n}\n\n/** Callback invoked before an upload retry fetches a fresh upload URL. */\nexport type UploadRetryListener = (event: UploadRetryEvent) => void\n\n/** Shared public options for upload-layer fresh-URL retries. */\nexport interface UploadRetryOptions {\n /** Retry settings shared with the main transport retry configuration. */\n readonly retry?: Partial<RetryOptions> | undefined\n /** Callback invoked before a fresh-URL upload retry. */\n readonly onUploadRetry?: UploadRetryListener | undefined\n /**\n * Whether ambiguous failures after an upload POST should be retried,\n * including response-body read failures and transport errors where B2 may\n * have stored the payload before the success response was lost.\n * Defaults to false. Set true only when duplicate versions or parts are\n * acceptable.\n */\n readonly retryResponseBodyFailures?: boolean | undefined\n}\n\ninterface UploadLayerRetryOptions extends Omit<UploadRetryOptions, 'retryResponseBodyFailures'> {\n readonly retryResponseBodyFailures: boolean\n /** Abort signal for cancelling upload attempts and retry backoff sleeps. */\n readonly signal?: AbortSignal | undefined\n}\n\ninterface FreshUrlRetryOptions<T> extends UploadLayerRetryOptions {\n readonly fileName: string\n readonly partNumber: number | null\n readonly checkout: () => UploadUrlEntry | null\n readonly fetchFresh: () => Promise<UploadUrlEntry>\n readonly returnEntry: (entry: UploadUrlEntry) => void\n readonly evictEntry: (entry: UploadUrlEntry) => void\n readonly upload: (entry: UploadUrlEntry) => Promise<T>\n}\n\nconst freshUrlRetryOverride: Partial<RetryOptions> = { maxRetries: 0 }\n\n/**\n * Resolves the public default for `retryResponseBodyFailures`. Callers must\n * opt into replaying ambiguous upload POST failures for every upload mode.\n *\n * @param value - Caller-provided override, if any.\n *\n * @returns The boolean value passed to the upload retry helper.\n */\nexport function resolveRetryResponseBodyFailures(value: boolean | undefined): boolean {\n return value ?? false\n}\n\n/**\n * Fetches a small-file upload URL, bypassing the pool.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param bucketId - Bucket to upload into.\n * @param signal - Optional abort signal for the fresh URL request.\n *\n * @returns A fresh upload URL entry.\n */\nexport async function fetchFreshUploadUrl(\n raw: RawClient,\n accountInfo: AccountInfo,\n bucketId: BucketId,\n signal?: AbortSignal,\n): Promise<UploadUrlEntry> {\n const resp = await raw.getUploadUrl(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n bucketId,\n },\n {\n ...(signal !== undefined ? { signal } : {}),\n retry: freshUrlRetryOverride,\n },\n )\n return { uploadUrl: resp.uploadUrl, authorizationToken: resp.authorizationToken }\n}\n\n/**\n * Fetches a large-file part upload URL, bypassing the pool.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param fileId - Large file to upload a part into.\n * @param signal - Optional abort signal for the fresh URL request.\n *\n * @returns A fresh part upload URL entry.\n */\nexport async function fetchFreshPartUploadUrl(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: LargeFileId,\n signal?: AbortSignal,\n): Promise<UploadUrlEntry> {\n const resp = await raw.getUploadPartUrl(\n accountInfo.getApiUrl(),\n accountInfo.getAuthToken(),\n {\n fileId,\n },\n {\n ...(signal !== undefined ? { signal } : {}),\n retry: freshUrlRetryOverride,\n },\n )\n return { uploadUrl: resp.uploadUrl, authorizationToken: resp.authorizationToken }\n}\n\ninterface UploadPartWithFreshUrlOptions extends UploadLayerRetryOptions {\n /** File name associated with the large-file upload. */\n readonly fileName: string\n /** One-based part number. */\n readonly partNumber: number\n /** Part bytes to upload. */\n readonly data: BodyInit\n /** Number of bytes in the part. */\n readonly contentLength: number\n /** SHA-1 hex digest of the part bytes. */\n readonly contentSha1: string\n /** Server-side encryption settings for this part. */\n readonly serverSideEncryption?: EncryptionSetting | undefined\n}\n\n/**\n * Uploads one multipart part with fresh-URL retry.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param fileId - Large file to upload a part into.\n * @param options - Part upload parameters and retry settings.\n *\n * @returns The uploaded part response.\n */\nexport function uploadPartWithFreshUrl(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: LargeFileId,\n options: UploadPartWithFreshUrlOptions,\n): Promise<UploadPartResponse> {\n return withFreshUploadUrlRetry({\n fileName: options.fileName,\n partNumber: options.partNumber,\n retry: options.retry,\n signal: options.signal,\n onUploadRetry: options.onUploadRetry,\n retryResponseBodyFailures: options.retryResponseBodyFailures,\n checkout: () => accountInfo.checkoutPartUploadUrl(fileId),\n fetchFresh: () => fetchFreshPartUploadUrl(raw, accountInfo, fileId, options.signal),\n returnEntry: (entry) => accountInfo.returnPartUploadUrl(fileId, entry),\n evictEntry: (entry) => accountInfo.evictPartUploadUrl(fileId, entry),\n upload: (entry) =>\n raw.uploadPart(\n entry.uploadUrl,\n {\n authorization: entry.authorizationToken,\n partNumber: options.partNumber,\n contentLength: options.contentLength,\n contentSha1: options.contentSha1,\n ...(options.serverSideEncryption !== undefined\n ? { serverSideEncryption: options.serverSideEncryption }\n : {}),\n },\n options.data,\n {\n ...(options.signal !== undefined ? { signal: options.signal } : {}),\n ...(options.retry !== undefined ? { retry: options.retry } : {}),\n },\n ),\n })\n}\n\n/**\n * Runs an upload operation with B2's documented retry flow: evict the failed\n * upload URL, back off, fetch a fresh upload URL, and retry there.\n *\n * For single-request file uploads, sending the POST again after a lost success\n * response can create a duplicate file version. Multipart retries re-send the\n * same part number instead. Response-body retry defaults are caller-specific:\n * single-request uploads keep them off by default, while multipart callers can\n * opt in with `retryResponseBodyFailures: true` when replaying the same part\n * number is acceptable.\n *\n * @param options - URL checkout, upload, eviction, and retry callbacks.\n *\n * @returns The successful upload result.\n */\nexport async function withFreshUploadUrlRetry<T>(options: FreshUrlRetryOptions<T>): Promise<T> {\n const retryOptions = { ...DEFAULT_RETRY_OPTIONS, ...options.retry }\n\n for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {\n let uploadEntry: UploadUrlEntry | undefined\n let uploadStarted = false\n\n try {\n options.signal?.throwIfAborted()\n uploadEntry =\n attempt === 0\n ? (options.checkout() ?? (await options.fetchFresh()))\n : await options.fetchFresh()\n\n uploadStarted = true\n const result = await options.upload(uploadEntry)\n options.returnEntry(uploadEntry)\n return result\n } catch (err) {\n const retryError = normalizeUploadRetryError(err, options)\n if (uploadEntry !== undefined) {\n if (isUploadRateLimitError(retryError)) {\n options.returnEntry(uploadEntry)\n } else {\n options.evictEntry(uploadEntry)\n }\n }\n if (options.signal?.aborted) {\n throw err\n }\n if (isUploadRateLimitError(retryError) && uploadEntry !== undefined) {\n throw retryError\n }\n if (\n !isUploadRetryable(retryError, { ...options, uploadStarted }) ||\n attempt === retryOptions.maxRetries\n ) {\n throw retryError\n }\n\n const retryAttempt = attempt + 1\n const retryAfter = retryError instanceof B2Error ? retryError.retryAfter : undefined\n const delayMs = computeBackoff(attempt, retryOptions, retryAfter)\n notifyUploadRetry(options, {\n fileName: options.fileName,\n partNumber: options.partNumber,\n attempt: retryAttempt,\n maxRetries: retryOptions.maxRetries,\n delayMs,\n error: retryError,\n })\n await sleep(delayMs, options.signal)\n }\n }\n\n // Unreachable at runtime: the loop returns on success or throws from the\n // final failed attempt. This satisfies TypeScript's return-path analysis.\n /* v8 ignore next -- defensive return-path guard. */\n throw new NetworkError('Upload retry budget exhausted')\n}\n\nfunction notifyUploadRetry(options: UploadLayerRetryOptions, event: UploadRetryEvent): void {\n try {\n options.onUploadRetry?.(event)\n } catch {\n // Telemetry observers must not replace the underlying upload failure or\n // prevent a retry that can recover with a fresh upload URL.\n }\n}\n\nfunction isUploadRetryable(\n err: unknown,\n options: UploadLayerRetryOptions & {\n readonly partNumber: number | null\n readonly uploadStarted: boolean\n },\n): err is B2Error | NetworkError {\n if (err instanceof NetworkError) {\n if (err.cause instanceof B2SsrfError) return false\n if (!options.uploadStarted) return true\n return options.retryResponseBodyFailures === true\n }\n if (err instanceof BadAuthTokenError) return true\n if (isUploadUrlInvalidationError(err)) return true\n return err instanceof B2Error && err.retryable\n}\n\nfunction isUploadRateLimitError(err: unknown): err is B2Error {\n return err instanceof B2Error && err.status === 429\n}\n\nfunction normalizeUploadRetryError(err: unknown, options: UploadLayerRetryOptions): unknown {\n if (err instanceof B2Error || err instanceof NetworkError) return err\n if (err instanceof UploadResponseBodyError) {\n if (!options.retryResponseBodyFailures) return err\n return new NetworkError(err.message, err)\n }\n if (err instanceof DOMException && err.name === 'AbortError') return err\n if (err instanceof TypeError || err instanceof SyntaxError || err instanceof DOMException) {\n const message = err instanceof Error ? err.message : 'Upload response read failed'\n return new NetworkError(message, err)\n }\n return err\n}\n\nfunction isUploadUrlInvalidationError(err: unknown): boolean {\n if (err instanceof BadUploadUrlError) return true\n return err instanceof BadRequestError && /upload url/i.test(err.message)\n}\n"],"mappings":"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"}
|
package/dist/upload/retry.js
CHANGED
|
@@ -119,8 +119,10 @@ async function withFreshUploadUrlRetry(options) {
|
|
|
119
119
|
return result;
|
|
120
120
|
} catch (err) {
|
|
121
121
|
const retryError = normalizeUploadRetryError(err, options);
|
|
122
|
-
if (uploadEntry !== void 0)
|
|
123
|
-
|
|
122
|
+
if (uploadEntry !== void 0) {
|
|
123
|
+
if (isUploadRateLimitError(retryError)) options.returnEntry(uploadEntry);
|
|
124
|
+
else options.evictEntry(uploadEntry);
|
|
125
|
+
}
|
|
124
126
|
if (options.signal?.aborted) throw err;
|
|
125
127
|
if (isUploadRateLimitError(retryError) && uploadEntry !== void 0) throw retryError;
|
|
126
128
|
if (!isUploadRetryable(retryError, {
|
|
@@ -169,7 +171,10 @@ function normalizeUploadRetryError(err, options) {
|
|
|
169
171
|
return new NetworkError(err.message, err);
|
|
170
172
|
}
|
|
171
173
|
if (err instanceof DOMException && err.name === "AbortError") return err;
|
|
172
|
-
if (err instanceof TypeError || err instanceof SyntaxError || err instanceof DOMException)
|
|
174
|
+
if (err instanceof TypeError || err instanceof SyntaxError || err instanceof DOMException) {
|
|
175
|
+
const message = err instanceof Error ? err.message : "Upload response read failed";
|
|
176
|
+
return new NetworkError(message, err);
|
|
177
|
+
}
|
|
173
178
|
return err;
|
|
174
179
|
}
|
|
175
180
|
function isUploadUrlInvalidationError(err) {
|