@backblaze-labs/b2-sdk 0.1.0 → 0.2.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.
Files changed (701) hide show
  1. package/CHANGELOG.md +110 -5
  2. package/README.md +144 -33
  3. package/dist/_virtual/_b2-sdk-version-json.cjs +4 -0
  4. package/dist/_virtual/_b2-sdk-version-json.js +4 -0
  5. package/dist/auth/account-info.d.cts +32 -0
  6. package/dist/auth/account-info.d.cts.map +1 -1
  7. package/dist/auth/account-info.d.ts +32 -0
  8. package/dist/auth/account-info.d.ts.map +1 -1
  9. package/dist/auth/file.cjs +408 -207
  10. package/dist/auth/file.cjs.map +1 -1
  11. package/dist/auth/file.d.cts +77 -3
  12. package/dist/auth/file.d.cts.map +1 -1
  13. package/dist/auth/file.d.ts +77 -3
  14. package/dist/auth/file.d.ts.map +1 -1
  15. package/dist/auth/file.js +407 -207
  16. package/dist/auth/file.js.map +1 -1
  17. package/dist/auth/in-memory.cjs +212 -183
  18. package/dist/auth/in-memory.cjs.map +1 -1
  19. package/dist/auth/in-memory.d.cts +8 -0
  20. package/dist/auth/in-memory.d.cts.map +1 -1
  21. package/dist/auth/in-memory.d.ts +8 -0
  22. package/dist/auth/in-memory.d.ts.map +1 -1
  23. package/dist/auth/in-memory.js +211 -182
  24. package/dist/auth/in-memory.js.map +1 -1
  25. package/dist/auth/index.cjs +7 -9
  26. package/dist/auth/index.d.cts +3 -2
  27. package/dist/auth/index.d.cts.map +1 -1
  28. package/dist/auth/index.d.ts +3 -2
  29. package/dist/auth/index.d.ts.map +1 -1
  30. package/dist/auth/index.js +2 -8
  31. package/dist/auth/realms.cjs +80 -7
  32. package/dist/auth/realms.cjs.map +1 -1
  33. package/dist/auth/realms.d.cts +27 -4
  34. package/dist/auth/realms.d.cts.map +1 -1
  35. package/dist/auth/realms.d.ts +27 -4
  36. package/dist/auth/realms.d.ts.map +1 -1
  37. package/dist/auth/realms.js +80 -9
  38. package/dist/auth/realms.js.map +1 -1
  39. package/dist/auth/upload-url-pool.cjs +55 -51
  40. package/dist/auth/upload-url-pool.cjs.map +1 -1
  41. package/dist/auth/upload-url-pool.js +55 -51
  42. package/dist/auth/upload-url-pool.js.map +1 -1
  43. package/dist/bucket.cjs +785 -787
  44. package/dist/bucket.cjs.map +1 -1
  45. package/dist/bucket.d.cts +35 -47
  46. package/dist/bucket.d.cts.map +1 -1
  47. package/dist/bucket.d.ts +35 -47
  48. package/dist/bucket.d.ts.map +1 -1
  49. package/dist/bucket.js +780 -782
  50. package/dist/bucket.js.map +1 -1
  51. package/dist/client.cjs +246 -228
  52. package/dist/client.cjs.map +1 -1
  53. package/dist/client.d.cts +25 -17
  54. package/dist/client.d.cts.map +1 -1
  55. package/dist/client.d.ts +25 -17
  56. package/dist/client.d.ts.map +1 -1
  57. package/dist/client.js +243 -225
  58. package/dist/client.js.map +1 -1
  59. package/dist/copy/large.cjs +114 -71
  60. package/dist/copy/large.cjs.map +1 -1
  61. package/dist/copy/large.d.cts +6 -0
  62. package/dist/copy/large.d.cts.map +1 -1
  63. package/dist/copy/large.d.ts +6 -0
  64. package/dist/copy/large.d.ts.map +1 -1
  65. package/dist/copy/large.js +112 -69
  66. package/dist/copy/large.js.map +1 -1
  67. package/dist/download/checksum.cjs +89 -0
  68. package/dist/download/checksum.cjs.map +1 -0
  69. package/dist/download/checksum.d.cts +40 -0
  70. package/dist/download/checksum.d.cts.map +1 -0
  71. package/dist/download/checksum.d.ts +40 -0
  72. package/dist/download/checksum.d.ts.map +1 -0
  73. package/dist/download/checksum.js +87 -0
  74. package/dist/download/checksum.js.map +1 -0
  75. package/dist/download/parallel.cjs +214 -118
  76. package/dist/download/parallel.cjs.map +1 -1
  77. package/dist/download/parallel.d.cts +5 -1
  78. package/dist/download/parallel.d.cts.map +1 -1
  79. package/dist/download/parallel.d.ts +5 -1
  80. package/dist/download/parallel.d.ts.map +1 -1
  81. package/dist/download/parallel.js +212 -116
  82. package/dist/download/parallel.js.map +1 -1
  83. package/dist/download/single.cjs +188 -97
  84. package/dist/download/single.cjs.map +1 -1
  85. package/dist/download/single.d.cts +6 -1
  86. package/dist/download/single.d.cts.map +1 -1
  87. package/dist/download/single.d.ts +6 -1
  88. package/dist/download/single.d.ts.map +1 -1
  89. package/dist/download/single.js +186 -98
  90. package/dist/download/single.js.map +1 -1
  91. package/dist/errors/index.cjs +699 -243
  92. package/dist/errors/index.cjs.map +1 -1
  93. package/dist/errors/index.d.cts +285 -82
  94. package/dist/errors/index.d.cts.map +1 -1
  95. package/dist/errors/index.d.ts +285 -82
  96. package/dist/errors/index.d.ts.map +1 -1
  97. package/dist/errors/index.js +678 -261
  98. package/dist/errors/index.js.map +1 -1
  99. package/dist/http/retry.cjs +45 -28
  100. package/dist/http/retry.cjs.map +1 -1
  101. package/dist/http/retry.d.cts +15 -2
  102. package/dist/http/retry.d.cts.map +1 -1
  103. package/dist/http/retry.d.ts +15 -2
  104. package/dist/http/retry.d.ts.map +1 -1
  105. package/dist/http/retry.js +46 -31
  106. package/dist/http/retry.js.map +1 -1
  107. package/dist/http/transport.cjs +438 -140
  108. package/dist/http/transport.cjs.map +1 -1
  109. package/dist/http/transport.d.cts +21 -2
  110. package/dist/http/transport.d.cts.map +1 -1
  111. package/dist/http/transport.d.ts +21 -2
  112. package/dist/http/transport.d.ts.map +1 -1
  113. package/dist/http/transport.js +437 -140
  114. package/dist/http/transport.js.map +1 -1
  115. package/dist/http/url-guard.cjs +154 -89
  116. package/dist/http/url-guard.cjs.map +1 -1
  117. package/dist/http/url-guard.d.cts +25 -3
  118. package/dist/http/url-guard.d.cts.map +1 -1
  119. package/dist/http/url-guard.d.ts +25 -3
  120. package/dist/http/url-guard.d.ts.map +1 -1
  121. package/dist/http/url-guard.js +152 -90
  122. package/dist/http/url-guard.js.map +1 -1
  123. package/dist/http/user-agent.cjs +84 -44
  124. package/dist/http/user-agent.cjs.map +1 -1
  125. package/dist/http/user-agent.js +84 -46
  126. package/dist/http/user-agent.js.map +1 -1
  127. package/dist/index.cjs +101 -77
  128. package/dist/index.d.cts +27 -21
  129. package/dist/index.d.cts.map +1 -1
  130. package/dist/index.d.ts +27 -21
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +15 -71
  133. package/dist/internal/b2-naming.cjs +66 -0
  134. package/dist/internal/b2-naming.cjs.map +1 -0
  135. package/dist/internal/b2-naming.d.cts +43 -0
  136. package/dist/internal/b2-naming.d.cts.map +1 -0
  137. package/dist/internal/b2-naming.d.ts +43 -0
  138. package/dist/internal/b2-naming.d.ts.map +1 -0
  139. package/dist/internal/b2-naming.js +60 -0
  140. package/dist/internal/b2-naming.js.map +1 -0
  141. package/dist/internal/upload-retry-options.cjs +21 -0
  142. package/dist/internal/upload-retry-options.cjs.map +1 -0
  143. package/dist/internal/upload-retry-options.d.cts +12 -0
  144. package/dist/internal/upload-retry-options.d.cts.map +1 -0
  145. package/dist/internal/upload-retry-options.d.ts +12 -0
  146. package/dist/internal/upload-retry-options.d.ts.map +1 -0
  147. package/dist/internal/upload-retry-options.js +21 -0
  148. package/dist/internal/upload-retry-options.js.map +1 -0
  149. package/dist/internal/url-redaction.cjs +31 -0
  150. package/dist/internal/url-redaction.cjs.map +1 -0
  151. package/dist/internal/url-redaction.d.cts +17 -0
  152. package/dist/internal/url-redaction.d.cts.map +1 -0
  153. package/dist/internal/url-redaction.d.ts +17 -0
  154. package/dist/internal/url-redaction.d.ts.map +1 -0
  155. package/dist/internal/url-redaction.js +31 -0
  156. package/dist/internal/url-redaction.js.map +1 -0
  157. package/dist/notifications/index.cjs +4 -6
  158. package/dist/notifications/index.d.cts +1 -1
  159. package/dist/notifications/index.d.cts.map +1 -1
  160. package/dist/notifications/index.d.ts +1 -1
  161. package/dist/notifications/index.d.ts.map +1 -1
  162. package/dist/notifications/index.js +1 -6
  163. package/dist/notifications/webhook.cjs +195 -85
  164. package/dist/notifications/webhook.cjs.map +1 -1
  165. package/dist/notifications/webhook.d.cts.map +1 -1
  166. package/dist/notifications/webhook.d.ts.map +1 -1
  167. package/dist/notifications/webhook.js +195 -87
  168. package/dist/notifications/webhook.js.map +1 -1
  169. package/dist/object.cjs +253 -210
  170. package/dist/object.cjs.map +1 -1
  171. package/dist/object.d.cts +9 -55
  172. package/dist/object.d.cts.map +1 -1
  173. package/dist/object.d.ts +9 -55
  174. package/dist/object.d.ts.map +1 -1
  175. package/dist/object.js +249 -206
  176. package/dist/object.js.map +1 -1
  177. package/dist/raw/encoding.cjs +74 -35
  178. package/dist/raw/encoding.cjs.map +1 -1
  179. package/dist/raw/encoding.js +74 -38
  180. package/dist/raw/encoding.js.map +1 -1
  181. package/dist/raw/index.cjs +678 -587
  182. package/dist/raw/index.cjs.map +1 -1
  183. package/dist/raw/index.d.cts +121 -18
  184. package/dist/raw/index.d.cts.map +1 -1
  185. package/dist/raw/index.d.ts +121 -18
  186. package/dist/raw/index.d.ts.map +1 -1
  187. package/dist/raw/index.js +674 -589
  188. package/dist/raw/index.js.map +1 -1
  189. package/dist/s3/index.cjs +355 -18
  190. package/dist/s3/index.cjs.map +1 -1
  191. package/dist/s3/index.d.cts +233 -5
  192. package/dist/s3/index.d.cts.map +1 -1
  193. package/dist/s3/index.d.ts +233 -5
  194. package/dist/s3/index.d.ts.map +1 -1
  195. package/dist/s3/index.js +351 -22
  196. package/dist/s3/index.js.map +1 -1
  197. package/dist/s3/sigv4.cjs +149 -0
  198. package/dist/s3/sigv4.cjs.map +1 -0
  199. package/dist/s3/sigv4.d.cts +43 -0
  200. package/dist/s3/sigv4.d.cts.map +1 -0
  201. package/dist/s3/sigv4.d.ts +43 -0
  202. package/dist/s3/sigv4.d.ts.map +1 -0
  203. package/dist/s3/sigv4.js +148 -0
  204. package/dist/s3/sigv4.js.map +1 -0
  205. package/dist/s3/validation.cjs +69 -0
  206. package/dist/s3/validation.cjs.map +1 -0
  207. package/dist/s3/validation.d.cts +43 -0
  208. package/dist/s3/validation.d.cts.map +1 -0
  209. package/dist/s3/validation.d.ts +43 -0
  210. package/dist/s3/validation.d.ts.map +1 -0
  211. package/dist/s3/validation.js +66 -0
  212. package/dist/s3/validation.js.map +1 -0
  213. package/dist/simulator/capabilities.cjs +63 -48
  214. package/dist/simulator/capabilities.cjs.map +1 -1
  215. package/dist/simulator/capabilities.js +63 -49
  216. package/dist/simulator/capabilities.js.map +1 -1
  217. package/dist/simulator/index.cjs +2124 -1548
  218. package/dist/simulator/index.cjs.map +1 -1
  219. package/dist/simulator/index.d.cts +79 -11
  220. package/dist/simulator/index.d.cts.map +1 -1
  221. package/dist/simulator/index.d.ts +79 -11
  222. package/dist/simulator/index.d.ts.map +1 -1
  223. package/dist/simulator/index.js +2112 -1546
  224. package/dist/simulator/index.js.map +1 -1
  225. package/dist/simulator/validation.cjs +191 -153
  226. package/dist/simulator/validation.cjs.map +1 -1
  227. package/dist/simulator/validation.d.cts +3 -21
  228. package/dist/simulator/validation.d.cts.map +1 -1
  229. package/dist/simulator/validation.d.ts +3 -21
  230. package/dist/simulator/validation.d.ts.map +1 -1
  231. package/dist/simulator/validation.js +188 -162
  232. package/dist/simulator/validation.js.map +1 -1
  233. package/dist/streams/collect.cjs +74 -24
  234. package/dist/streams/collect.cjs.map +1 -1
  235. package/dist/streams/collect.d.cts +14 -1
  236. package/dist/streams/collect.d.cts.map +1 -1
  237. package/dist/streams/collect.d.ts +14 -1
  238. package/dist/streams/collect.d.ts.map +1 -1
  239. package/dist/streams/collect.js +74 -25
  240. package/dist/streams/collect.js.map +1 -1
  241. package/dist/streams/file-source.cjs +287 -0
  242. package/dist/streams/file-source.cjs.map +1 -0
  243. package/dist/streams/file-source.d.cts +96 -0
  244. package/dist/streams/file-source.d.cts.map +1 -0
  245. package/dist/streams/file-source.d.ts +96 -0
  246. package/dist/streams/file-source.d.ts.map +1 -0
  247. package/dist/streams/file-source.js +286 -0
  248. package/dist/streams/file-source.js.map +1 -0
  249. package/dist/streams/hash.cjs +207 -115
  250. package/dist/streams/hash.cjs.map +1 -1
  251. package/dist/streams/hash.d.cts +5 -4
  252. package/dist/streams/hash.d.cts.map +1 -1
  253. package/dist/streams/hash.d.ts +5 -4
  254. package/dist/streams/hash.d.ts.map +1 -1
  255. package/dist/streams/hash.js +208 -95
  256. package/dist/streams/hash.js.map +1 -1
  257. package/dist/streams/index.cjs +12 -12
  258. package/dist/streams/index.d.cts +3 -3
  259. package/dist/streams/index.d.cts.map +1 -1
  260. package/dist/streams/index.d.ts +3 -3
  261. package/dist/streams/index.d.ts.map +1 -1
  262. package/dist/streams/index.js +2 -10
  263. package/dist/streams/progress.cjs +60 -46
  264. package/dist/streams/progress.cjs.map +1 -1
  265. package/dist/streams/progress.js +60 -46
  266. package/dist/streams/progress.js.map +1 -1
  267. package/dist/streams/source.cjs +316 -146
  268. package/dist/streams/source.cjs.map +1 -1
  269. package/dist/streams/source.d.cts +56 -18
  270. package/dist/streams/source.d.cts.map +1 -1
  271. package/dist/streams/source.d.ts +56 -18
  272. package/dist/streams/source.d.ts.map +1 -1
  273. package/dist/streams/source.js +312 -149
  274. package/dist/streams/source.js.map +1 -1
  275. package/dist/sync/actions/index.cjs +236 -184
  276. package/dist/sync/actions/index.cjs.map +1 -1
  277. package/dist/sync/actions/index.d.cts +29 -21
  278. package/dist/sync/actions/index.d.cts.map +1 -1
  279. package/dist/sync/actions/index.d.ts +29 -21
  280. package/dist/sync/actions/index.d.ts.map +1 -1
  281. package/dist/sync/actions/index.js +236 -190
  282. package/dist/sync/actions/index.js.map +1 -1
  283. package/dist/sync/b2-sha1-reader.cjs +148 -0
  284. package/dist/sync/b2-sha1-reader.cjs.map +1 -0
  285. package/dist/sync/b2-sha1-reader.d.cts +58 -0
  286. package/dist/sync/b2-sha1-reader.d.cts.map +1 -0
  287. package/dist/sync/b2-sha1-reader.d.ts +58 -0
  288. package/dist/sync/b2-sha1-reader.d.ts.map +1 -0
  289. package/dist/sync/b2-sha1-reader.js +145 -0
  290. package/dist/sync/b2-sha1-reader.js.map +1 -0
  291. package/dist/sync/download-staging.cjs +265 -0
  292. package/dist/sync/download-staging.cjs.map +1 -0
  293. package/dist/sync/download-staging.d.cts +54 -0
  294. package/dist/sync/download-staging.d.cts.map +1 -0
  295. package/dist/sync/download-staging.d.ts +54 -0
  296. package/dist/sync/download-staging.d.ts.map +1 -0
  297. package/dist/sync/download-staging.js +259 -0
  298. package/dist/sync/download-staging.js.map +1 -0
  299. package/dist/sync/filesystem-errors.cjs +32 -0
  300. package/dist/sync/filesystem-errors.cjs.map +1 -0
  301. package/dist/sync/filesystem-errors.d.cts +8 -0
  302. package/dist/sync/filesystem-errors.d.cts.map +1 -0
  303. package/dist/sync/filesystem-errors.d.ts +8 -0
  304. package/dist/sync/filesystem-errors.d.ts.map +1 -0
  305. package/dist/sync/filesystem-errors.js +32 -0
  306. package/dist/sync/filesystem-errors.js.map +1 -0
  307. package/dist/sync/filters.cjs +217 -0
  308. package/dist/sync/filters.cjs.map +1 -0
  309. package/dist/sync/filters.d.cts +51 -0
  310. package/dist/sync/filters.d.cts.map +1 -0
  311. package/dist/sync/filters.d.ts +51 -0
  312. package/dist/sync/filters.d.ts.map +1 -0
  313. package/dist/sync/filters.js +213 -0
  314. package/dist/sync/filters.js.map +1 -0
  315. package/dist/sync/index.cjs +39 -22
  316. package/dist/sync/index.d.cts +14 -9
  317. package/dist/sync/index.d.cts.map +1 -1
  318. package/dist/sync/index.d.ts +14 -9
  319. package/dist/sync/index.d.ts.map +1 -1
  320. package/dist/sync/index.js +9 -20
  321. package/dist/sync/local-file-identity.cjs +61 -0
  322. package/dist/sync/local-file-identity.cjs.map +1 -0
  323. package/dist/sync/local-file-identity.d.cts +37 -0
  324. package/dist/sync/local-file-identity.d.cts.map +1 -0
  325. package/dist/sync/local-file-identity.d.ts +37 -0
  326. package/dist/sync/local-file-identity.d.ts.map +1 -0
  327. package/dist/sync/local-file-identity.js +60 -0
  328. package/dist/sync/local-file-identity.js.map +1 -0
  329. package/dist/sync/local-file-io.cjs +364 -0
  330. package/dist/sync/local-file-io.cjs.map +1 -0
  331. package/dist/sync/local-file-io.d.cts +75 -0
  332. package/dist/sync/local-file-io.d.cts.map +1 -0
  333. package/dist/sync/local-file-io.d.ts +75 -0
  334. package/dist/sync/local-file-io.d.ts.map +1 -0
  335. package/dist/sync/local-file-io.js +360 -0
  336. package/dist/sync/local-file-io.js.map +1 -0
  337. package/dist/sync/local-filesystem-root.cjs +27 -0
  338. package/dist/sync/local-filesystem-root.cjs.map +1 -0
  339. package/dist/sync/local-filesystem-root.d.cts +18 -0
  340. package/dist/sync/local-filesystem-root.d.cts.map +1 -0
  341. package/dist/sync/local-filesystem-root.d.ts +18 -0
  342. package/dist/sync/local-filesystem-root.d.ts.map +1 -0
  343. package/dist/sync/local-filesystem-root.js +26 -0
  344. package/dist/sync/local-filesystem-root.js.map +1 -0
  345. package/dist/sync/local-sha1.cjs +119 -0
  346. package/dist/sync/local-sha1.cjs.map +1 -0
  347. package/dist/sync/local-sha1.d.cts +39 -0
  348. package/dist/sync/local-sha1.d.cts.map +1 -0
  349. package/dist/sync/local-sha1.d.ts +39 -0
  350. package/dist/sync/local-sha1.d.ts.map +1 -0
  351. package/dist/sync/local-sha1.js +117 -0
  352. package/dist/sync/local-sha1.js.map +1 -0
  353. package/dist/sync/pairing.cjs +118 -29
  354. package/dist/sync/pairing.cjs.map +1 -1
  355. package/dist/sync/pairing.d.cts +7 -2
  356. package/dist/sync/pairing.d.cts.map +1 -1
  357. package/dist/sync/pairing.d.ts +7 -2
  358. package/dist/sync/pairing.d.ts.map +1 -1
  359. package/dist/sync/pairing.js +120 -31
  360. package/dist/sync/pairing.js.map +1 -1
  361. package/dist/sync/path-order.cjs +30 -0
  362. package/dist/sync/path-order.cjs.map +1 -0
  363. package/dist/sync/path-order.d.cts +28 -0
  364. package/dist/sync/path-order.d.cts.map +1 -0
  365. package/dist/sync/path-order.d.ts +28 -0
  366. package/dist/sync/path-order.d.ts.map +1 -0
  367. package/dist/sync/path-order.js +29 -0
  368. package/dist/sync/path-order.js.map +1 -0
  369. package/dist/sync/path-safety.cjs +111 -0
  370. package/dist/sync/path-safety.cjs.map +1 -0
  371. package/dist/sync/path-safety.d.cts +78 -0
  372. package/dist/sync/path-safety.d.cts.map +1 -0
  373. package/dist/sync/path-safety.d.ts +78 -0
  374. package/dist/sync/path-safety.d.ts.map +1 -0
  375. package/dist/sync/path-safety.js +105 -0
  376. package/dist/sync/path-safety.js.map +1 -0
  377. package/dist/sync/paths.d.cts +19 -0
  378. package/dist/sync/paths.d.cts.map +1 -0
  379. package/dist/sync/paths.d.ts +19 -0
  380. package/dist/sync/paths.d.ts.map +1 -0
  381. package/dist/sync/policies/compare.cjs +389 -11
  382. package/dist/sync/policies/compare.cjs.map +1 -1
  383. package/dist/sync/policies/compare.d.cts +100 -2
  384. package/dist/sync/policies/compare.d.cts.map +1 -1
  385. package/dist/sync/policies/compare.d.ts +100 -2
  386. package/dist/sync/policies/compare.d.ts.map +1 -1
  387. package/dist/sync/policies/compare.js +387 -13
  388. package/dist/sync/policies/compare.js.map +1 -1
  389. package/dist/sync/policies/index.cjs +76 -65
  390. package/dist/sync/policies/index.cjs.map +1 -1
  391. package/dist/sync/policies/index.d.cts +35 -6
  392. package/dist/sync/policies/index.d.cts.map +1 -1
  393. package/dist/sync/policies/index.d.ts +35 -6
  394. package/dist/sync/policies/index.d.ts.map +1 -1
  395. package/dist/sync/policies/index.js +76 -65
  396. package/dist/sync/policies/index.js.map +1 -1
  397. package/dist/sync/prefix.cjs +96 -0
  398. package/dist/sync/prefix.cjs.map +1 -0
  399. package/dist/sync/prefix.d.cts +55 -0
  400. package/dist/sync/prefix.d.cts.map +1 -0
  401. package/dist/sync/prefix.d.ts +55 -0
  402. package/dist/sync/prefix.d.ts.map +1 -0
  403. package/dist/sync/prefix.js +92 -0
  404. package/dist/sync/prefix.js.map +1 -0
  405. package/dist/sync/regexp-safety.cjs +192 -0
  406. package/dist/sync/regexp-safety.cjs.map +1 -0
  407. package/dist/sync/regexp-safety.d.cts +29 -0
  408. package/dist/sync/regexp-safety.d.cts.map +1 -0
  409. package/dist/sync/regexp-safety.d.ts +29 -0
  410. package/dist/sync/regexp-safety.d.ts.map +1 -0
  411. package/dist/sync/regexp-safety.js +190 -0
  412. package/dist/sync/regexp-safety.js.map +1 -0
  413. package/dist/sync/scan-events.cjs +33 -0
  414. package/dist/sync/scan-events.cjs.map +1 -0
  415. package/dist/sync/scan-events.d.cts +17 -0
  416. package/dist/sync/scan-events.d.cts.map +1 -0
  417. package/dist/sync/scan-events.d.ts +17 -0
  418. package/dist/sync/scan-events.d.ts.map +1 -0
  419. package/dist/sync/scan-events.js +32 -0
  420. package/dist/sync/scan-events.js.map +1 -0
  421. package/dist/sync/scan-limit.cjs +33 -0
  422. package/dist/sync/scan-limit.cjs.map +1 -0
  423. package/dist/sync/scan-limit.d.cts +21 -0
  424. package/dist/sync/scan-limit.d.cts.map +1 -0
  425. package/dist/sync/scan-limit.d.ts +21 -0
  426. package/dist/sync/scan-limit.d.ts.map +1 -0
  427. package/dist/sync/scan-limit.js +31 -0
  428. package/dist/sync/scan-limit.js.map +1 -0
  429. package/dist/sync/scanners/b2.cjs +239 -55
  430. package/dist/sync/scanners/b2.cjs.map +1 -1
  431. package/dist/sync/scanners/b2.d.cts +26 -7
  432. package/dist/sync/scanners/b2.d.cts.map +1 -1
  433. package/dist/sync/scanners/b2.d.ts +26 -7
  434. package/dist/sync/scanners/b2.d.ts.map +1 -1
  435. package/dist/sync/scanners/b2.js +239 -55
  436. package/dist/sync/scanners/b2.js.map +1 -1
  437. package/dist/sync/scanners/local.cjs +215 -56
  438. package/dist/sync/scanners/local.cjs.map +1 -1
  439. package/dist/sync/scanners/local.d.cts +22 -7
  440. package/dist/sync/scanners/local.d.cts.map +1 -1
  441. package/dist/sync/scanners/local.d.ts +22 -7
  442. package/dist/sync/scanners/local.d.ts.map +1 -1
  443. package/dist/sync/scanners/local.js +216 -57
  444. package/dist/sync/scanners/local.js.map +1 -1
  445. package/dist/sync/sha1-metadata.cjs +94 -0
  446. package/dist/sync/sha1-metadata.cjs.map +1 -0
  447. package/dist/sync/sha1-metadata.d.cts +82 -0
  448. package/dist/sync/sha1-metadata.d.cts.map +1 -0
  449. package/dist/sync/sha1-metadata.d.ts +82 -0
  450. package/dist/sync/sha1-metadata.d.ts.map +1 -0
  451. package/dist/sync/sha1-metadata.js +89 -0
  452. package/dist/sync/sha1-metadata.js.map +1 -0
  453. package/dist/sync/sha1-options.cjs +23 -0
  454. package/dist/sync/sha1-options.cjs.map +1 -0
  455. package/dist/sync/sha1-options.d.cts +14 -0
  456. package/dist/sync/sha1-options.d.cts.map +1 -0
  457. package/dist/sync/sha1-options.d.ts +14 -0
  458. package/dist/sync/sha1-options.d.ts.map +1 -0
  459. package/dist/sync/sha1-options.js +21 -0
  460. package/dist/sync/sha1-options.js.map +1 -0
  461. package/dist/sync/synchronizer.cjs +782 -197
  462. package/dist/sync/synchronizer.cjs.map +1 -1
  463. package/dist/sync/synchronizer.d.cts +47 -1
  464. package/dist/sync/synchronizer.d.cts.map +1 -1
  465. package/dist/sync/synchronizer.d.ts +47 -1
  466. package/dist/sync/synchronizer.d.ts.map +1 -1
  467. package/dist/sync/synchronizer.js +779 -173
  468. package/dist/sync/synchronizer.js.map +1 -1
  469. package/dist/sync/types.d.cts +208 -16
  470. package/dist/sync/types.d.cts.map +1 -1
  471. package/dist/sync/types.d.ts +208 -16
  472. package/dist/sync/types.d.ts.map +1 -1
  473. package/dist/types/auth.cjs +76 -56
  474. package/dist/types/auth.cjs.map +1 -1
  475. package/dist/types/auth.d.cts +67 -9
  476. package/dist/types/auth.d.cts.map +1 -1
  477. package/dist/types/auth.d.ts +67 -9
  478. package/dist/types/auth.d.ts.map +1 -1
  479. package/dist/types/auth.js +77 -57
  480. package/dist/types/auth.js.map +1 -1
  481. package/dist/types/bucket.cjs +74 -38
  482. package/dist/types/bucket.cjs.map +1 -1
  483. package/dist/types/bucket.js +75 -41
  484. package/dist/types/bucket.js.map +1 -1
  485. package/dist/types/encryption.cjs +364 -327
  486. package/dist/types/encryption.cjs.map +1 -1
  487. package/dist/types/encryption.d.cts +16 -0
  488. package/dist/types/encryption.d.cts.map +1 -1
  489. package/dist/types/encryption.d.ts +16 -0
  490. package/dist/types/encryption.d.ts.map +1 -1
  491. package/dist/types/encryption.js +364 -310
  492. package/dist/types/encryption.js.map +1 -1
  493. package/dist/types/errors.cjs +47 -0
  494. package/dist/types/errors.cjs.map +1 -0
  495. package/dist/types/errors.d.cts +13 -4
  496. package/dist/types/errors.d.cts.map +1 -1
  497. package/dist/types/errors.d.ts +13 -4
  498. package/dist/types/errors.d.ts.map +1 -1
  499. package/dist/types/errors.js +47 -0
  500. package/dist/types/errors.js.map +1 -0
  501. package/dist/types/file.cjs +36 -19
  502. package/dist/types/file.cjs.map +1 -1
  503. package/dist/types/file.d.cts +2 -2
  504. package/dist/types/file.d.cts.map +1 -1
  505. package/dist/types/file.d.ts +2 -2
  506. package/dist/types/file.d.ts.map +1 -1
  507. package/dist/types/file.js +37 -21
  508. package/dist/types/file.js.map +1 -1
  509. package/dist/types/ids.cjs +53 -9
  510. package/dist/types/ids.cjs.map +1 -1
  511. package/dist/types/ids.js +54 -15
  512. package/dist/types/ids.js.map +1 -1
  513. package/dist/types/index.d.cts +12 -11
  514. package/dist/types/index.d.cts.map +1 -1
  515. package/dist/types/index.d.ts +12 -11
  516. package/dist/types/index.d.ts.map +1 -1
  517. package/dist/types/key.d.cts +44 -8
  518. package/dist/types/key.d.cts.map +1 -1
  519. package/dist/types/key.d.ts +44 -8
  520. package/dist/types/key.d.ts.map +1 -1
  521. package/dist/types/lock.cjs +33 -13
  522. package/dist/types/lock.cjs.map +1 -1
  523. package/dist/types/lock.js +34 -15
  524. package/dist/types/lock.js.map +1 -1
  525. package/dist/types/notifications.cjs +44 -22
  526. package/dist/types/notifications.cjs.map +1 -1
  527. package/dist/types/notifications.js +45 -23
  528. package/dist/types/notifications.js.map +1 -1
  529. package/dist/types/upload.d.cts +39 -5
  530. package/dist/types/upload.d.cts.map +1 -1
  531. package/dist/types/upload.d.ts +39 -5
  532. package/dist/types/upload.d.ts.map +1 -1
  533. package/dist/upload/abort-scope.cjs +91 -0
  534. package/dist/upload/abort-scope.cjs.map +1 -0
  535. package/dist/upload/abort-scope.d.cts +48 -0
  536. package/dist/upload/abort-scope.d.cts.map +1 -0
  537. package/dist/upload/abort-scope.d.ts +48 -0
  538. package/dist/upload/abort-scope.d.ts.map +1 -0
  539. package/dist/upload/abort-scope.js +88 -0
  540. package/dist/upload/abort-scope.js.map +1 -0
  541. package/dist/upload/cancel.cjs +183 -8
  542. package/dist/upload/cancel.cjs.map +1 -1
  543. package/dist/upload/cancel.d.cts +110 -2
  544. package/dist/upload/cancel.d.cts.map +1 -1
  545. package/dist/upload/cancel.d.ts +110 -2
  546. package/dist/upload/cancel.d.ts.map +1 -1
  547. package/dist/upload/cancel.js +178 -9
  548. package/dist/upload/cancel.js.map +1 -1
  549. package/dist/upload/concurrency.cjs +79 -52
  550. package/dist/upload/concurrency.cjs.map +1 -1
  551. package/dist/upload/concurrency.js +79 -53
  552. package/dist/upload/concurrency.js.map +1 -1
  553. package/dist/upload/finish.cjs +59 -0
  554. package/dist/upload/finish.cjs.map +1 -0
  555. package/dist/upload/finish.d.cts +26 -0
  556. package/dist/upload/finish.d.cts.map +1 -0
  557. package/dist/upload/finish.d.ts +26 -0
  558. package/dist/upload/finish.d.ts.map +1 -0
  559. package/dist/upload/finish.js +59 -0
  560. package/dist/upload/finish.js.map +1 -0
  561. package/dist/upload/large.cjs +301 -218
  562. package/dist/upload/large.cjs.map +1 -1
  563. package/dist/upload/large.d.cts +76 -9
  564. package/dist/upload/large.d.cts.map +1 -1
  565. package/dist/upload/large.d.ts +76 -9
  566. package/dist/upload/large.d.ts.map +1 -1
  567. package/dist/upload/large.js +301 -218
  568. package/dist/upload/large.js.map +1 -1
  569. package/dist/upload/options.cjs +28 -0
  570. package/dist/upload/options.cjs.map +1 -0
  571. package/dist/upload/options.d.cts +104 -0
  572. package/dist/upload/options.d.cts.map +1 -0
  573. package/dist/upload/options.d.ts +104 -0
  574. package/dist/upload/options.d.ts.map +1 -0
  575. package/dist/upload/options.js +27 -0
  576. package/dist/upload/options.js.map +1 -0
  577. package/dist/upload/resume.cjs +333 -33
  578. package/dist/upload/resume.cjs.map +1 -1
  579. package/dist/upload/resume.d.cts +82 -12
  580. package/dist/upload/resume.d.cts.map +1 -1
  581. package/dist/upload/resume.d.ts +82 -12
  582. package/dist/upload/resume.d.ts.map +1 -1
  583. package/dist/upload/resume.js +330 -33
  584. package/dist/upload/resume.js.map +1 -1
  585. package/dist/upload/retry.cjs +186 -0
  586. package/dist/upload/retry.cjs.map +1 -0
  587. package/dist/upload/retry.d.cts +127 -0
  588. package/dist/upload/retry.d.cts.map +1 -0
  589. package/dist/upload/retry.d.ts +127 -0
  590. package/dist/upload/retry.d.ts.map +1 -0
  591. package/dist/upload/retry.js +182 -0
  592. package/dist/upload/retry.js.map +1 -0
  593. package/dist/upload/single.cjs +68 -44
  594. package/dist/upload/single.cjs.map +1 -1
  595. package/dist/upload/single.d.cts +2 -1
  596. package/dist/upload/single.d.cts.map +1 -1
  597. package/dist/upload/single.d.ts +2 -1
  598. package/dist/upload/single.d.ts.map +1 -1
  599. package/dist/upload/single.js +68 -44
  600. package/dist/upload/single.js.map +1 -1
  601. package/dist/upload/stream.cjs +317 -193
  602. package/dist/upload/stream.cjs.map +1 -1
  603. package/dist/upload/stream.d.cts +3 -1
  604. package/dist/upload/stream.d.cts.map +1 -1
  605. package/dist/upload/stream.d.ts +3 -1
  606. package/dist/upload/stream.d.ts.map +1 -1
  607. package/dist/upload/stream.js +316 -192
  608. package/dist/upload/stream.js.map +1 -1
  609. package/dist/util/best-effort.cjs +42 -8
  610. package/dist/util/best-effort.cjs.map +1 -1
  611. package/dist/util/best-effort.d.cts +2 -1
  612. package/dist/util/best-effort.d.cts.map +1 -1
  613. package/dist/util/best-effort.d.ts +2 -1
  614. package/dist/util/best-effort.d.ts.map +1 -1
  615. package/dist/util/best-effort.js +43 -9
  616. package/dist/util/best-effort.js.map +1 -1
  617. package/dist/util/bytes.cjs +23 -0
  618. package/dist/util/bytes.cjs.map +1 -0
  619. package/dist/util/bytes.d.cts +15 -0
  620. package/dist/util/bytes.d.cts.map +1 -0
  621. package/dist/util/bytes.d.ts +15 -0
  622. package/dist/util/bytes.d.ts.map +1 -0
  623. package/dist/util/bytes.js +23 -0
  624. package/dist/util/bytes.js.map +1 -0
  625. package/dist/util/crypto.cjs +68 -0
  626. package/dist/util/crypto.cjs.map +1 -0
  627. package/dist/util/crypto.d.cts +26 -0
  628. package/dist/util/crypto.d.cts.map +1 -0
  629. package/dist/util/crypto.d.ts +26 -0
  630. package/dist/util/crypto.d.ts.map +1 -0
  631. package/dist/util/crypto.js +66 -0
  632. package/dist/util/crypto.js.map +1 -0
  633. package/dist/util/defaults.cjs +25 -9
  634. package/dist/util/defaults.cjs.map +1 -1
  635. package/dist/util/defaults.js +26 -11
  636. package/dist/util/defaults.js.map +1 -1
  637. package/dist/util/error-reason.cjs +41 -0
  638. package/dist/util/error-reason.cjs.map +1 -0
  639. package/dist/util/error-reason.d.cts +9 -0
  640. package/dist/util/error-reason.d.cts.map +1 -0
  641. package/dist/util/error-reason.d.ts +9 -0
  642. package/dist/util/error-reason.d.ts.map +1 -0
  643. package/dist/util/error-reason.js +41 -0
  644. package/dist/util/error-reason.js.map +1 -0
  645. package/dist/util/http.cjs +23 -0
  646. package/dist/util/http.cjs.map +1 -0
  647. package/dist/util/http.d.cts +13 -0
  648. package/dist/util/http.d.cts.map +1 -0
  649. package/dist/util/http.d.ts +13 -0
  650. package/dist/util/http.d.ts.map +1 -0
  651. package/dist/util/http.js +23 -0
  652. package/dist/util/http.js.map +1 -0
  653. package/dist/util/normalize.cjs +66 -7
  654. package/dist/util/normalize.cjs.map +1 -1
  655. package/dist/util/normalize.js +67 -10
  656. package/dist/util/normalize.js.map +1 -1
  657. package/dist/util/paginator.cjs +63 -14
  658. package/dist/util/paginator.cjs.map +1 -1
  659. package/dist/util/paginator.js +64 -16
  660. package/dist/util/paginator.js.map +1 -1
  661. package/dist/util/plan-ranges.cjs +47 -22
  662. package/dist/util/plan-ranges.cjs.map +1 -1
  663. package/dist/util/plan-ranges.js +48 -24
  664. package/dist/util/plan-ranges.js.map +1 -1
  665. package/dist/util/sha1.cjs +27 -0
  666. package/dist/util/sha1.cjs.map +1 -0
  667. package/dist/util/sha1.d.cts +17 -0
  668. package/dist/util/sha1.d.cts.map +1 -0
  669. package/dist/util/sha1.d.ts +17 -0
  670. package/dist/util/sha1.d.ts.map +1 -0
  671. package/dist/util/sha1.js +26 -0
  672. package/dist/util/sha1.js.map +1 -0
  673. package/dist/util/text-codec.cjs +29 -5
  674. package/dist/util/text-codec.cjs.map +1 -1
  675. package/dist/util/text-codec.js +30 -7
  676. package/dist/util/text-codec.js.map +1 -1
  677. package/dist/util/to-error.cjs +18 -4
  678. package/dist/util/to-error.cjs.map +1 -1
  679. package/dist/util/to-error.js +19 -5
  680. package/dist/util/to-error.js.map +1 -1
  681. package/dist/version.cjs +17 -5
  682. package/dist/version.cjs.map +1 -1
  683. package/dist/version.d.cts +3 -3
  684. package/dist/version.d.ts +3 -3
  685. package/dist/version.js +19 -6
  686. package/dist/version.js.map +1 -1
  687. package/package.json +19 -16
  688. package/dist/auth/index.cjs.map +0 -1
  689. package/dist/auth/index.js.map +0 -1
  690. package/dist/index.cjs.map +0 -1
  691. package/dist/index.js.map +0 -1
  692. package/dist/notifications/index.cjs.map +0 -1
  693. package/dist/notifications/index.js.map +0 -1
  694. package/dist/package.json.cjs +0 -9
  695. package/dist/package.json.cjs.map +0 -1
  696. package/dist/package.json.js +0 -9
  697. package/dist/package.json.js.map +0 -1
  698. package/dist/streams/index.cjs.map +0 -1
  699. package/dist/streams/index.js.map +0 -1
  700. package/dist/sync/index.cjs.map +0 -1
  701. package/dist/sync/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"parallel.cjs","sources":["../../src/download/parallel.ts"],"sourcesContent":["import type { AccountInfo } from '../auth/account-info.ts'\nimport { DEFAULT_RETRY_OPTIONS, computeBackoff, sleep } from '../http/retry.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport { collectStream } from '../streams/collect.ts'\nimport type { FileId } from '../types/ids.ts'\nimport { DEFAULT_TRANSFER_CONCURRENCY } from '../util/defaults.ts'\nimport { byteRangeHeader, planRanges } from '../util/plan-ranges.ts'\n\n/** Options for downloading a file using concurrent byte-range requests. */\nexport interface ParallelDownloadOptions {\n /** ID of the file to download. */\n readonly fileId: FileId\n /** Total file size in bytes (must be known in advance). */\n readonly totalSize: number\n /** Size of each ranged chunk in bytes. Defaults to 10 MB. */\n readonly rangeSize?: number\n /** Maximum number of chunks fetched in parallel. Defaults to 4. */\n readonly concurrency?: number\n /** Maximum retry attempts per range on transient failures. Defaults to 5. */\n readonly maxRetries?: number\n /** Signal to abort the download. */\n readonly signal?: AbortSignal\n}\n\n/**\n * Creates a readable stream that downloads a file using parallel byte-range requests.\n *\n * The file is split into fixed-size ranges fetched in a **sliding\n * window** keyed off the consumer's read pace. The window size is\n * `concurrency * 2`: up to `concurrency` ranges are in flight at once,\n * plus up to `concurrency` completed-but-not-yet-emitted ranges buffered\n * ahead of the read head. New fetches kick off only when the consumer\n * reads a chunk, so a slow downstream pipe (e.g. a saturated network\n * sink or a `pipeTo` consumer that drains slowly) backpressures into\n * the SDK and bounds peak memory to `(concurrency * 2) * rangeSize`.\n *\n * The previous eager implementation scheduled every range into\n * `Promise.all` up front; a slow head-of-line range could hold the\n * entire file in memory while later ranges finished and waited.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param options - Parallel download parameters (file ID, size, concurrency).\n *\n * @returns A `ReadableStream` that yields file bytes in order.\n */\nexport function createParallelDownloadStream(\n raw: RawClient,\n accountInfo: AccountInfo,\n options: ParallelDownloadOptions,\n): ReadableStream<Uint8Array> {\n const rangeSize = options.rangeSize ?? 10 * 1024 * 1024\n const concurrency = options.concurrency ?? DEFAULT_TRANSFER_CONCURRENCY\n const totalSize = options.totalSize\n // `maxRetries` is part of `DEFAULT_RETRY_OPTIONS`; only override when\n // the caller supplied an explicit value so the shared default stays\n // the single source of truth.\n const retryOptions = {\n ...DEFAULT_RETRY_OPTIONS,\n ...(options.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),\n }\n const abort = options.signal\n\n const ranges = planRanges(totalSize, rangeSize)\n\n // `inflight` holds currently-fetching range promises; `buffer` holds\n // completed ranges waiting for their slot in the emit order. Total\n // scheduled-but-not-emitted = inflight.size + buffer.size is capped\n // at `windowSize`, so peak memory is bounded regardless of any\n // single range's latency.\n const windowSize = concurrency * 2\n const inflight = new Map<number, Promise<void>>()\n const buffer = new Map<number, Uint8Array>()\n let nextToSchedule = 0\n let nextToEmit = 0\n let firstError: unknown = null\n\n function scheduleNext(): void {\n while (\n firstError === null &&\n // Honour abort here so a completed range that triggers a top-up\n // doesn't queue one final fetch after the caller aborted. Without\n // this gate, one extra range request fires post-abort before the\n // `pull()` loop notices.\n abort?.aborted !== true &&\n nextToSchedule < ranges.length &&\n inflight.size + buffer.size < windowSize\n ) {\n const range = ranges[nextToSchedule]\n if (range === undefined) break\n const idx = nextToSchedule\n nextToSchedule++\n const task = (async () => {\n try {\n const data = await fetchRangeWithRetry(\n raw,\n accountInfo,\n options.fileId,\n range.start,\n range.end,\n retryOptions,\n abort,\n )\n buffer.set(idx, data)\n } catch (err) {\n if (firstError === null) firstError = err\n } finally {\n inflight.delete(idx)\n }\n })()\n inflight.set(idx, task)\n }\n }\n\n return new ReadableStream<Uint8Array>({\n start(controller) {\n // Pre-warm: kick off the initial window so the first `pull` has\n // something to await. An already-aborted signal surfaces as a\n // stream error rather than a synchronous throw, matching the\n // contract callers had with the previous eager implementation.\n try {\n abort?.throwIfAborted()\n scheduleNext()\n } catch (err) {\n controller.error(err)\n }\n },\n async pull(controller) {\n try {\n // Wait until the next in-order chunk is available.\n while (!buffer.has(nextToEmit)) {\n abort?.throwIfAborted()\n if (firstError !== null) throw firstError\n /* v8 ignore start -- defensive close: every code path that\n populates `nextToEmit < ranges.length` also schedules into\n `inflight`. The pre-warm in `start()` plus `scheduleNext()`\n after each enqueue guarantee that whenever there are\n pending ranges, at least one is in flight. This guard\n exists only so a future refactor can't accidentally stall\n the stream by leaving both maps empty mid-stream. */\n if (inflight.size === 0) {\n controller.close()\n return\n }\n /* v8 ignore stop */\n await Promise.race(inflight.values())\n }\n\n const data = buffer.get(nextToEmit)\n if (data !== undefined) {\n buffer.delete(nextToEmit)\n nextToEmit++\n controller.enqueue(data)\n }\n\n // Top up the window now that we freed a slot by emitting.\n scheduleNext()\n\n if (\n nextToEmit >= ranges.length &&\n buffer.size === 0 &&\n inflight.size === 0 &&\n firstError === null\n ) {\n controller.close()\n }\n } catch (err) {\n controller.error(err)\n }\n },\n cancel() {\n // Abort propagation handles in-flight requests; just drop buffered\n // data so it can be GC'd promptly.\n buffer.clear()\n },\n })\n}\n\n/**\n * Fetches a single byte range with bounded retry on transient failures.\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param fileId - ID of the file being downloaded.\n * @param start - Inclusive byte offset where the range begins.\n * @param end - Inclusive byte offset where the range ends.\n * @param retryOptions - Retry settings controlling attempts and backoff.\n * @param signal - Optional abort signal that cancels the range and any pending retry.\n *\n * @returns The range's bytes, or throws after exhausting all retry attempts.\n */\nasync function fetchRangeWithRetry(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: FileId,\n start: number,\n end: number,\n retryOptions: typeof DEFAULT_RETRY_OPTIONS,\n signal: AbortSignal | undefined,\n): Promise<Uint8Array> {\n let lastError: unknown\n for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = computeBackoff(attempt - 1, retryOptions)\n await sleep(delay, signal)\n }\n try {\n signal?.throwIfAborted()\n const resp = await raw.downloadFileById(\n accountInfo.getDownloadUrl(),\n accountInfo.getAuthToken(),\n fileId,\n {\n range: byteRangeHeader(start, end),\n ...(signal !== undefined ? { signal } : {}),\n },\n )\n if (!resp.body) throw new Error('Download chunk has no body')\n return await collectStream(resp.body)\n } catch (err) {\n lastError = err\n // Honour AbortSignal cancellation: don't retry past an abort.\n if (signal?.aborted) throw err\n if (err instanceof DOMException && err.name === 'AbortError') throw err\n }\n }\n throw lastError instanceof Error ? lastError : new Error('Range download failed after retries')\n}\n"],"names":["DEFAULT_TRANSFER_CONCURRENCY","DEFAULT_RETRY_OPTIONS","planRanges","computeBackoff","sleep","byteRangeHeader","collectStream"],"mappings":"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"}
1
+ {"version":3,"file":"parallel.cjs","names":[],"sources":["../../src/download/parallel.ts"],"sourcesContent":["import type { AccountInfo } from '../auth/account-info.ts'\nimport { B2Error, classifyError, NetworkError } from '../errors/index.ts'\nimport { computeBackoff, DEFAULT_RETRY_OPTIONS, type RetryOptions, sleep } from '../http/retry.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport { collectStream } from '../streams/collect.ts'\nimport { IncrementalSha1 } from '../streams/hash.ts'\nimport type { B2ErrorResponse } from '../types/errors.ts'\nimport type { FileId } from '../types/ids.ts'\nimport { DEFAULT_TRANSFER_CONCURRENCY } from '../util/defaults.ts'\nimport { normalizeSha1 } from '../util/normalize.ts'\nimport { byteRangeHeader, planRanges } from '../util/plan-ranges.ts'\nimport { normalizeVerifiableSha1 } from '../util/sha1.ts'\nimport { utf8Decoder } from '../util/text-codec.ts'\nimport { assertDownloadSha1, assertDownloadSha1HeaderAgreement } from './checksum.ts'\n\n/** Options for downloading a file using concurrent byte-range requests. */\nexport interface ParallelDownloadOptions {\n /** ID of the file to download. */\n readonly fileId: FileId\n /** Total file size in bytes (must be known in advance). */\n readonly totalSize: number\n /** Size of each ranged chunk in bytes. Defaults to 10 MB. */\n readonly rangeSize?: number\n /** Maximum number of chunks fetched in parallel. Defaults to 4. */\n readonly concurrency?: number\n /**\n * Extra retry attempts per range on transient failures. Defaults to 0 because\n * `B2Client` already applies `RetryTransport`; set this only when supplying a\n * raw client that does not already retry transport failures.\n */\n readonly maxRetries?: number\n /** Signal to abort the download. */\n readonly signal?: AbortSignal\n}\n\ninterface RangeDownloadResult {\n readonly data: Uint8Array\n readonly contentSha1: string | null\n}\n\n/**\n * Creates a readable stream that downloads a file using parallel byte-range requests.\n *\n * The file is split into fixed-size ranges fetched in a **sliding\n * window** keyed off the consumer's read pace. The window size is\n * `concurrency * 2`: up to `concurrency` ranges are in flight at once,\n * plus up to `concurrency` completed-but-not-yet-emitted ranges buffered\n * ahead of the read head. New fetches kick off only when the consumer\n * reads a chunk, so a slow downstream pipe (e.g. a saturated network\n * sink or a `pipeTo` consumer that drains slowly) backpressures into\n * the SDK and bounds peak memory to `(concurrency * 2) * rangeSize`.\n *\n * The previous eager implementation scheduled every range into\n * `Promise.all` up front; a slow head-of-line range could hold the\n * entire file in memory while later ranges finished and waited.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param options - Parallel download parameters (file ID, size, concurrency).\n *\n * @returns A `ReadableStream` that yields file bytes in order.\n */\nexport function createParallelDownloadStream(\n raw: RawClient,\n accountInfo: AccountInfo,\n options: ParallelDownloadOptions,\n): ReadableStream<Uint8Array> {\n const rangeSize = options.rangeSize ?? 10 * 1024 * 1024\n const concurrency = options.concurrency ?? DEFAULT_TRANSFER_CONCURRENCY\n const totalSize = options.totalSize\n // The high-level B2Client already wraps the raw transport in RetryTransport.\n // Keep the parallel-download outer retry disabled by default so each range\n // has one retry budget unless callers explicitly opt into an extra raw-client\n // retry layer.\n const retryOptions = {\n ...DEFAULT_RETRY_OPTIONS,\n maxRetries: options.maxRetries ?? 0,\n }\n const abort = options.signal\n\n const ranges = planRanges(totalSize, rangeSize)\n\n // `inflight` holds currently-fetching range promises; `buffer` holds\n // completed ranges waiting for their slot in the emit order. Total\n // scheduled-but-not-emitted = inflight.size + buffer.size is capped\n // at `windowSize`, so peak memory is bounded regardless of any\n // single range's latency.\n const windowSize = concurrency * 2\n const inflight = new Map<number, Promise<void>>()\n const buffer = new Map<number, RangeDownloadResult>()\n let assembledSha1: IncrementalSha1 | null = null\n let nextToSchedule = 0\n let nextToEmit = 0\n let expectedSha1: string | null | undefined\n let firstError: unknown = null\n\n function scheduleNext(): void {\n while (\n firstError === null &&\n // Honour abort here so a completed range that triggers a top-up\n // doesn't queue one final fetch after the caller aborted. Without\n // this gate, one extra range request fires post-abort before the\n // `pull()` loop notices.\n abort?.aborted !== true &&\n nextToSchedule < ranges.length &&\n inflight.size + buffer.size < windowSize\n ) {\n const range = ranges[nextToSchedule]\n if (range === undefined) break\n const idx = nextToSchedule\n nextToSchedule++\n const task = (async () => {\n try {\n const result = await fetchRangeWithRetry(\n raw,\n accountInfo,\n options.fileId,\n range.start,\n range.end,\n totalSize,\n retryOptions,\n abort,\n )\n buffer.set(idx, result)\n } catch (err) {\n if (firstError === null) firstError = err\n } finally {\n inflight.delete(idx)\n }\n })()\n inflight.set(idx, task)\n }\n }\n\n return new ReadableStream<Uint8Array>({\n start(controller) {\n // Pre-warm: kick off the initial window so the first `pull` has\n // something to await. An already-aborted signal surfaces as a\n // stream error rather than a synchronous throw, matching the\n // contract callers had with the previous eager implementation.\n try {\n abort?.throwIfAborted()\n scheduleNext()\n } catch (err) {\n controller.error(err)\n }\n },\n async pull(controller) {\n try {\n // Wait until the next in-order chunk is available.\n while (!buffer.has(nextToEmit)) {\n abort?.throwIfAborted()\n if (firstError !== null) throw firstError\n // Zero-byte downloads have no planned ranges and close here. For\n // non-empty downloads, this guard prevents a future refactor from\n // stalling the stream if it leaves both maps empty mid-stream.\n if (inflight.size === 0) {\n controller.close()\n return\n }\n await Promise.race(inflight.values())\n }\n\n const result = buffer.get(nextToEmit)\n if (result !== undefined) {\n buffer.delete(nextToEmit)\n nextToEmit++\n const rangeSha1 = normalizeVerifiableSha1(result.contentSha1)\n if (expectedSha1 === undefined) {\n expectedSha1 = rangeSha1\n } else {\n // B2 range responses should agree on the same whole-file SHA-1;\n // any value or presence change makes verification untrustworthy.\n assertDownloadSha1HeaderAgreement(expectedSha1, rangeSha1)\n }\n if (expectedSha1 !== null) {\n assembledSha1 ??= new IncrementalSha1()\n await assembledSha1.update(result.data)\n }\n controller.enqueue(result.data)\n }\n\n // Top up the window now that we freed a slot by emitting.\n scheduleNext()\n\n if (\n nextToEmit >= ranges.length &&\n buffer.size === 0 &&\n inflight.size === 0 &&\n firstError === null\n ) {\n if (expectedSha1 !== undefined && expectedSha1 !== null && assembledSha1 !== null) {\n assertDownloadSha1(expectedSha1, await assembledSha1.digest())\n }\n controller.close()\n }\n } catch (err) {\n controller.error(err)\n }\n },\n cancel() {\n // Abort propagation handles in-flight requests; just drop buffered\n // data so it can be GC'd promptly.\n buffer.clear()\n },\n })\n}\n\n/**\n * Fetches a single byte range with bounded retry on transient failures.\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param fileId - ID of the file being downloaded.\n * @param start - Inclusive byte offset where the range begins.\n * @param end - Inclusive byte offset where the range ends.\n * @param totalSize - Expected complete file size.\n * @param retryOptions - Retry settings controlling attempts and backoff.\n * @param signal - Optional abort signal that cancels the range and any pending retry.\n *\n * @returns The range's bytes, or throws after exhausting all retry attempts.\n */\nasync function fetchRangeWithRetry(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: FileId,\n start: number,\n end: number,\n totalSize: number,\n retryOptions: RetryOptions,\n signal: AbortSignal | undefined,\n): Promise<RangeDownloadResult> {\n let lastError: unknown\n for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {\n if (attempt > 0) {\n const retryAfter =\n lastError instanceof B2Error && lastError.retryAfter !== undefined\n ? lastError.retryAfter\n : undefined\n const delay = computeBackoff(attempt - 1, retryOptions, retryAfter)\n await sleep(delay, signal)\n }\n try {\n signal?.throwIfAborted()\n const resp = await raw.downloadFileById(\n accountInfo.getDownloadUrl(),\n accountInfo.getAuthToken(),\n fileId,\n {\n range: byteRangeHeader(start, end),\n ...(signal !== undefined ? { signal } : {}),\n },\n )\n if (resp.status < 200 || resp.status >= 300) {\n throw await classifyDownloadResponseError(resp)\n }\n if (!resp.body) throw new Error('Download chunk has no body')\n const data = await collectStream(resp.body)\n validateRangeResponse(resp, start, end, totalSize, data.byteLength)\n return {\n data,\n contentSha1: normalizeSha1(resp.headers.get('X-Bz-Content-Sha1')),\n }\n } catch (err) {\n lastError = err\n // Honour AbortSignal cancellation: don't retry past an abort.\n if (signal?.aborted) throw err\n if (err instanceof DOMException && err.name === 'AbortError') throw err\n if (!isRetryableRangeError(err) || attempt === retryOptions.maxRetries) {\n throw err\n }\n }\n }\n throw lastError instanceof Error ? lastError : new Error('Range download failed after retries')\n}\n\nclass RangeValidationError extends Error {\n readonly retryable = false\n\n constructor(message: string) {\n super(message)\n this.name = 'RangeValidationError'\n }\n}\n\nfunction validateRangeResponse(\n response: { headers: Headers; status: number },\n start: number,\n end: number,\n totalSize: number,\n byteLength: number,\n): void {\n const expectedLength = end - start + 1\n if (response.status !== 206) {\n throw new RangeValidationError(\n `Expected HTTP 206 Partial Content for range ${start}-${end}, got ${response.status}`,\n )\n }\n\n const contentRange = response.headers.get('Content-Range')\n if (contentRange === null) {\n throw new RangeValidationError(`Missing Content-Range for range ${start}-${end}`)\n }\n\n const match = contentRange.match(/^bytes (\\d+)-(\\d+)\\/(\\d+|\\*)$/)\n if (match === null) {\n throw new RangeValidationError(\n `Invalid Content-Range for range ${start}-${end}: ${contentRange}`,\n )\n }\n\n const actualStart = Number.parseInt(match[1] ?? '', 10)\n const actualEnd = Number.parseInt(match[2] ?? '', 10)\n const actualTotal = match[3] ?? ''\n if (actualStart !== start || actualEnd !== end) {\n throw new RangeValidationError(\n `Content-Range ${contentRange} does not match requested range ${start}-${end}`,\n )\n }\n\n if (actualTotal === '*') {\n throw new RangeValidationError(`Content-Range ${contentRange} does not include total size`)\n }\n\n const parsedTotal = Number.parseInt(actualTotal, 10)\n if (parsedTotal !== totalSize) {\n throw new RangeValidationError(\n `Content-Range ${contentRange} does not match expected total size ${totalSize}`,\n )\n }\n\n if (byteLength !== expectedLength) {\n throw new RangeValidationError(\n `Expected ${expectedLength} bytes for range ${start}-${end}, got ${byteLength}`,\n )\n }\n}\n\nasync function classifyDownloadResponseError(response: {\n headers: Headers\n status: number\n body: ReadableStream<Uint8Array> | null\n}): Promise<B2Error> {\n let errorBody: B2ErrorResponse = {\n status: response.status,\n code: 'internal_error',\n message: `HTTP ${response.status}`,\n }\n\n if (response.body !== null) {\n const bytes = await collectStream(response.body)\n try {\n const parsed = JSON.parse(utf8Decoder.decode(bytes)) as Partial<B2ErrorResponse>\n errorBody = {\n status: response.status,\n code: parsed.code ?? 'internal_error',\n message: parsed.message ?? `HTTP ${response.status}`,\n }\n } catch {\n // Keep the synthetic errorBody above when the response body is not JSON.\n }\n }\n\n const retryAfterHeader = response.headers.get('Retry-After')\n const retryAfter = retryAfterHeader !== null ? Number.parseInt(retryAfterHeader, 10) : undefined\n const requestId = response.headers.get('X-Bz-Request-Id') ?? undefined\n\n return classifyError(errorBody, {\n ...(retryAfter !== undefined ? { retryAfter } : {}),\n ...(requestId !== undefined ? { requestId } : {}),\n })\n}\n\nfunction isRetryableRangeError(err: unknown): boolean {\n if (err instanceof B2Error || err instanceof NetworkError) return err.retryable\n if (hasRetryableFlag(err)) return err.retryable\n // Bare FetchTransport surfaces network failures as TypeError. Validation\n // failures use RangeValidationError above, so they never land here.\n return err instanceof TypeError\n}\n\nfunction hasRetryableFlag(err: unknown): err is { retryable: boolean } {\n return (\n typeof err === 'object' &&\n err !== null &&\n 'retryable' in err &&\n typeof (err as { retryable?: unknown }).retryable === 'boolean'\n )\n}\n"],"mappings":"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"}
@@ -11,7 +11,11 @@ export interface ParallelDownloadOptions {
11
11
  readonly rangeSize?: number;
12
12
  /** Maximum number of chunks fetched in parallel. Defaults to 4. */
13
13
  readonly concurrency?: number;
14
- /** Maximum retry attempts per range on transient failures. Defaults to 5. */
14
+ /**
15
+ * Extra retry attempts per range on transient failures. Defaults to 0 because
16
+ * `B2Client` already applies `RetryTransport`; set this only when supplying a
17
+ * raw client that does not already retry transport failures.
18
+ */
15
19
  readonly maxRetries?: number;
16
20
  /** Signal to abort the download. */
17
21
  readonly signal?: AbortSignal;
@@ -1 +1 @@
1
- {"version":3,"file":"parallel.d.ts","sourceRoot":"","sources":["../../src/download/parallel.js"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAI7C,2EAA2E;AAC3E,MAAM,WAAW,uBAAuB;IACtC,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,SAAS,EACd,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,uBAAuB,GAC/B,cAAc,CAAC,UAAU,CAAC,CA8H5B"}
1
+ {"version":3,"file":"parallel.d.ts","sourceRoot":"","sources":["../../src/download/parallel.js"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAG1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAIhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAQ7C,2EAA2E;AAC3E,MAAM,WAAW,uBAAuB;IACtC,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAOD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,SAAS,EACd,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,uBAAuB,GAC/B,cAAc,CAAC,UAAU,CAAC,CA4I5B"}
@@ -11,7 +11,11 @@ export interface ParallelDownloadOptions {
11
11
  readonly rangeSize?: number;
12
12
  /** Maximum number of chunks fetched in parallel. Defaults to 4. */
13
13
  readonly concurrency?: number;
14
- /** Maximum retry attempts per range on transient failures. Defaults to 5. */
14
+ /**
15
+ * Extra retry attempts per range on transient failures. Defaults to 0 because
16
+ * `B2Client` already applies `RetryTransport`; set this only when supplying a
17
+ * raw client that does not already retry transport failures.
18
+ */
15
19
  readonly maxRetries?: number;
16
20
  /** Signal to abort the download. */
17
21
  readonly signal?: AbortSignal;
@@ -1 +1 @@
1
- {"version":3,"file":"parallel.d.ts","sourceRoot":"","sources":["../../src/download/parallel.js"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAE1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAI7C,2EAA2E;AAC3E,MAAM,WAAW,uBAAuB;IACtC,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,SAAS,EACd,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,uBAAuB,GAC/B,cAAc,CAAC,UAAU,CAAC,CA8H5B"}
1
+ {"version":3,"file":"parallel.d.ts","sourceRoot":"","sources":["../../src/download/parallel.js"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAG1D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAIhD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAQ7C,2EAA2E;AAC3E,MAAM,WAAW,uBAAuB;IACtC,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,6DAA6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B;AAOD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,SAAS,EACd,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,uBAAuB,GAC/B,cAAc,CAAC,UAAU,CAAC,CA4I5B"}
@@ -1,122 +1,218 @@
1
+ import { B2Error, NetworkError, classifyError } from "../errors/index.js";
2
+ import { byteRangeHeader, planRanges } from "../util/plan-ranges.js";
3
+ import { utf8Decoder } from "../util/text-codec.js";
4
+ import { normalizeSha1 } from "../util/normalize.js";
5
+ import { IncrementalSha1 } from "../streams/hash.js";
6
+ import { normalizeVerifiableSha1 } from "../util/sha1.js";
7
+ import { assertDownloadSha1, assertDownloadSha1HeaderAgreement } from "./checksum.js";
1
8
  import { DEFAULT_RETRY_OPTIONS, computeBackoff, sleep } from "../http/retry.js";
2
9
  import { collectStream } from "../streams/collect.js";
3
- import { DEFAULT_TRANSFER_CONCURRENCY } from "../util/defaults.js";
4
- import { planRanges, byteRangeHeader } from "../util/plan-ranges.js";
10
+ //#region src/download/parallel.ts
11
+ /**
12
+ * Creates a readable stream that downloads a file using parallel byte-range requests.
13
+ *
14
+ * The file is split into fixed-size ranges fetched in a **sliding
15
+ * window** keyed off the consumer's read pace. The window size is
16
+ * `concurrency * 2`: up to `concurrency` ranges are in flight at once,
17
+ * plus up to `concurrency` completed-but-not-yet-emitted ranges buffered
18
+ * ahead of the read head. New fetches kick off only when the consumer
19
+ * reads a chunk, so a slow downstream pipe (e.g. a saturated network
20
+ * sink or a `pipeTo` consumer that drains slowly) backpressures into
21
+ * the SDK and bounds peak memory to `(concurrency * 2) * rangeSize`.
22
+ *
23
+ * The previous eager implementation scheduled every range into
24
+ * `Promise.all` up front; a slow head-of-line range could hold the
25
+ * entire file in memory while later ranges finished and waited.
26
+ *
27
+ * @param raw - Low-level B2 API client.
28
+ * @param accountInfo - Authorized account state.
29
+ * @param options - Parallel download parameters (file ID, size, concurrency).
30
+ *
31
+ * @returns A `ReadableStream` that yields file bytes in order.
32
+ */
5
33
  function createParallelDownloadStream(raw, accountInfo, options) {
6
- const rangeSize = options.rangeSize ?? 10 * 1024 * 1024;
7
- const concurrency = options.concurrency ?? DEFAULT_TRANSFER_CONCURRENCY;
8
- const totalSize = options.totalSize;
9
- const retryOptions = {
10
- ...DEFAULT_RETRY_OPTIONS,
11
- ...options.maxRetries !== void 0 ? { maxRetries: options.maxRetries } : {}
12
- };
13
- const abort = options.signal;
14
- const ranges = planRanges(totalSize, rangeSize);
15
- const windowSize = concurrency * 2;
16
- const inflight = /* @__PURE__ */ new Map();
17
- const buffer = /* @__PURE__ */ new Map();
18
- let nextToSchedule = 0;
19
- let nextToEmit = 0;
20
- let firstError = null;
21
- function scheduleNext() {
22
- while (firstError === null && // Honour abort here so a completed range that triggers a top-up
23
- // doesn't queue one final fetch after the caller aborted. Without
24
- // this gate, one extra range request fires post-abort before the
25
- // `pull()` loop notices.
26
- abort?.aborted !== true && nextToSchedule < ranges.length && inflight.size + buffer.size < windowSize) {
27
- const range = ranges[nextToSchedule];
28
- if (range === void 0) break;
29
- const idx = nextToSchedule;
30
- nextToSchedule++;
31
- const task = (async () => {
32
- try {
33
- const data = await fetchRangeWithRetry(
34
- raw,
35
- accountInfo,
36
- options.fileId,
37
- range.start,
38
- range.end,
39
- retryOptions,
40
- abort
41
- );
42
- buffer.set(idx, data);
43
- } catch (err) {
44
- if (firstError === null) firstError = err;
45
- } finally {
46
- inflight.delete(idx);
47
- }
48
- })();
49
- inflight.set(idx, task);
50
- }
51
- }
52
- return new ReadableStream({
53
- start(controller) {
54
- try {
55
- abort?.throwIfAborted();
56
- scheduleNext();
57
- } catch (err) {
58
- controller.error(err);
59
- }
60
- },
61
- async pull(controller) {
62
- try {
63
- while (!buffer.has(nextToEmit)) {
64
- abort?.throwIfAborted();
65
- if (firstError !== null) throw firstError;
66
- if (inflight.size === 0) {
67
- controller.close();
68
- return;
69
- }
70
- await Promise.race(inflight.values());
71
- }
72
- const data = buffer.get(nextToEmit);
73
- if (data !== void 0) {
74
- buffer.delete(nextToEmit);
75
- nextToEmit++;
76
- controller.enqueue(data);
77
- }
78
- scheduleNext();
79
- if (nextToEmit >= ranges.length && buffer.size === 0 && inflight.size === 0 && firstError === null) {
80
- controller.close();
81
- }
82
- } catch (err) {
83
- controller.error(err);
84
- }
85
- },
86
- cancel() {
87
- buffer.clear();
88
- }
89
- });
34
+ const rangeSize = options.rangeSize ?? 10 * 1024 * 1024;
35
+ const concurrency = options.concurrency ?? 4;
36
+ const totalSize = options.totalSize;
37
+ const retryOptions = {
38
+ ...DEFAULT_RETRY_OPTIONS,
39
+ maxRetries: options.maxRetries ?? 0
40
+ };
41
+ const abort = options.signal;
42
+ const ranges = planRanges(totalSize, rangeSize);
43
+ const windowSize = concurrency * 2;
44
+ const inflight = /* @__PURE__ */ new Map();
45
+ const buffer = /* @__PURE__ */ new Map();
46
+ let assembledSha1 = null;
47
+ let nextToSchedule = 0;
48
+ let nextToEmit = 0;
49
+ let expectedSha1;
50
+ let firstError = null;
51
+ function scheduleNext() {
52
+ while (firstError === null && abort?.aborted !== true && nextToSchedule < ranges.length && inflight.size + buffer.size < windowSize) {
53
+ const range = ranges[nextToSchedule];
54
+ if (range === void 0) break;
55
+ const idx = nextToSchedule;
56
+ nextToSchedule++;
57
+ const task = (async () => {
58
+ try {
59
+ const result = await fetchRangeWithRetry(raw, accountInfo, options.fileId, range.start, range.end, totalSize, retryOptions, abort);
60
+ buffer.set(idx, result);
61
+ } catch (err) {
62
+ if (firstError === null) firstError = err;
63
+ } finally {
64
+ inflight.delete(idx);
65
+ }
66
+ })();
67
+ inflight.set(idx, task);
68
+ }
69
+ }
70
+ return new ReadableStream({
71
+ start(controller) {
72
+ try {
73
+ abort?.throwIfAborted();
74
+ scheduleNext();
75
+ } catch (err) {
76
+ controller.error(err);
77
+ }
78
+ },
79
+ async pull(controller) {
80
+ try {
81
+ while (!buffer.has(nextToEmit)) {
82
+ abort?.throwIfAborted();
83
+ if (firstError !== null) throw firstError;
84
+ if (inflight.size === 0) {
85
+ controller.close();
86
+ return;
87
+ }
88
+ await Promise.race(inflight.values());
89
+ }
90
+ const result = buffer.get(nextToEmit);
91
+ if (result !== void 0) {
92
+ buffer.delete(nextToEmit);
93
+ nextToEmit++;
94
+ const rangeSha1 = normalizeVerifiableSha1(result.contentSha1);
95
+ if (expectedSha1 === void 0) expectedSha1 = rangeSha1;
96
+ else assertDownloadSha1HeaderAgreement(expectedSha1, rangeSha1);
97
+ if (expectedSha1 !== null) {
98
+ assembledSha1 ??= new IncrementalSha1();
99
+ await assembledSha1.update(result.data);
100
+ }
101
+ controller.enqueue(result.data);
102
+ }
103
+ scheduleNext();
104
+ if (nextToEmit >= ranges.length && buffer.size === 0 && inflight.size === 0 && firstError === null) {
105
+ if (expectedSha1 !== void 0 && expectedSha1 !== null && assembledSha1 !== null) assertDownloadSha1(expectedSha1, await assembledSha1.digest());
106
+ controller.close();
107
+ }
108
+ } catch (err) {
109
+ controller.error(err);
110
+ }
111
+ },
112
+ cancel() {
113
+ buffer.clear();
114
+ }
115
+ });
90
116
  }
91
- async function fetchRangeWithRetry(raw, accountInfo, fileId, start, end, retryOptions, signal) {
92
- let lastError;
93
- for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {
94
- if (attempt > 0) {
95
- const delay = computeBackoff(attempt - 1, retryOptions);
96
- await sleep(delay, signal);
97
- }
98
- try {
99
- signal?.throwIfAborted();
100
- const resp = await raw.downloadFileById(
101
- accountInfo.getDownloadUrl(),
102
- accountInfo.getAuthToken(),
103
- fileId,
104
- {
105
- range: byteRangeHeader(start, end),
106
- ...signal !== void 0 ? { signal } : {}
107
- }
108
- );
109
- if (!resp.body) throw new Error("Download chunk has no body");
110
- return await collectStream(resp.body);
111
- } catch (err) {
112
- lastError = err;
113
- if (signal?.aborted) throw err;
114
- if (err instanceof DOMException && err.name === "AbortError") throw err;
115
- }
116
- }
117
- throw lastError instanceof Error ? lastError : new Error("Range download failed after retries");
117
+ /**
118
+ * Fetches a single byte range with bounded retry on transient failures.
119
+ * @param raw - Low-level B2 API client.
120
+ * @param accountInfo - Authorized account state.
121
+ * @param fileId - ID of the file being downloaded.
122
+ * @param start - Inclusive byte offset where the range begins.
123
+ * @param end - Inclusive byte offset where the range ends.
124
+ * @param totalSize - Expected complete file size.
125
+ * @param retryOptions - Retry settings controlling attempts and backoff.
126
+ * @param signal - Optional abort signal that cancels the range and any pending retry.
127
+ *
128
+ * @returns The range's bytes, or throws after exhausting all retry attempts.
129
+ */
130
+ async function fetchRangeWithRetry(raw, accountInfo, fileId, start, end, totalSize, retryOptions, signal) {
131
+ let lastError;
132
+ for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {
133
+ if (attempt > 0) {
134
+ const retryAfter = lastError instanceof B2Error && lastError.retryAfter !== void 0 ? lastError.retryAfter : void 0;
135
+ await sleep(computeBackoff(attempt - 1, retryOptions, retryAfter), signal);
136
+ }
137
+ try {
138
+ signal?.throwIfAborted();
139
+ const resp = await raw.downloadFileById(accountInfo.getDownloadUrl(), accountInfo.getAuthToken(), fileId, {
140
+ range: byteRangeHeader(start, end),
141
+ ...signal !== void 0 ? { signal } : {}
142
+ });
143
+ if (resp.status < 200 || resp.status >= 300) throw await classifyDownloadResponseError(resp);
144
+ if (!resp.body) throw new Error("Download chunk has no body");
145
+ const data = await collectStream(resp.body);
146
+ validateRangeResponse(resp, start, end, totalSize, data.byteLength);
147
+ return {
148
+ data,
149
+ contentSha1: normalizeSha1(resp.headers.get("X-Bz-Content-Sha1"))
150
+ };
151
+ } catch (err) {
152
+ lastError = err;
153
+ if (signal?.aborted) throw err;
154
+ if (err instanceof DOMException && err.name === "AbortError") throw err;
155
+ if (!isRetryableRangeError(err) || attempt === retryOptions.maxRetries) throw err;
156
+ }
157
+ }
158
+ throw lastError instanceof Error ? lastError : /* @__PURE__ */ new Error("Range download failed after retries");
118
159
  }
119
- export {
120
- createParallelDownloadStream
160
+ var RangeValidationError = class extends Error {
161
+ retryable = false;
162
+ constructor(message) {
163
+ super(message);
164
+ this.name = "RangeValidationError";
165
+ }
121
166
  };
122
- //# sourceMappingURL=parallel.js.map
167
+ function validateRangeResponse(response, start, end, totalSize, byteLength) {
168
+ const expectedLength = end - start + 1;
169
+ if (response.status !== 206) throw new RangeValidationError(`Expected HTTP 206 Partial Content for range ${start}-${end}, got ${response.status}`);
170
+ const contentRange = response.headers.get("Content-Range");
171
+ if (contentRange === null) throw new RangeValidationError(`Missing Content-Range for range ${start}-${end}`);
172
+ const match = contentRange.match(/^bytes (\d+)-(\d+)\/(\d+|\*)$/);
173
+ if (match === null) throw new RangeValidationError(`Invalid Content-Range for range ${start}-${end}: ${contentRange}`);
174
+ const actualStart = Number.parseInt(match[1] ?? "", 10);
175
+ const actualEnd = Number.parseInt(match[2] ?? "", 10);
176
+ const actualTotal = match[3] ?? "";
177
+ if (actualStart !== start || actualEnd !== end) throw new RangeValidationError(`Content-Range ${contentRange} does not match requested range ${start}-${end}`);
178
+ if (actualTotal === "*") throw new RangeValidationError(`Content-Range ${contentRange} does not include total size`);
179
+ if (Number.parseInt(actualTotal, 10) !== totalSize) throw new RangeValidationError(`Content-Range ${contentRange} does not match expected total size ${totalSize}`);
180
+ if (byteLength !== expectedLength) throw new RangeValidationError(`Expected ${expectedLength} bytes for range ${start}-${end}, got ${byteLength}`);
181
+ }
182
+ async function classifyDownloadResponseError(response) {
183
+ let errorBody = {
184
+ status: response.status,
185
+ code: "internal_error",
186
+ message: `HTTP ${response.status}`
187
+ };
188
+ if (response.body !== null) {
189
+ const bytes = await collectStream(response.body);
190
+ try {
191
+ const parsed = JSON.parse(utf8Decoder.decode(bytes));
192
+ errorBody = {
193
+ status: response.status,
194
+ code: parsed.code ?? "internal_error",
195
+ message: parsed.message ?? `HTTP ${response.status}`
196
+ };
197
+ } catch {}
198
+ }
199
+ const retryAfterHeader = response.headers.get("Retry-After");
200
+ const retryAfter = retryAfterHeader !== null ? Number.parseInt(retryAfterHeader, 10) : void 0;
201
+ const requestId = response.headers.get("X-Bz-Request-Id") ?? void 0;
202
+ return classifyError(errorBody, {
203
+ ...retryAfter !== void 0 ? { retryAfter } : {},
204
+ ...requestId !== void 0 ? { requestId } : {}
205
+ });
206
+ }
207
+ function isRetryableRangeError(err) {
208
+ if (err instanceof B2Error || err instanceof NetworkError) return err.retryable;
209
+ if (hasRetryableFlag(err)) return err.retryable;
210
+ return err instanceof TypeError;
211
+ }
212
+ function hasRetryableFlag(err) {
213
+ return typeof err === "object" && err !== null && "retryable" in err && typeof err.retryable === "boolean";
214
+ }
215
+ //#endregion
216
+ export { createParallelDownloadStream };
217
+
218
+ //# sourceMappingURL=parallel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"parallel.js","sources":["../../src/download/parallel.ts"],"sourcesContent":["import type { AccountInfo } from '../auth/account-info.ts'\nimport { DEFAULT_RETRY_OPTIONS, computeBackoff, sleep } from '../http/retry.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport { collectStream } from '../streams/collect.ts'\nimport type { FileId } from '../types/ids.ts'\nimport { DEFAULT_TRANSFER_CONCURRENCY } from '../util/defaults.ts'\nimport { byteRangeHeader, planRanges } from '../util/plan-ranges.ts'\n\n/** Options for downloading a file using concurrent byte-range requests. */\nexport interface ParallelDownloadOptions {\n /** ID of the file to download. */\n readonly fileId: FileId\n /** Total file size in bytes (must be known in advance). */\n readonly totalSize: number\n /** Size of each ranged chunk in bytes. Defaults to 10 MB. */\n readonly rangeSize?: number\n /** Maximum number of chunks fetched in parallel. Defaults to 4. */\n readonly concurrency?: number\n /** Maximum retry attempts per range on transient failures. Defaults to 5. */\n readonly maxRetries?: number\n /** Signal to abort the download. */\n readonly signal?: AbortSignal\n}\n\n/**\n * Creates a readable stream that downloads a file using parallel byte-range requests.\n *\n * The file is split into fixed-size ranges fetched in a **sliding\n * window** keyed off the consumer's read pace. The window size is\n * `concurrency * 2`: up to `concurrency` ranges are in flight at once,\n * plus up to `concurrency` completed-but-not-yet-emitted ranges buffered\n * ahead of the read head. New fetches kick off only when the consumer\n * reads a chunk, so a slow downstream pipe (e.g. a saturated network\n * sink or a `pipeTo` consumer that drains slowly) backpressures into\n * the SDK and bounds peak memory to `(concurrency * 2) * rangeSize`.\n *\n * The previous eager implementation scheduled every range into\n * `Promise.all` up front; a slow head-of-line range could hold the\n * entire file in memory while later ranges finished and waited.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param options - Parallel download parameters (file ID, size, concurrency).\n *\n * @returns A `ReadableStream` that yields file bytes in order.\n */\nexport function createParallelDownloadStream(\n raw: RawClient,\n accountInfo: AccountInfo,\n options: ParallelDownloadOptions,\n): ReadableStream<Uint8Array> {\n const rangeSize = options.rangeSize ?? 10 * 1024 * 1024\n const concurrency = options.concurrency ?? DEFAULT_TRANSFER_CONCURRENCY\n const totalSize = options.totalSize\n // `maxRetries` is part of `DEFAULT_RETRY_OPTIONS`; only override when\n // the caller supplied an explicit value so the shared default stays\n // the single source of truth.\n const retryOptions = {\n ...DEFAULT_RETRY_OPTIONS,\n ...(options.maxRetries !== undefined ? { maxRetries: options.maxRetries } : {}),\n }\n const abort = options.signal\n\n const ranges = planRanges(totalSize, rangeSize)\n\n // `inflight` holds currently-fetching range promises; `buffer` holds\n // completed ranges waiting for their slot in the emit order. Total\n // scheduled-but-not-emitted = inflight.size + buffer.size is capped\n // at `windowSize`, so peak memory is bounded regardless of any\n // single range's latency.\n const windowSize = concurrency * 2\n const inflight = new Map<number, Promise<void>>()\n const buffer = new Map<number, Uint8Array>()\n let nextToSchedule = 0\n let nextToEmit = 0\n let firstError: unknown = null\n\n function scheduleNext(): void {\n while (\n firstError === null &&\n // Honour abort here so a completed range that triggers a top-up\n // doesn't queue one final fetch after the caller aborted. Without\n // this gate, one extra range request fires post-abort before the\n // `pull()` loop notices.\n abort?.aborted !== true &&\n nextToSchedule < ranges.length &&\n inflight.size + buffer.size < windowSize\n ) {\n const range = ranges[nextToSchedule]\n if (range === undefined) break\n const idx = nextToSchedule\n nextToSchedule++\n const task = (async () => {\n try {\n const data = await fetchRangeWithRetry(\n raw,\n accountInfo,\n options.fileId,\n range.start,\n range.end,\n retryOptions,\n abort,\n )\n buffer.set(idx, data)\n } catch (err) {\n if (firstError === null) firstError = err\n } finally {\n inflight.delete(idx)\n }\n })()\n inflight.set(idx, task)\n }\n }\n\n return new ReadableStream<Uint8Array>({\n start(controller) {\n // Pre-warm: kick off the initial window so the first `pull` has\n // something to await. An already-aborted signal surfaces as a\n // stream error rather than a synchronous throw, matching the\n // contract callers had with the previous eager implementation.\n try {\n abort?.throwIfAborted()\n scheduleNext()\n } catch (err) {\n controller.error(err)\n }\n },\n async pull(controller) {\n try {\n // Wait until the next in-order chunk is available.\n while (!buffer.has(nextToEmit)) {\n abort?.throwIfAborted()\n if (firstError !== null) throw firstError\n /* v8 ignore start -- defensive close: every code path that\n populates `nextToEmit < ranges.length` also schedules into\n `inflight`. The pre-warm in `start()` plus `scheduleNext()`\n after each enqueue guarantee that whenever there are\n pending ranges, at least one is in flight. This guard\n exists only so a future refactor can't accidentally stall\n the stream by leaving both maps empty mid-stream. */\n if (inflight.size === 0) {\n controller.close()\n return\n }\n /* v8 ignore stop */\n await Promise.race(inflight.values())\n }\n\n const data = buffer.get(nextToEmit)\n if (data !== undefined) {\n buffer.delete(nextToEmit)\n nextToEmit++\n controller.enqueue(data)\n }\n\n // Top up the window now that we freed a slot by emitting.\n scheduleNext()\n\n if (\n nextToEmit >= ranges.length &&\n buffer.size === 0 &&\n inflight.size === 0 &&\n firstError === null\n ) {\n controller.close()\n }\n } catch (err) {\n controller.error(err)\n }\n },\n cancel() {\n // Abort propagation handles in-flight requests; just drop buffered\n // data so it can be GC'd promptly.\n buffer.clear()\n },\n })\n}\n\n/**\n * Fetches a single byte range with bounded retry on transient failures.\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param fileId - ID of the file being downloaded.\n * @param start - Inclusive byte offset where the range begins.\n * @param end - Inclusive byte offset where the range ends.\n * @param retryOptions - Retry settings controlling attempts and backoff.\n * @param signal - Optional abort signal that cancels the range and any pending retry.\n *\n * @returns The range's bytes, or throws after exhausting all retry attempts.\n */\nasync function fetchRangeWithRetry(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: FileId,\n start: number,\n end: number,\n retryOptions: typeof DEFAULT_RETRY_OPTIONS,\n signal: AbortSignal | undefined,\n): Promise<Uint8Array> {\n let lastError: unknown\n for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {\n if (attempt > 0) {\n const delay = computeBackoff(attempt - 1, retryOptions)\n await sleep(delay, signal)\n }\n try {\n signal?.throwIfAborted()\n const resp = await raw.downloadFileById(\n accountInfo.getDownloadUrl(),\n accountInfo.getAuthToken(),\n fileId,\n {\n range: byteRangeHeader(start, end),\n ...(signal !== undefined ? { signal } : {}),\n },\n )\n if (!resp.body) throw new Error('Download chunk has no body')\n return await collectStream(resp.body)\n } catch (err) {\n lastError = err\n // Honour AbortSignal cancellation: don't retry past an abort.\n if (signal?.aborted) throw err\n if (err instanceof DOMException && err.name === 'AbortError') throw err\n }\n }\n throw lastError instanceof Error ? lastError : new Error('Range download failed after retries')\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"parallel.js","names":[],"sources":["../../src/download/parallel.ts"],"sourcesContent":["import type { AccountInfo } from '../auth/account-info.ts'\nimport { B2Error, classifyError, NetworkError } from '../errors/index.ts'\nimport { computeBackoff, DEFAULT_RETRY_OPTIONS, type RetryOptions, sleep } from '../http/retry.ts'\nimport type { RawClient } from '../raw/index.ts'\nimport { collectStream } from '../streams/collect.ts'\nimport { IncrementalSha1 } from '../streams/hash.ts'\nimport type { B2ErrorResponse } from '../types/errors.ts'\nimport type { FileId } from '../types/ids.ts'\nimport { DEFAULT_TRANSFER_CONCURRENCY } from '../util/defaults.ts'\nimport { normalizeSha1 } from '../util/normalize.ts'\nimport { byteRangeHeader, planRanges } from '../util/plan-ranges.ts'\nimport { normalizeVerifiableSha1 } from '../util/sha1.ts'\nimport { utf8Decoder } from '../util/text-codec.ts'\nimport { assertDownloadSha1, assertDownloadSha1HeaderAgreement } from './checksum.ts'\n\n/** Options for downloading a file using concurrent byte-range requests. */\nexport interface ParallelDownloadOptions {\n /** ID of the file to download. */\n readonly fileId: FileId\n /** Total file size in bytes (must be known in advance). */\n readonly totalSize: number\n /** Size of each ranged chunk in bytes. Defaults to 10 MB. */\n readonly rangeSize?: number\n /** Maximum number of chunks fetched in parallel. Defaults to 4. */\n readonly concurrency?: number\n /**\n * Extra retry attempts per range on transient failures. Defaults to 0 because\n * `B2Client` already applies `RetryTransport`; set this only when supplying a\n * raw client that does not already retry transport failures.\n */\n readonly maxRetries?: number\n /** Signal to abort the download. */\n readonly signal?: AbortSignal\n}\n\ninterface RangeDownloadResult {\n readonly data: Uint8Array\n readonly contentSha1: string | null\n}\n\n/**\n * Creates a readable stream that downloads a file using parallel byte-range requests.\n *\n * The file is split into fixed-size ranges fetched in a **sliding\n * window** keyed off the consumer's read pace. The window size is\n * `concurrency * 2`: up to `concurrency` ranges are in flight at once,\n * plus up to `concurrency` completed-but-not-yet-emitted ranges buffered\n * ahead of the read head. New fetches kick off only when the consumer\n * reads a chunk, so a slow downstream pipe (e.g. a saturated network\n * sink or a `pipeTo` consumer that drains slowly) backpressures into\n * the SDK and bounds peak memory to `(concurrency * 2) * rangeSize`.\n *\n * The previous eager implementation scheduled every range into\n * `Promise.all` up front; a slow head-of-line range could hold the\n * entire file in memory while later ranges finished and waited.\n *\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param options - Parallel download parameters (file ID, size, concurrency).\n *\n * @returns A `ReadableStream` that yields file bytes in order.\n */\nexport function createParallelDownloadStream(\n raw: RawClient,\n accountInfo: AccountInfo,\n options: ParallelDownloadOptions,\n): ReadableStream<Uint8Array> {\n const rangeSize = options.rangeSize ?? 10 * 1024 * 1024\n const concurrency = options.concurrency ?? DEFAULT_TRANSFER_CONCURRENCY\n const totalSize = options.totalSize\n // The high-level B2Client already wraps the raw transport in RetryTransport.\n // Keep the parallel-download outer retry disabled by default so each range\n // has one retry budget unless callers explicitly opt into an extra raw-client\n // retry layer.\n const retryOptions = {\n ...DEFAULT_RETRY_OPTIONS,\n maxRetries: options.maxRetries ?? 0,\n }\n const abort = options.signal\n\n const ranges = planRanges(totalSize, rangeSize)\n\n // `inflight` holds currently-fetching range promises; `buffer` holds\n // completed ranges waiting for their slot in the emit order. Total\n // scheduled-but-not-emitted = inflight.size + buffer.size is capped\n // at `windowSize`, so peak memory is bounded regardless of any\n // single range's latency.\n const windowSize = concurrency * 2\n const inflight = new Map<number, Promise<void>>()\n const buffer = new Map<number, RangeDownloadResult>()\n let assembledSha1: IncrementalSha1 | null = null\n let nextToSchedule = 0\n let nextToEmit = 0\n let expectedSha1: string | null | undefined\n let firstError: unknown = null\n\n function scheduleNext(): void {\n while (\n firstError === null &&\n // Honour abort here so a completed range that triggers a top-up\n // doesn't queue one final fetch after the caller aborted. Without\n // this gate, one extra range request fires post-abort before the\n // `pull()` loop notices.\n abort?.aborted !== true &&\n nextToSchedule < ranges.length &&\n inflight.size + buffer.size < windowSize\n ) {\n const range = ranges[nextToSchedule]\n if (range === undefined) break\n const idx = nextToSchedule\n nextToSchedule++\n const task = (async () => {\n try {\n const result = await fetchRangeWithRetry(\n raw,\n accountInfo,\n options.fileId,\n range.start,\n range.end,\n totalSize,\n retryOptions,\n abort,\n )\n buffer.set(idx, result)\n } catch (err) {\n if (firstError === null) firstError = err\n } finally {\n inflight.delete(idx)\n }\n })()\n inflight.set(idx, task)\n }\n }\n\n return new ReadableStream<Uint8Array>({\n start(controller) {\n // Pre-warm: kick off the initial window so the first `pull` has\n // something to await. An already-aborted signal surfaces as a\n // stream error rather than a synchronous throw, matching the\n // contract callers had with the previous eager implementation.\n try {\n abort?.throwIfAborted()\n scheduleNext()\n } catch (err) {\n controller.error(err)\n }\n },\n async pull(controller) {\n try {\n // Wait until the next in-order chunk is available.\n while (!buffer.has(nextToEmit)) {\n abort?.throwIfAborted()\n if (firstError !== null) throw firstError\n // Zero-byte downloads have no planned ranges and close here. For\n // non-empty downloads, this guard prevents a future refactor from\n // stalling the stream if it leaves both maps empty mid-stream.\n if (inflight.size === 0) {\n controller.close()\n return\n }\n await Promise.race(inflight.values())\n }\n\n const result = buffer.get(nextToEmit)\n if (result !== undefined) {\n buffer.delete(nextToEmit)\n nextToEmit++\n const rangeSha1 = normalizeVerifiableSha1(result.contentSha1)\n if (expectedSha1 === undefined) {\n expectedSha1 = rangeSha1\n } else {\n // B2 range responses should agree on the same whole-file SHA-1;\n // any value or presence change makes verification untrustworthy.\n assertDownloadSha1HeaderAgreement(expectedSha1, rangeSha1)\n }\n if (expectedSha1 !== null) {\n assembledSha1 ??= new IncrementalSha1()\n await assembledSha1.update(result.data)\n }\n controller.enqueue(result.data)\n }\n\n // Top up the window now that we freed a slot by emitting.\n scheduleNext()\n\n if (\n nextToEmit >= ranges.length &&\n buffer.size === 0 &&\n inflight.size === 0 &&\n firstError === null\n ) {\n if (expectedSha1 !== undefined && expectedSha1 !== null && assembledSha1 !== null) {\n assertDownloadSha1(expectedSha1, await assembledSha1.digest())\n }\n controller.close()\n }\n } catch (err) {\n controller.error(err)\n }\n },\n cancel() {\n // Abort propagation handles in-flight requests; just drop buffered\n // data so it can be GC'd promptly.\n buffer.clear()\n },\n })\n}\n\n/**\n * Fetches a single byte range with bounded retry on transient failures.\n * @param raw - Low-level B2 API client.\n * @param accountInfo - Authorized account state.\n * @param fileId - ID of the file being downloaded.\n * @param start - Inclusive byte offset where the range begins.\n * @param end - Inclusive byte offset where the range ends.\n * @param totalSize - Expected complete file size.\n * @param retryOptions - Retry settings controlling attempts and backoff.\n * @param signal - Optional abort signal that cancels the range and any pending retry.\n *\n * @returns The range's bytes, or throws after exhausting all retry attempts.\n */\nasync function fetchRangeWithRetry(\n raw: RawClient,\n accountInfo: AccountInfo,\n fileId: FileId,\n start: number,\n end: number,\n totalSize: number,\n retryOptions: RetryOptions,\n signal: AbortSignal | undefined,\n): Promise<RangeDownloadResult> {\n let lastError: unknown\n for (let attempt = 0; attempt <= retryOptions.maxRetries; attempt++) {\n if (attempt > 0) {\n const retryAfter =\n lastError instanceof B2Error && lastError.retryAfter !== undefined\n ? lastError.retryAfter\n : undefined\n const delay = computeBackoff(attempt - 1, retryOptions, retryAfter)\n await sleep(delay, signal)\n }\n try {\n signal?.throwIfAborted()\n const resp = await raw.downloadFileById(\n accountInfo.getDownloadUrl(),\n accountInfo.getAuthToken(),\n fileId,\n {\n range: byteRangeHeader(start, end),\n ...(signal !== undefined ? { signal } : {}),\n },\n )\n if (resp.status < 200 || resp.status >= 300) {\n throw await classifyDownloadResponseError(resp)\n }\n if (!resp.body) throw new Error('Download chunk has no body')\n const data = await collectStream(resp.body)\n validateRangeResponse(resp, start, end, totalSize, data.byteLength)\n return {\n data,\n contentSha1: normalizeSha1(resp.headers.get('X-Bz-Content-Sha1')),\n }\n } catch (err) {\n lastError = err\n // Honour AbortSignal cancellation: don't retry past an abort.\n if (signal?.aborted) throw err\n if (err instanceof DOMException && err.name === 'AbortError') throw err\n if (!isRetryableRangeError(err) || attempt === retryOptions.maxRetries) {\n throw err\n }\n }\n }\n throw lastError instanceof Error ? lastError : new Error('Range download failed after retries')\n}\n\nclass RangeValidationError extends Error {\n readonly retryable = false\n\n constructor(message: string) {\n super(message)\n this.name = 'RangeValidationError'\n }\n}\n\nfunction validateRangeResponse(\n response: { headers: Headers; status: number },\n start: number,\n end: number,\n totalSize: number,\n byteLength: number,\n): void {\n const expectedLength = end - start + 1\n if (response.status !== 206) {\n throw new RangeValidationError(\n `Expected HTTP 206 Partial Content for range ${start}-${end}, got ${response.status}`,\n )\n }\n\n const contentRange = response.headers.get('Content-Range')\n if (contentRange === null) {\n throw new RangeValidationError(`Missing Content-Range for range ${start}-${end}`)\n }\n\n const match = contentRange.match(/^bytes (\\d+)-(\\d+)\\/(\\d+|\\*)$/)\n if (match === null) {\n throw new RangeValidationError(\n `Invalid Content-Range for range ${start}-${end}: ${contentRange}`,\n )\n }\n\n const actualStart = Number.parseInt(match[1] ?? '', 10)\n const actualEnd = Number.parseInt(match[2] ?? '', 10)\n const actualTotal = match[3] ?? ''\n if (actualStart !== start || actualEnd !== end) {\n throw new RangeValidationError(\n `Content-Range ${contentRange} does not match requested range ${start}-${end}`,\n )\n }\n\n if (actualTotal === '*') {\n throw new RangeValidationError(`Content-Range ${contentRange} does not include total size`)\n }\n\n const parsedTotal = Number.parseInt(actualTotal, 10)\n if (parsedTotal !== totalSize) {\n throw new RangeValidationError(\n `Content-Range ${contentRange} does not match expected total size ${totalSize}`,\n )\n }\n\n if (byteLength !== expectedLength) {\n throw new RangeValidationError(\n `Expected ${expectedLength} bytes for range ${start}-${end}, got ${byteLength}`,\n )\n }\n}\n\nasync function classifyDownloadResponseError(response: {\n headers: Headers\n status: number\n body: ReadableStream<Uint8Array> | null\n}): Promise<B2Error> {\n let errorBody: B2ErrorResponse = {\n status: response.status,\n code: 'internal_error',\n message: `HTTP ${response.status}`,\n }\n\n if (response.body !== null) {\n const bytes = await collectStream(response.body)\n try {\n const parsed = JSON.parse(utf8Decoder.decode(bytes)) as Partial<B2ErrorResponse>\n errorBody = {\n status: response.status,\n code: parsed.code ?? 'internal_error',\n message: parsed.message ?? `HTTP ${response.status}`,\n }\n } catch {\n // Keep the synthetic errorBody above when the response body is not JSON.\n }\n }\n\n const retryAfterHeader = response.headers.get('Retry-After')\n const retryAfter = retryAfterHeader !== null ? Number.parseInt(retryAfterHeader, 10) : undefined\n const requestId = response.headers.get('X-Bz-Request-Id') ?? undefined\n\n return classifyError(errorBody, {\n ...(retryAfter !== undefined ? { retryAfter } : {}),\n ...(requestId !== undefined ? { requestId } : {}),\n })\n}\n\nfunction isRetryableRangeError(err: unknown): boolean {\n if (err instanceof B2Error || err instanceof NetworkError) return err.retryable\n if (hasRetryableFlag(err)) return err.retryable\n // Bare FetchTransport surfaces network failures as TypeError. Validation\n // failures use RangeValidationError above, so they never land here.\n return err instanceof TypeError\n}\n\nfunction hasRetryableFlag(err: unknown): err is { retryable: boolean } {\n return (\n typeof err === 'object' &&\n err !== null &&\n 'retryable' in err &&\n typeof (err as { retryable?: unknown }).retryable === 'boolean'\n )\n}\n"],"mappings":"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"}