@backblaze-labs/b2-sdk 0.3.0 → 0.4.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 (393) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +18 -5
  3. package/dist/_virtual/_b2-sdk-version-json.cjs +1 -1
  4. package/dist/_virtual/_b2-sdk-version-json.js +1 -1
  5. package/dist/auth/reauth-coalescer.cjs +65 -0
  6. package/dist/auth/reauth-coalescer.cjs.map +1 -0
  7. package/dist/auth/reauth-coalescer.d.cts +34 -0
  8. package/dist/auth/reauth-coalescer.d.cts.map +1 -0
  9. package/dist/auth/reauth-coalescer.d.ts +34 -0
  10. package/dist/auth/reauth-coalescer.d.ts.map +1 -0
  11. package/dist/auth/reauth-coalescer.js +65 -0
  12. package/dist/auth/reauth-coalescer.js.map +1 -0
  13. package/dist/backup/raw.cjs +37 -15
  14. package/dist/backup/raw.cjs.map +1 -1
  15. package/dist/backup/raw.d.cts +2 -2
  16. package/dist/backup/raw.d.cts.map +1 -1
  17. package/dist/backup/raw.d.ts +2 -2
  18. package/dist/backup/raw.d.ts.map +1 -1
  19. package/dist/backup/raw.js +37 -15
  20. package/dist/backup/raw.js.map +1 -1
  21. package/dist/bucket-validation.cjs +111 -0
  22. package/dist/bucket-validation.cjs.map +1 -0
  23. package/dist/bucket-validation.d.cts +27 -0
  24. package/dist/bucket-validation.d.cts.map +1 -0
  25. package/dist/bucket-validation.d.ts +27 -0
  26. package/dist/bucket-validation.d.ts.map +1 -0
  27. package/dist/bucket-validation.js +109 -0
  28. package/dist/bucket-validation.js.map +1 -0
  29. package/dist/bucket.cjs +123 -73
  30. package/dist/bucket.cjs.map +1 -1
  31. package/dist/bucket.d.cts +156 -61
  32. package/dist/bucket.d.cts.map +1 -1
  33. package/dist/bucket.d.ts +156 -61
  34. package/dist/bucket.d.ts.map +1 -1
  35. package/dist/bucket.js +123 -73
  36. package/dist/bucket.js.map +1 -1
  37. package/dist/client.cjs +29 -8
  38. package/dist/client.cjs.map +1 -1
  39. package/dist/client.d.cts +30 -9
  40. package/dist/client.d.cts.map +1 -1
  41. package/dist/client.d.ts +30 -9
  42. package/dist/client.d.ts.map +1 -1
  43. package/dist/client.js +29 -8
  44. package/dist/client.js.map +1 -1
  45. package/dist/download/single.cjs +38 -13
  46. package/dist/download/single.cjs.map +1 -1
  47. package/dist/download/single.d.cts +10 -4
  48. package/dist/download/single.d.cts.map +1 -1
  49. package/dist/download/single.d.ts +10 -4
  50. package/dist/download/single.d.ts.map +1 -1
  51. package/dist/download/single.js +38 -13
  52. package/dist/download/single.js.map +1 -1
  53. package/dist/errors/index.cjs +27 -4
  54. package/dist/errors/index.cjs.map +1 -1
  55. package/dist/errors/index.d.cts +15 -0
  56. package/dist/errors/index.d.cts.map +1 -1
  57. package/dist/errors/index.d.ts +15 -0
  58. package/dist/errors/index.d.ts.map +1 -1
  59. package/dist/errors/index.js +27 -5
  60. package/dist/errors/index.js.map +1 -1
  61. package/dist/http/non-idempotent-mutations.cjs +38 -0
  62. package/dist/http/non-idempotent-mutations.cjs.map +1 -0
  63. package/dist/http/non-idempotent-mutations.d.cts +25 -0
  64. package/dist/http/non-idempotent-mutations.d.cts.map +1 -0
  65. package/dist/http/non-idempotent-mutations.d.ts +25 -0
  66. package/dist/http/non-idempotent-mutations.d.ts.map +1 -0
  67. package/dist/http/non-idempotent-mutations.js +35 -0
  68. package/dist/http/non-idempotent-mutations.js.map +1 -0
  69. package/dist/http/transport.cjs +9 -8
  70. package/dist/http/transport.cjs.map +1 -1
  71. package/dist/http/transport.d.cts +5 -0
  72. package/dist/http/transport.d.cts.map +1 -1
  73. package/dist/http/transport.d.ts +5 -0
  74. package/dist/http/transport.d.ts.map +1 -1
  75. package/dist/http/transport.js +9 -8
  76. package/dist/http/transport.js.map +1 -1
  77. package/dist/http/user-agent.cjs +12 -1
  78. package/dist/http/user-agent.cjs.map +1 -1
  79. package/dist/http/user-agent.d.cts +8 -0
  80. package/dist/http/user-agent.d.cts.map +1 -1
  81. package/dist/http/user-agent.d.ts +8 -0
  82. package/dist/http/user-agent.d.ts.map +1 -1
  83. package/dist/http/user-agent.js +13 -3
  84. package/dist/http/user-agent.js.map +1 -1
  85. package/dist/index.cjs +25 -0
  86. package/dist/index.d.cts +10 -9
  87. package/dist/index.d.cts.map +1 -1
  88. package/dist/index.d.ts +10 -9
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +7 -6
  91. package/dist/notifications/index.cjs +3 -0
  92. package/dist/notifications/index.d.cts +2 -0
  93. package/dist/notifications/index.d.cts.map +1 -1
  94. package/dist/notifications/index.d.ts +2 -0
  95. package/dist/notifications/index.d.ts.map +1 -1
  96. package/dist/notifications/index.js +2 -1
  97. package/dist/object.cjs +12 -1
  98. package/dist/object.cjs.map +1 -1
  99. package/dist/object.d.cts +7 -1
  100. package/dist/object.d.cts.map +1 -1
  101. package/dist/object.d.ts +7 -1
  102. package/dist/object.d.ts.map +1 -1
  103. package/dist/object.js +12 -1
  104. package/dist/object.js.map +1 -1
  105. package/dist/partner/auth-core.cjs +4 -23
  106. package/dist/partner/auth-core.cjs.map +1 -1
  107. package/dist/partner/auth-core.d.cts +1 -1
  108. package/dist/partner/auth-core.d.cts.map +1 -1
  109. package/dist/partner/auth-core.d.ts +1 -1
  110. package/dist/partner/auth-core.d.ts.map +1 -1
  111. package/dist/partner/auth-core.js +5 -24
  112. package/dist/partner/auth-core.js.map +1 -1
  113. package/dist/partner/client.cjs +25 -10
  114. package/dist/partner/client.cjs.map +1 -1
  115. package/dist/partner/client.d.cts +32 -10
  116. package/dist/partner/client.d.cts.map +1 -1
  117. package/dist/partner/client.d.ts +32 -10
  118. package/dist/partner/client.d.ts.map +1 -1
  119. package/dist/partner/client.js +25 -10
  120. package/dist/partner/client.js.map +1 -1
  121. package/dist/partner/endpoint-url.cjs +1 -0
  122. package/dist/partner/endpoint-url.cjs.map +1 -1
  123. package/dist/partner/endpoint-url.d.cts +2 -0
  124. package/dist/partner/endpoint-url.d.cts.map +1 -1
  125. package/dist/partner/endpoint-url.d.ts +2 -0
  126. package/dist/partner/endpoint-url.d.ts.map +1 -1
  127. package/dist/partner/endpoint-url.js +1 -0
  128. package/dist/partner/endpoint-url.js.map +1 -1
  129. package/dist/partner/in-memory.cjs.map +1 -1
  130. package/dist/partner/in-memory.js.map +1 -1
  131. package/dist/partner/index.cjs +9 -0
  132. package/dist/partner/index.d.cts +11 -2
  133. package/dist/partner/index.d.cts.map +1 -1
  134. package/dist/partner/index.d.ts +11 -2
  135. package/dist/partner/index.d.ts.map +1 -1
  136. package/dist/partner/index.js +3 -1
  137. package/dist/partner/raw.cjs +52 -44
  138. package/dist/partner/raw.cjs.map +1 -1
  139. package/dist/partner/raw.d.cts +16 -19
  140. package/dist/partner/raw.d.cts.map +1 -1
  141. package/dist/partner/raw.d.ts +16 -19
  142. package/dist/partner/raw.d.ts.map +1 -1
  143. package/dist/partner/raw.js +52 -44
  144. package/dist/partner/raw.js.map +1 -1
  145. package/dist/partner/redaction.cjs +133 -108
  146. package/dist/partner/redaction.cjs.map +1 -1
  147. package/dist/partner/redaction.d.cts +47 -13
  148. package/dist/partner/redaction.d.cts.map +1 -1
  149. package/dist/partner/redaction.d.ts +47 -13
  150. package/dist/partner/redaction.d.ts.map +1 -1
  151. package/dist/partner/redaction.js +133 -109
  152. package/dist/partner/redaction.js.map +1 -1
  153. package/dist/raw/encoding.cjs +29 -10
  154. package/dist/raw/encoding.cjs.map +1 -1
  155. package/dist/raw/encoding.d.cts +3 -2
  156. package/dist/raw/encoding.d.cts.map +1 -1
  157. package/dist/raw/encoding.d.ts +3 -2
  158. package/dist/raw/encoding.d.ts.map +1 -1
  159. package/dist/raw/encoding.js +29 -10
  160. package/dist/raw/encoding.js.map +1 -1
  161. package/dist/raw/index.cjs +64 -39
  162. package/dist/raw/index.cjs.map +1 -1
  163. package/dist/raw/index.d.cts +21 -19
  164. package/dist/raw/index.d.cts.map +1 -1
  165. package/dist/raw/index.d.ts +21 -19
  166. package/dist/raw/index.d.ts.map +1 -1
  167. package/dist/raw/index.js +47 -40
  168. package/dist/raw/index.js.map +1 -1
  169. package/dist/raw/url.cjs.map +1 -1
  170. package/dist/raw/url.d.cts +3 -3
  171. package/dist/raw/url.d.ts +3 -3
  172. package/dist/raw/url.js.map +1 -1
  173. package/dist/simulator/index.cjs +456 -125
  174. package/dist/simulator/index.cjs.map +1 -1
  175. package/dist/simulator/index.d.cts +16 -1
  176. package/dist/simulator/index.d.cts.map +1 -1
  177. package/dist/simulator/index.d.ts +16 -1
  178. package/dist/simulator/index.d.ts.map +1 -1
  179. package/dist/simulator/index.js +449 -130
  180. package/dist/simulator/index.js.map +1 -1
  181. package/dist/simulator/partner-capabilities.cjs +77 -0
  182. package/dist/simulator/partner-capabilities.cjs.map +1 -0
  183. package/dist/simulator/partner-capabilities.d.cts +53 -0
  184. package/dist/simulator/partner-capabilities.d.cts.map +1 -0
  185. package/dist/simulator/partner-capabilities.d.ts +53 -0
  186. package/dist/simulator/partner-capabilities.d.ts.map +1 -0
  187. package/dist/simulator/partner-capabilities.js +73 -0
  188. package/dist/simulator/partner-capabilities.js.map +1 -0
  189. package/dist/simulator/partner.cjs +55 -61
  190. package/dist/simulator/partner.cjs.map +1 -1
  191. package/dist/simulator/partner.d.cts +5 -2
  192. package/dist/simulator/partner.d.cts.map +1 -1
  193. package/dist/simulator/partner.d.ts +5 -2
  194. package/dist/simulator/partner.d.ts.map +1 -1
  195. package/dist/simulator/partner.js +56 -61
  196. package/dist/simulator/partner.js.map +1 -1
  197. package/dist/simulator/validation.cjs +160 -30
  198. package/dist/simulator/validation.cjs.map +1 -1
  199. package/dist/simulator/validation.d.cts +28 -6
  200. package/dist/simulator/validation.d.cts.map +1 -1
  201. package/dist/simulator/validation.d.ts +28 -6
  202. package/dist/simulator/validation.d.ts.map +1 -1
  203. package/dist/simulator/validation.js +148 -31
  204. package/dist/simulator/validation.js.map +1 -1
  205. package/dist/streams/file-source.cjs +1 -1
  206. package/dist/streams/file-source.cjs.map +1 -1
  207. package/dist/streams/file-source.js +1 -1
  208. package/dist/streams/file-source.js.map +1 -1
  209. package/dist/streams/hash.cjs +246 -13
  210. package/dist/streams/hash.cjs.map +1 -1
  211. package/dist/streams/hash.d.cts +37 -12
  212. package/dist/streams/hash.d.cts.map +1 -1
  213. package/dist/streams/hash.d.ts +37 -12
  214. package/dist/streams/hash.d.ts.map +1 -1
  215. package/dist/streams/hash.js +246 -15
  216. package/dist/streams/hash.js.map +1 -1
  217. package/dist/streams/index.cjs +2 -0
  218. package/dist/streams/index.d.cts +1 -1
  219. package/dist/streams/index.d.cts.map +1 -1
  220. package/dist/streams/index.d.ts +1 -1
  221. package/dist/streams/index.d.ts.map +1 -1
  222. package/dist/streams/index.js +2 -2
  223. package/dist/streams/progress.cjs +63 -4
  224. package/dist/streams/progress.cjs.map +1 -1
  225. package/dist/streams/progress.d.cts +28 -2
  226. package/dist/streams/progress.d.cts.map +1 -1
  227. package/dist/streams/progress.d.ts +28 -2
  228. package/dist/streams/progress.d.ts.map +1 -1
  229. package/dist/streams/progress.js +63 -4
  230. package/dist/streams/progress.js.map +1 -1
  231. package/dist/sync/download-staging.cjs +147 -35
  232. package/dist/sync/download-staging.cjs.map +1 -1
  233. package/dist/sync/download-staging.d.cts +25 -2
  234. package/dist/sync/download-staging.d.cts.map +1 -1
  235. package/dist/sync/download-staging.d.ts +25 -2
  236. package/dist/sync/download-staging.d.ts.map +1 -1
  237. package/dist/sync/download-staging.js +146 -36
  238. package/dist/sync/download-staging.js.map +1 -1
  239. package/dist/sync/index.d.cts +1 -1
  240. package/dist/sync/index.d.cts.map +1 -1
  241. package/dist/sync/index.d.ts +1 -1
  242. package/dist/sync/index.d.ts.map +1 -1
  243. package/dist/sync/local-file-io.cjs +5 -1
  244. package/dist/sync/local-file-io.cjs.map +1 -1
  245. package/dist/sync/local-file-io.d.cts +2 -0
  246. package/dist/sync/local-file-io.d.cts.map +1 -1
  247. package/dist/sync/local-file-io.d.ts +2 -0
  248. package/dist/sync/local-file-io.d.ts.map +1 -1
  249. package/dist/sync/local-file-io.js +5 -1
  250. package/dist/sync/local-file-io.js.map +1 -1
  251. package/dist/sync/pairing.cjs +1 -0
  252. package/dist/sync/pairing.cjs.map +1 -1
  253. package/dist/sync/pairing.js +1 -0
  254. package/dist/sync/pairing.js.map +1 -1
  255. package/dist/sync/scanners/b2.cjs +2 -0
  256. package/dist/sync/scanners/b2.cjs.map +1 -1
  257. package/dist/sync/scanners/b2.d.cts.map +1 -1
  258. package/dist/sync/scanners/b2.d.ts.map +1 -1
  259. package/dist/sync/scanners/b2.js +2 -0
  260. package/dist/sync/scanners/b2.js.map +1 -1
  261. package/dist/sync/scanners/local.cjs +150 -33
  262. package/dist/sync/scanners/local.cjs.map +1 -1
  263. package/dist/sync/scanners/local.d.cts +6 -0
  264. package/dist/sync/scanners/local.d.cts.map +1 -1
  265. package/dist/sync/scanners/local.d.ts +6 -0
  266. package/dist/sync/scanners/local.d.ts.map +1 -1
  267. package/dist/sync/scanners/local.js +150 -33
  268. package/dist/sync/scanners/local.js.map +1 -1
  269. package/dist/sync/synchronizer.cjs +76 -29
  270. package/dist/sync/synchronizer.cjs.map +1 -1
  271. package/dist/sync/synchronizer.d.cts +1 -0
  272. package/dist/sync/synchronizer.d.cts.map +1 -1
  273. package/dist/sync/synchronizer.d.ts +1 -0
  274. package/dist/sync/synchronizer.d.ts.map +1 -1
  275. package/dist/sync/synchronizer.js +76 -29
  276. package/dist/sync/synchronizer.js.map +1 -1
  277. package/dist/sync/types.d.cts +19 -3
  278. package/dist/sync/types.d.cts.map +1 -1
  279. package/dist/sync/types.d.ts +19 -3
  280. package/dist/sync/types.d.ts.map +1 -1
  281. package/dist/types/backup.d.cts +6 -1
  282. package/dist/types/backup.d.cts.map +1 -1
  283. package/dist/types/backup.d.ts +6 -1
  284. package/dist/types/backup.d.ts.map +1 -1
  285. package/dist/types/bucket.cjs +81 -11
  286. package/dist/types/bucket.cjs.map +1 -1
  287. package/dist/types/bucket.d.cts +190 -39
  288. package/dist/types/bucket.d.cts.map +1 -1
  289. package/dist/types/bucket.d.ts +190 -39
  290. package/dist/types/bucket.d.ts.map +1 -1
  291. package/dist/types/bucket.js +67 -12
  292. package/dist/types/bucket.js.map +1 -1
  293. package/dist/types/download.cjs.map +1 -1
  294. package/dist/types/download.d.cts +3 -17
  295. package/dist/types/download.d.cts.map +1 -1
  296. package/dist/types/download.d.ts +3 -17
  297. package/dist/types/download.d.ts.map +1 -1
  298. package/dist/types/download.js.map +1 -1
  299. package/dist/types/encryption.cjs +18 -0
  300. package/dist/types/encryption.cjs.map +1 -1
  301. package/dist/types/encryption.d.cts +21 -11
  302. package/dist/types/encryption.d.cts.map +1 -1
  303. package/dist/types/encryption.d.ts +21 -11
  304. package/dist/types/encryption.d.ts.map +1 -1
  305. package/dist/types/encryption.js +18 -0
  306. package/dist/types/encryption.js.map +1 -1
  307. package/dist/types/file.cjs +3 -0
  308. package/dist/types/file.cjs.map +1 -1
  309. package/dist/types/file.d.cts +125 -16
  310. package/dist/types/file.d.cts.map +1 -1
  311. package/dist/types/file.d.ts +125 -16
  312. package/dist/types/file.d.ts.map +1 -1
  313. package/dist/types/file.js +3 -1
  314. package/dist/types/file.js.map +1 -1
  315. package/dist/types/index.d.cts +7 -7
  316. package/dist/types/index.d.cts.map +1 -1
  317. package/dist/types/index.d.ts +7 -7
  318. package/dist/types/index.d.ts.map +1 -1
  319. package/dist/types/key.d.cts +4 -3
  320. package/dist/types/key.d.cts.map +1 -1
  321. package/dist/types/key.d.ts +4 -3
  322. package/dist/types/key.d.ts.map +1 -1
  323. package/dist/types/notifications.cjs +30 -0
  324. package/dist/types/notifications.cjs.map +1 -1
  325. package/dist/types/notifications.d.cts +26 -1
  326. package/dist/types/notifications.d.cts.map +1 -1
  327. package/dist/types/notifications.d.ts +26 -1
  328. package/dist/types/notifications.d.ts.map +1 -1
  329. package/dist/types/notifications.js +29 -1
  330. package/dist/types/notifications.js.map +1 -1
  331. package/dist/types/partner.cjs.map +1 -1
  332. package/dist/types/partner.d.cts +28 -36
  333. package/dist/types/partner.d.cts.map +1 -1
  334. package/dist/types/partner.d.ts +28 -36
  335. package/dist/types/partner.d.ts.map +1 -1
  336. package/dist/types/partner.js.map +1 -1
  337. package/dist/types/upload.d.cts +4 -0
  338. package/dist/types/upload.d.cts.map +1 -1
  339. package/dist/types/upload.d.ts +4 -0
  340. package/dist/types/upload.d.ts.map +1 -1
  341. package/dist/upload/large.cjs +10 -7
  342. package/dist/upload/large.cjs.map +1 -1
  343. package/dist/upload/large.d.cts +15 -6
  344. package/dist/upload/large.d.cts.map +1 -1
  345. package/dist/upload/large.d.ts +15 -6
  346. package/dist/upload/large.d.ts.map +1 -1
  347. package/dist/upload/large.js +10 -7
  348. package/dist/upload/large.js.map +1 -1
  349. package/dist/upload/options.cjs.map +1 -1
  350. package/dist/upload/options.d.cts +12 -0
  351. package/dist/upload/options.d.cts.map +1 -1
  352. package/dist/upload/options.d.ts +12 -0
  353. package/dist/upload/options.d.ts.map +1 -1
  354. package/dist/upload/options.js.map +1 -1
  355. package/dist/upload/resume.cjs +4 -3
  356. package/dist/upload/resume.cjs.map +1 -1
  357. package/dist/upload/resume.d.cts +6 -4
  358. package/dist/upload/resume.d.cts.map +1 -1
  359. package/dist/upload/resume.d.ts +6 -4
  360. package/dist/upload/resume.d.ts.map +1 -1
  361. package/dist/upload/resume.js +4 -3
  362. package/dist/upload/resume.js.map +1 -1
  363. package/dist/upload/retry.cjs +17 -10
  364. package/dist/upload/retry.cjs.map +1 -1
  365. package/dist/upload/retry.d.cts.map +1 -1
  366. package/dist/upload/retry.d.ts.map +1 -1
  367. package/dist/upload/retry.js +17 -10
  368. package/dist/upload/retry.js.map +1 -1
  369. package/dist/upload/single.cjs +35 -31
  370. package/dist/upload/single.cjs.map +1 -1
  371. package/dist/upload/single.d.cts +6 -0
  372. package/dist/upload/single.d.cts.map +1 -1
  373. package/dist/upload/single.d.ts +6 -0
  374. package/dist/upload/single.d.ts.map +1 -1
  375. package/dist/upload/single.js +35 -31
  376. package/dist/upload/single.js.map +1 -1
  377. package/dist/upload/stream.cjs +12 -1
  378. package/dist/upload/stream.cjs.map +1 -1
  379. package/dist/upload/stream.d.cts +6 -0
  380. package/dist/upload/stream.d.cts.map +1 -1
  381. package/dist/upload/stream.d.ts +6 -0
  382. package/dist/upload/stream.d.ts.map +1 -1
  383. package/dist/upload/stream.js +12 -1
  384. package/dist/upload/stream.js.map +1 -1
  385. package/dist/version.cjs +56 -4
  386. package/dist/version.cjs.map +1 -1
  387. package/dist/version.d.cts +53 -4
  388. package/dist/version.d.cts.map +1 -1
  389. package/dist/version.d.ts +53 -4
  390. package/dist/version.d.ts.map +1 -1
  391. package/dist/version.js +52 -5
  392. package/dist/version.js.map +1 -1
  393. package/package.json +11 -4
@@ -1,3 +1,4 @@
1
+ const require_filesystem_errors = require("./filesystem-errors.cjs");
1
2
  const require_path_safety = require("./path-safety.cjs");
2
3
  //#region src/sync/download-staging.ts
3
4
  /** @internal */
@@ -12,6 +13,9 @@ var MAX_CLEANUP_WARNING_ENTRIES = 3;
12
13
  /** @internal */
13
14
  var DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT = 1024;
14
15
  var reapedManagedDirectories = /* @__PURE__ */ new Map();
16
+ var activeManagedDirectoryCreations = /* @__PURE__ */ new Map();
17
+ /** @internal */
18
+ var downloadStagingTestHooks = {};
15
19
  /**
16
20
  * Checks whether a single path segment matches the SDK-managed staging directory name.
17
21
  * @param segment - Candidate path segment.
@@ -32,48 +36,122 @@ function isDownloadStagingDirectorySegment(segment) {
32
36
  * @param statForDeviceCheck - Stat function used to verify filesystem devices.
33
37
  * @param beforeStagingMarkerWrite - Test hook called before marker creation.
34
38
  * @param activityEntryLimit - Maximum entry count inspected for stale staging activity.
39
+ * @param afterManagedStagingMarkerWrite - Test hook called after managed-root marker creation.
35
40
  *
36
- * @returns The resolved staging directory path.
41
+ * @returns The resolved managed and per-run staging directory paths.
37
42
  *
38
43
  * @internal
39
44
  */
40
- async function createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, beforeStagingMarkerWrite, activityEntryLimit = DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT) {
45
+ async function createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, beforeStagingMarkerWrite, activityEntryLimit = DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT, afterManagedStagingMarkerWrite) {
41
46
  const { chmod, lstat, mkdir, readdir, realpath, rm } = await import("node:fs/promises");
42
47
  const managedDirectory = path.join(rootRealPath, DOWNLOAD_STAGING_DIRECTORY_NAME);
48
+ const releaseManagedDirectoryCreation = retainDownloadStagingRootCreation(managedDirectory);
43
49
  try {
44
- await mkdir(managedDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE });
50
+ try {
51
+ await mkdir(managedDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE });
52
+ } catch (err) {
53
+ if (!require_path_safety.hasErrorCode(err, "EEXIST")) throw err;
54
+ }
55
+ if (!(await lstat(managedDirectory)).isDirectory()) throw new Error(`unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is not a directory`);
56
+ const realManagedDirectory = await realpath(managedDirectory);
57
+ require_path_safety.assertPathInsideRoot(rootRealPath, realManagedDirectory, path);
58
+ await assertDownloadPathSameDevice(rootRealPath, realManagedDirectory, statForDeviceCheck, "unsafe local destination path: cannot stage download across filesystems");
59
+ if (!await isManagedDownloadStagingRoot(realManagedDirectory)) {
60
+ if ((await readdir(realManagedDirectory)).length > 0 && !await isManagedDownloadStagingRoot(realManagedDirectory)) throw new Error(`unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`);
61
+ }
62
+ await beforeStagingMarkerWrite?.(realManagedDirectory);
63
+ await writeStagingMarker(realManagedDirectory, path);
64
+ await afterManagedStagingMarkerWrite?.(realManagedDirectory);
65
+ /* v8 ignore next -- best-effort chmod */
66
+ await chmod(realManagedDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {});
67
+ await reapStaleDownloadStagingDirectoriesOnce(realManagedDirectory, path, Date.now(), activityEntryLimit);
68
+ const stagingDirectory = path.join(realManagedDirectory, `${Date.now()}-${randomUUID()}${DOWNLOAD_STAGING_ENTRY_SUFFIX}`);
69
+ await mkdir(stagingDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE });
70
+ /* v8 ignore next -- best-effort chmod */
71
+ await chmod(stagingDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {});
72
+ try {
73
+ const realStagingDirectory = await realpath(stagingDirectory);
74
+ require_path_safety.assertPathInsideRoot(realManagedDirectory, realStagingDirectory, path);
75
+ await assertDownloadPathSameDevice(rootRealPath, realStagingDirectory, statForDeviceCheck, "unsafe local destination path: cannot stage download across filesystems");
76
+ await beforeStagingMarkerWrite?.(realStagingDirectory);
77
+ await writeStagingMarker(realStagingDirectory, path);
78
+ return {
79
+ managedDirectory: realManagedDirectory,
80
+ stagingDirectory: realStagingDirectory
81
+ };
82
+ } catch (err) {
83
+ /* v8 ignore next -- best-effort cleanup */
84
+ await rm(stagingDirectory, {
85
+ recursive: true,
86
+ force: true
87
+ }).catch(() => {});
88
+ throw err;
89
+ }
90
+ } finally {
91
+ releaseManagedDirectoryCreation();
92
+ }
93
+ }
94
+ /**
95
+ * Removes the SDK root marker and the now-empty SDK-managed staging root
96
+ * once all per-run `.download` entries are gone.
97
+ * @param rootRealPath - Resolved local sync root path.
98
+ * @param managedDirectory - Resolved SDK-managed staging root path.
99
+ * @param path - Node path module used for platform-specific path operations.
100
+ *
101
+ * @internal
102
+ */
103
+ async function removeDownloadStagingRootMarkerAndDirectory(rootRealPath, managedDirectory, path) {
104
+ try {
105
+ await removeDownloadStagingRootMarkerAndDirectoryCore(rootRealPath, managedDirectory, path);
45
106
  } catch (err) {
46
- if (!require_path_safety.hasErrorCode(err, "EEXIST")) throw err;
107
+ emitCleanupWarning(`failed to remove B2 SDK download staging root: ${require_filesystem_errors.localFilesystemErrorReason(err)}`);
47
108
  }
48
- if (!(await lstat(managedDirectory)).isDirectory()) throw new Error(`unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is not a directory`);
49
- const realManagedDirectory = await realpath(managedDirectory);
109
+ }
110
+ async function removeDownloadStagingRootMarkerAndDirectoryCore(rootRealPath, managedDirectory, path) {
111
+ const { lstat, readdir, realpath, rmdir, unlink } = await import("node:fs/promises");
112
+ if (downloadStagingRootCreationIsActive(managedDirectory)) return;
113
+ const managedStats = await lstat(managedDirectory).catch((err) => {
114
+ if (require_path_safety.hasErrorCode(err, "ENOENT")) return void 0;
115
+ throw err;
116
+ });
117
+ if (managedStats === void 0 || !managedStats.isDirectory()) return;
118
+ const realManagedDirectory = await realpath(managedDirectory).catch((err) => {
119
+ if (require_path_safety.hasErrorCode(err, "ENOENT")) return void 0;
120
+ throw err;
121
+ });
122
+ if (realManagedDirectory === void 0) return;
50
123
  require_path_safety.assertPathInsideRoot(rootRealPath, realManagedDirectory, path);
51
- await assertDownloadPathSameDevice(rootRealPath, realManagedDirectory, statForDeviceCheck, "unsafe local destination path: cannot stage download across filesystems");
52
- if (!await isManagedDownloadStagingRoot(realManagedDirectory)) {
53
- if ((await readdir(realManagedDirectory)).length > 0 && !await isManagedDownloadStagingRoot(realManagedDirectory)) throw new Error(`unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`);
54
- }
55
- await beforeStagingMarkerWrite?.(realManagedDirectory);
56
- await writeStagingMarker(realManagedDirectory, path);
57
- /* v8 ignore next -- best-effort chmod */
58
- await chmod(realManagedDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {});
59
- await reapStaleDownloadStagingDirectoriesOnce(realManagedDirectory, path, Date.now(), activityEntryLimit);
60
- const stagingDirectory = path.join(realManagedDirectory, `${Date.now()}-${randomUUID()}${DOWNLOAD_STAGING_ENTRY_SUFFIX}`);
61
- await mkdir(stagingDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE });
62
- /* v8 ignore next -- best-effort chmod */
63
- await chmod(stagingDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {});
124
+ if (downloadStagingRootCreationIsActive(realManagedDirectory)) return;
125
+ const markerPath = path.join(realManagedDirectory, DOWNLOAD_STAGING_MARKER_NAME);
126
+ if ((await lstat(markerPath).catch((err) => {
127
+ if (require_path_safety.hasErrorCode(err, "ENOENT")) return void 0;
128
+ throw err;
129
+ }))?.isFile() !== true) return;
130
+ await downloadStagingTestHooks.afterCleanupMarkerStat?.(realManagedDirectory);
131
+ const entries = await readdir(realManagedDirectory, { withFileTypes: true }).catch((err) => {
132
+ if (require_path_safety.hasErrorCode(err, "ENOENT")) return void 0;
133
+ throw err;
134
+ });
135
+ if (entries === void 0) return;
136
+ if (entries.length !== 1 || entries[0]?.name !== ".b2sdk-staging-marker.partial") return;
137
+ if (await realpath(managedDirectory).catch(() => void 0) !== realManagedDirectory) return;
138
+ if (downloadStagingRootCreationIsActive(realManagedDirectory)) return;
139
+ await downloadStagingTestHooks.beforeCleanupMarkerUnlink?.(markerPath);
140
+ if (!await unlink(markerPath).then(() => true).catch((err) => {
141
+ if (require_path_safety.hasErrorCode(err, "ENOENT")) return false;
142
+ throw err;
143
+ })) return;
64
144
  try {
65
- const realStagingDirectory = await realpath(stagingDirectory);
66
- require_path_safety.assertPathInsideRoot(realManagedDirectory, realStagingDirectory, path);
67
- await assertDownloadPathSameDevice(rootRealPath, realStagingDirectory, statForDeviceCheck, "unsafe local destination path: cannot stage download across filesystems");
68
- await beforeStagingMarkerWrite?.(realStagingDirectory);
69
- await writeStagingMarker(realStagingDirectory, path);
70
- return realStagingDirectory;
145
+ await rmdir(realManagedDirectory);
71
146
  } catch (err) {
72
- /* v8 ignore next -- best-effort cleanup */
73
- await rm(stagingDirectory, {
74
- recursive: true,
75
- force: true
76
- }).catch(() => {});
147
+ if (require_path_safety.hasErrorCode(err, "ENOENT")) return;
148
+ if (require_path_safety.hasErrorCode(err, "ENOTEMPTY") || require_path_safety.hasErrorCode(err, "EEXIST")) {
149
+ await restoreDownloadStagingRootMarker(realManagedDirectory, path);
150
+ return;
151
+ }
152
+ await restoreDownloadStagingRootMarker(realManagedDirectory, path).catch((restoreErr) => {
153
+ emitCleanupWarning(`failed to restore B2 SDK download staging marker: ${require_filesystem_errors.localFilesystemErrorReason(restoreErr)}`);
154
+ });
77
155
  throw err;
78
156
  }
79
157
  }
@@ -119,7 +197,9 @@ async function writeStagingMarker(directory, path) {
119
197
  /* v8 ignore next -- best-effort chmod */
120
198
  await handle.chmod(PRIVATE_DOWNLOAD_FILE_MODE).catch(() => {});
121
199
  } catch (err) {
122
- if (require_path_safety.hasErrorCode(err, "EEXIST") || require_path_safety.hasErrorCode(err, "ELOOP")) {
200
+ if (require_path_safety.hasErrorCode(err, "EEXIST") ||
201
+ /* v8 ignore next -- ELOOP depends on platform-specific O_NOFOLLOW behavior */
202
+ require_path_safety.hasErrorCode(err, "ELOOP")) {
123
203
  if ((await lstat(markerPath).catch(() => void 0))?.isFile() === true) return;
124
204
  throw new Error("unsafe local destination path: staging marker is not a regular file");
125
205
  }
@@ -129,6 +209,24 @@ async function writeStagingMarker(directory, path) {
129
209
  await handle?.close().catch(() => {});
130
210
  }
131
211
  }
212
+ function retainDownloadStagingRootCreation(managedDirectory) {
213
+ activeManagedDirectoryCreations.set(managedDirectory, (activeManagedDirectoryCreations.get(managedDirectory) ?? 0) + 1);
214
+ return () => {
215
+ const count = activeManagedDirectoryCreations.get(managedDirectory);
216
+ /* v8 ignore next -- releases are paired with retains in a single closure */
217
+ if (count === void 0 || count <= 1) activeManagedDirectoryCreations.delete(managedDirectory);
218
+ else activeManagedDirectoryCreations.set(managedDirectory, count - 1);
219
+ };
220
+ }
221
+ function downloadStagingRootCreationIsActive(managedDirectory) {
222
+ return activeManagedDirectoryCreations.has(managedDirectory);
223
+ }
224
+ async function restoreDownloadStagingRootMarker(managedDirectory, path) {
225
+ const { lstat, realpath } = await import("node:fs/promises");
226
+ if ((await lstat(managedDirectory).catch(() => void 0))?.isDirectory() !== true) return;
227
+ if (await realpath(managedDirectory).catch(() => void 0) !== managedDirectory) return;
228
+ await writeStagingMarker(managedDirectory, path);
229
+ }
132
230
  /**
133
231
  * Verifies that a candidate path is on the same filesystem device as the root.
134
232
  * @param rootRealPath - Resolved local sync root path.
@@ -149,6 +247,7 @@ async function reapStaleDownloadStagingDirectoriesOnce(managedDirectory, path, n
149
247
  return;
150
248
  }
151
249
  const next = reapStaleDownloadStagingDirectories(managedDirectory, path, nowMillis, activityEntryLimit).finally(() => {
250
+ /* v8 ignore next -- this map entry cannot be replaced until the current cleanup settles */
152
251
  if (reapedManagedDirectories.get(managedDirectory) === next) reapedManagedDirectories.delete(managedDirectory);
153
252
  });
154
253
  reapedManagedDirectories.set(managedDirectory, next);
@@ -160,8 +259,10 @@ async function reapStaleDownloadStagingDirectories(managedDirectory, path, nowMi
160
259
  try {
161
260
  entries = await readdir(managedDirectory, { withFileTypes: true });
162
261
  } catch (err) {
262
+ /* v8 ignore next -- concurrent managed-root removal is a filesystem race without a stable hook */
163
263
  if (require_path_safety.hasErrorCode(err, "ENOENT")) return;
164
- emitCleanupWarning("failed to inspect B2 SDK download staging entries");
264
+ /* v8 ignore next -- platform-specific root permission failures cannot be simulated portably */
265
+ emitCleanupWarning(`failed to inspect B2 SDK download staging entries: ${require_filesystem_errors.localFilesystemErrorReason(err)}`);
165
266
  return;
166
267
  }
167
268
  const cleanupErrors = [];
@@ -176,11 +277,14 @@ async function reapStaleDownloadStagingDirectories(managedDirectory, path, nowMi
176
277
  operation: "inspect"
177
278
  });
178
279
  });
280
+ /* v8 ignore next -- requires a concurrent delete between activity read and realpath */
179
281
  if (realCandidate === void 0) return;
180
282
  try {
181
283
  require_path_safety.assertPathInsideRoot(managedDirectory, realCandidate, path);
182
284
  const latestActivity = await readManagedStagingEntryActivity(candidate, path, activityEntryLimit);
183
- if (latestActivity === void 0 || latestActivity.signature !== activity.signature || !stagingActivityIsStale(latestActivity, nowMillis)) return;
285
+ if (latestActivity === void 0 || latestActivity.signature !== activity.signature || !stagingActivityIsStale(latestActivity, nowMillis))
286
+ /* v8 ignore next -- requires concurrent writes between the first and second activity reads */
287
+ return;
184
288
  await rm(realCandidate, {
185
289
  recursive: true,
186
290
  force: true
@@ -206,7 +310,7 @@ async function readManagedStagingEntryActivity(candidate, path, activityEntryLim
206
310
  if (entries.length > activityEntryLimit) return void 0;
207
311
  const statsParts = [`.:${stagingStatsSignature(directoryStats)}`, `${DOWNLOAD_STAGING_MARKER_NAME}:${stagingStatsSignature(markerStats)}`];
208
312
  let newestActivityMs = Math.max(stagingStatsActivityMs(directoryStats), stagingStatsActivityMs(markerStats));
209
- for (const entry of [...entries].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
313
+ for (const entry of [...entries].sort(compareDirectoryEntriesByName)) {
210
314
  const stats = await lstat(path.join(candidate, entry.name));
211
315
  statsParts.push(`${entry.name}:${stagingStatsSignature(stats)}`);
212
316
  newestActivityMs = Math.max(newestActivityMs, stagingStatsActivityMs(stats));
@@ -219,6 +323,12 @@ async function readManagedStagingEntryActivity(candidate, path, activityEntryLim
219
323
  return;
220
324
  }
221
325
  }
326
+ function compareDirectoryEntriesByName(a, b) {
327
+ if (a.name < b.name) return -1;
328
+ if (a.name > b.name) return 1;
329
+ /* v8 ignore next -- duplicate directory names cannot occur in a single readdir result */
330
+ return 0;
331
+ }
222
332
  function stagingStatsActivityMs(stats) {
223
333
  return stats.mtimeMs;
224
334
  }
@@ -246,7 +356,7 @@ async function forEachWithConcurrency(items, concurrency, fn) {
246
356
  while (index < items.length) {
247
357
  const item = items[index];
248
358
  index += 1;
249
- if (item !== void 0) await fn(item);
359
+ await fn(item);
250
360
  }
251
361
  });
252
362
  await Promise.all(workers);
@@ -260,7 +370,9 @@ exports.DOWNLOAD_STAGING_DIRECTORY_NAME = DOWNLOAD_STAGING_DIRECTORY_NAME;
260
370
  exports.DOWNLOAD_STAGING_MARKER_NAME = DOWNLOAD_STAGING_MARKER_NAME;
261
371
  exports.assertDownloadPathSameDevice = assertDownloadPathSameDevice;
262
372
  exports.createDownloadStagingDirectory = createDownloadStagingDirectory;
373
+ exports.downloadStagingTestHooks = downloadStagingTestHooks;
263
374
  exports.isDownloadStagingDirectorySegment = isDownloadStagingDirectorySegment;
264
375
  exports.isManagedDownloadStagingRoot = isManagedDownloadStagingRoot;
376
+ exports.removeDownloadStagingRootMarkerAndDirectory = removeDownloadStagingRootMarkerAndDirectory;
265
377
 
266
378
  //# sourceMappingURL=download-staging.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"download-staging.cjs","names":[],"sources":["../../src/sync/download-staging.ts"],"sourcesContent":["import { assertPathInsideRoot, hasErrorCode, noFollowFlag } from './path-safety.ts'\n\ntype DeviceStat = { readonly dev: number | bigint }\ntype DeviceStatFn = (path: string) => Promise<DeviceStat>\n\n/** @internal */\nexport const DOWNLOAD_STAGING_DIRECTORY_NAME = '.b2sdk-download-staging'\n/** @internal */\nexport const DOWNLOAD_STAGING_MARKER_NAME = '.b2sdk-staging-marker.partial'\n\nconst CANONICAL_DOWNLOAD_STAGING_DIRECTORY_NAME = canonicalLocalFilesystemSegment(\n DOWNLOAD_STAGING_DIRECTORY_NAME,\n)\nconst DOWNLOAD_STAGING_ENTRY_SUFFIX = '.download'\nconst STALE_DOWNLOAD_STAGING_AGE_MS = 24 * 60 * 60 * 1000\nconst MAX_STAGING_CLEANUP_CONCURRENCY = 8\nconst MAX_CLEANUP_WARNING_ENTRIES = 3\n/** @internal */\nexport const DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT = 1024\nconst reapedManagedDirectories = new Map<string, Promise<void>>()\n\ntype StagingActivitySnapshot = {\n readonly newestActivityMs: number\n readonly signature: string\n}\n\ntype CleanupWarning = {\n readonly entryName: string\n readonly operation: 'inspect' | 'remove'\n}\n\n/**\n * Checks whether a single path segment matches the SDK-managed staging directory name.\n * @param segment - Candidate path segment.\n *\n * @returns True when the segment is the reserved staging directory name under\n * local filesystem canonicalization.\n *\n * @internal\n */\nexport function isDownloadStagingDirectorySegment(segment: string | undefined): boolean {\n return (\n segment !== undefined &&\n canonicalLocalFilesystemSegment(segment) === CANONICAL_DOWNLOAD_STAGING_DIRECTORY_NAME\n )\n}\n\n/**\n * Creates a private SDK-managed staging directory under a local sync root.\n * @param rootRealPath - Resolved local sync root path.\n * @param path - Node path module used for platform-specific path operations.\n * @param randomUUID - UUID provider used to create unique staging entries.\n * @param statForDeviceCheck - Stat function used to verify filesystem devices.\n * @param beforeStagingMarkerWrite - Test hook called before marker creation.\n * @param activityEntryLimit - Maximum entry count inspected for stale staging activity.\n *\n * @returns The resolved staging directory path.\n *\n * @internal\n */\nexport async function createDownloadStagingDirectory(\n rootRealPath: string,\n path: typeof import('node:path'),\n randomUUID: () => string,\n statForDeviceCheck: DeviceStatFn,\n beforeStagingMarkerWrite?: (directory: string) => Promise<void> | void,\n activityEntryLimit = DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT,\n): Promise<string> {\n const { chmod, lstat, mkdir, readdir, realpath, rm } = await import('node:fs/promises')\n const managedDirectory = path.join(rootRealPath, DOWNLOAD_STAGING_DIRECTORY_NAME)\n try {\n await mkdir(managedDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE })\n } catch (err) {\n if (!hasErrorCode(err, 'EEXIST')) throw err\n }\n const managedStats = await lstat(managedDirectory)\n if (!managedStats.isDirectory()) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is not a directory`,\n )\n }\n const realManagedDirectory = await realpath(managedDirectory)\n assertPathInsideRoot(rootRealPath, realManagedDirectory, path)\n await assertDownloadPathSameDevice(\n rootRealPath,\n realManagedDirectory,\n statForDeviceCheck,\n 'unsafe local destination path: cannot stage download across filesystems',\n )\n\n if (!(await isManagedDownloadStagingRoot(realManagedDirectory))) {\n const entries = await readdir(realManagedDirectory)\n if (entries.length > 0 && !(await isManagedDownloadStagingRoot(realManagedDirectory))) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`,\n )\n }\n }\n await beforeStagingMarkerWrite?.(realManagedDirectory)\n await writeStagingMarker(realManagedDirectory, path)\n /* v8 ignore next -- best-effort chmod */\n await chmod(realManagedDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {})\n await reapStaleDownloadStagingDirectoriesOnce(\n realManagedDirectory,\n path,\n Date.now(),\n activityEntryLimit,\n )\n\n const stagingDirectory = path.join(\n realManagedDirectory,\n `${Date.now()}-${randomUUID()}${DOWNLOAD_STAGING_ENTRY_SUFFIX}`,\n )\n await mkdir(stagingDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE })\n /* v8 ignore next -- best-effort chmod */\n await chmod(stagingDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {})\n try {\n const realStagingDirectory = await realpath(stagingDirectory)\n assertPathInsideRoot(realManagedDirectory, realStagingDirectory, path)\n await assertDownloadPathSameDevice(\n rootRealPath,\n realStagingDirectory,\n statForDeviceCheck,\n 'unsafe local destination path: cannot stage download across filesystems',\n )\n await beforeStagingMarkerWrite?.(realStagingDirectory)\n await writeStagingMarker(realStagingDirectory, path)\n return realStagingDirectory\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n throw err\n }\n}\n\n/**\n * Returns true when a scan-root entry is an SDK-managed download staging root.\n * @param directory - Candidate directory to inspect.\n *\n * @returns True when the directory contains SDK staging markers.\n *\n * @internal\n */\nexport async function isManagedDownloadStagingRoot(directory: string): Promise<boolean> {\n const { lstat, readdir } = await import('node:fs/promises')\n const path = await import('node:path')\n\n try {\n const markerStats = await lstat(path.join(directory, DOWNLOAD_STAGING_MARKER_NAME))\n if (markerStats.isFile()) return true\n } catch (err) {\n if (!hasErrorCode(err, 'ENOENT')) return false\n }\n\n let entries: import('node:fs').Dirent[]\n try {\n entries = await readdir(directory, { withFileTypes: true })\n } catch {\n return false\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith(DOWNLOAD_STAGING_ENTRY_SUFFIX)) continue\n try {\n const markerStats = await lstat(\n path.join(directory, entry.name, DOWNLOAD_STAGING_MARKER_NAME),\n )\n if (markerStats.isFile()) return true\n } catch {\n // Keep checking siblings; one corrupt entry should not hide user content.\n }\n }\n return false\n}\n\nconst PRIVATE_DOWNLOAD_FILE_MODE = 0o600\nconst PRIVATE_DOWNLOAD_DIRECTORY_MODE = 0o700\n\nasync function writeStagingMarker(\n directory: string,\n path: typeof import('node:path'),\n): Promise<void> {\n const { constants } = await import('node:fs')\n const { lstat, open } = await import('node:fs/promises')\n const markerPath = path.join(directory, DOWNLOAD_STAGING_MARKER_NAME)\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(\n markerPath,\n constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag(constants),\n PRIVATE_DOWNLOAD_FILE_MODE,\n )\n /* v8 ignore next -- best-effort chmod */\n await handle.chmod(PRIVATE_DOWNLOAD_FILE_MODE).catch(() => {})\n } catch (err) {\n if (hasErrorCode(err, 'EEXIST') || hasErrorCode(err, 'ELOOP')) {\n const markerStats = await lstat(markerPath).catch(() => undefined)\n if (markerStats?.isFile() === true) return\n throw new Error('unsafe local destination path: staging marker is not a regular file')\n }\n throw err\n } finally {\n /* v8 ignore next -- best-effort close */\n await handle?.close().catch(() => {})\n }\n}\n\n/**\n * Verifies that a candidate path is on the same filesystem device as the root.\n * @param rootRealPath - Resolved local sync root path.\n * @param candidateRealPath - Resolved candidate path to compare.\n * @param statForDeviceCheck - Stat function used to read device IDs.\n * @param message - Error message used when devices differ.\n *\n * @internal\n */\nexport async function assertDownloadPathSameDevice(\n rootRealPath: string,\n candidateRealPath: string,\n statForDeviceCheck: DeviceStatFn,\n message: string,\n): Promise<void> {\n const [rootStats, candidateStats] = await Promise.all([\n statForDeviceCheck(rootRealPath),\n statForDeviceCheck(candidateRealPath),\n ])\n if (rootStats.dev !== candidateStats.dev) throw new Error(message)\n}\n\nasync function reapStaleDownloadStagingDirectoriesOnce(\n managedDirectory: string,\n path: typeof import('node:path'),\n nowMillis: number,\n activityEntryLimit: number,\n): Promise<void> {\n const previous = reapedManagedDirectories.get(managedDirectory)\n if (previous !== undefined) {\n await previous\n return\n }\n\n const next = reapStaleDownloadStagingDirectories(\n managedDirectory,\n path,\n nowMillis,\n activityEntryLimit,\n ).finally(() => {\n if (reapedManagedDirectories.get(managedDirectory) === next) {\n reapedManagedDirectories.delete(managedDirectory)\n }\n })\n reapedManagedDirectories.set(managedDirectory, next)\n await next\n}\n\nasync function reapStaleDownloadStagingDirectories(\n managedDirectory: string,\n path: typeof import('node:path'),\n nowMillis: number,\n activityEntryLimit: number,\n): Promise<void> {\n const { readdir, realpath, rm } = await import('node:fs/promises')\n let entries: import('node:fs').Dirent[]\n try {\n entries = await readdir(managedDirectory, { withFileTypes: true })\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) return\n emitCleanupWarning('failed to inspect B2 SDK download staging entries')\n return\n }\n\n const cleanupErrors: CleanupWarning[] = []\n await forEachWithConcurrency(entries, MAX_STAGING_CLEANUP_CONCURRENCY, async (entry) => {\n if (!entry.isDirectory() || !entry.name.endsWith(DOWNLOAD_STAGING_ENTRY_SUFFIX)) return\n const candidate = path.join(managedDirectory, entry.name)\n const activity = await readManagedStagingEntryActivity(candidate, path, activityEntryLimit)\n if (activity === undefined || !stagingActivityIsStale(activity, nowMillis)) return\n const realCandidate = await realpath(candidate).catch(() => {\n cleanupErrors.push({ entryName: entry.name, operation: 'inspect' })\n return undefined\n })\n if (realCandidate === undefined) return\n try {\n assertPathInsideRoot(managedDirectory, realCandidate, path)\n const latestActivity = await readManagedStagingEntryActivity(\n candidate,\n path,\n activityEntryLimit,\n )\n if (\n latestActivity === undefined ||\n latestActivity.signature !== activity.signature ||\n !stagingActivityIsStale(latestActivity, nowMillis)\n ) {\n return\n }\n await rm(realCandidate, { recursive: true, force: true })\n } catch {\n cleanupErrors.push({ entryName: entry.name, operation: 'remove' })\n }\n })\n\n if (cleanupErrors.length > 0) {\n const noun = cleanupErrors.length === 1 ? 'entry' : 'entries'\n emitCleanupWarning(\n `failed to reap ${cleanupErrors.length} stale B2 SDK download staging ${noun}: ${cleanupErrors\n .slice(0, MAX_CLEANUP_WARNING_ENTRIES)\n .map(formatCleanupWarning)\n .join('; ')}`,\n )\n }\n}\n\nasync function readManagedStagingEntryActivity(\n candidate: string,\n path: typeof import('node:path'),\n activityEntryLimit: number,\n): Promise<StagingActivitySnapshot | undefined> {\n const { lstat, readdir } = await import('node:fs/promises')\n\n try {\n const [directoryStats, markerStats] = await Promise.all([\n lstat(candidate),\n lstat(path.join(candidate, DOWNLOAD_STAGING_MARKER_NAME)),\n ])\n if (!directoryStats.isDirectory() || !markerStats.isFile()) return undefined\n\n const entries = await readdir(candidate, { withFileTypes: true })\n if (entries.length > activityEntryLimit) return undefined\n\n const statsParts = [\n `.:${stagingStatsSignature(directoryStats)}`,\n `${DOWNLOAD_STAGING_MARKER_NAME}:${stagingStatsSignature(markerStats)}`,\n ]\n let newestActivityMs = Math.max(\n stagingStatsActivityMs(directoryStats),\n stagingStatsActivityMs(markerStats),\n )\n\n for (const entry of [...entries].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0,\n )) {\n const stats = await lstat(path.join(candidate, entry.name))\n statsParts.push(`${entry.name}:${stagingStatsSignature(stats)}`)\n newestActivityMs = Math.max(newestActivityMs, stagingStatsActivityMs(stats))\n }\n\n return { newestActivityMs, signature: statsParts.join('|') }\n } catch {\n return undefined\n }\n}\n\nfunction stagingStatsActivityMs(stats: { readonly mtimeMs: number }) {\n return stats.mtimeMs\n}\n\nfunction stagingStatsSignature(stats: {\n readonly ctimeMs: number\n readonly dev: number | bigint\n readonly ino: number | bigint\n readonly mtimeMs: number\n readonly size: number\n}): string {\n return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`\n}\n\nfunction stagingActivityIsStale(activity: StagingActivitySnapshot, nowMillis: number): boolean {\n return nowMillis - activity.newestActivityMs >= STALE_DOWNLOAD_STAGING_AGE_MS\n}\n\nfunction formatCleanupWarning(error: CleanupWarning): string {\n return `${error.operation} ${safeWarningName(error.entryName)}`\n}\n\nfunction safeWarningName(name: string): string {\n const chars = Array.from(name)\n const bounded = chars.slice(0, 80).join('')\n const suffix = chars.length > 80 ? '...' : ''\n return JSON.stringify(`${bounded.replace(/[^A-Za-z0-9._-]/g, '?')}${suffix}`)\n}\n\nfunction canonicalLocalFilesystemSegment(segment: string): string {\n return segment.normalize('NFC').toLocaleLowerCase('en-US')\n}\n\nasync function forEachWithConcurrency<T>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T) => Promise<void>,\n): Promise<void> {\n let index = 0\n const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {\n while (index < items.length) {\n const item = items[index]\n index += 1\n if (item !== undefined) await fn(item)\n }\n })\n await Promise.all(workers)\n}\n\nfunction emitCleanupWarning(message: string): void {\n const processLike = (\n globalThis as {\n process?: {\n emitWarning?: (warning: string, options?: { readonly code?: string }) => void\n }\n }\n ).process\n processLike?.emitWarning?.(message, { code: 'B2SDK_DOWNLOAD_STAGING_CLEANUP_FAILED' })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"download-staging.cjs","names":[],"sources":["../../src/sync/download-staging.ts"],"sourcesContent":["import { localFilesystemErrorReason } from './filesystem-errors.ts'\nimport { assertPathInsideRoot, hasErrorCode, noFollowFlag } from './path-safety.ts'\n\ntype DeviceStat = { readonly dev: number | bigint }\ntype DeviceStatFn = (path: string) => Promise<DeviceStat>\n\n/** @internal */\nexport const DOWNLOAD_STAGING_DIRECTORY_NAME = '.b2sdk-download-staging'\n/** @internal */\nexport const DOWNLOAD_STAGING_MARKER_NAME = '.b2sdk-staging-marker.partial'\n\nconst CANONICAL_DOWNLOAD_STAGING_DIRECTORY_NAME = canonicalLocalFilesystemSegment(\n DOWNLOAD_STAGING_DIRECTORY_NAME,\n)\nconst DOWNLOAD_STAGING_ENTRY_SUFFIX = '.download'\nconst STALE_DOWNLOAD_STAGING_AGE_MS = 24 * 60 * 60 * 1000\nconst MAX_STAGING_CLEANUP_CONCURRENCY = 8\nconst MAX_CLEANUP_WARNING_ENTRIES = 3\n/** @internal */\nexport const DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT = 1024\nconst reapedManagedDirectories = new Map<string, Promise<void>>()\nconst activeManagedDirectoryCreations = new Map<string, number>()\n\n/** @internal */\nexport const downloadStagingTestHooks: {\n afterCleanupMarkerStat?: (managedDirectory: string) => Promise<void> | void\n beforeCleanupMarkerUnlink?: (markerPath: string) => Promise<void> | void\n} = {}\n\ntype StagingActivitySnapshot = {\n readonly newestActivityMs: number\n readonly signature: string\n}\n\ntype CleanupWarning = {\n readonly entryName: string\n readonly operation: 'inspect' | 'remove'\n}\n\n/** @internal */\nexport interface DownloadStagingDirectory {\n /** Resolved SDK-managed staging root shared by download runs under one sync root. */\n readonly managedDirectory: string\n /** Resolved per-run staging directory for one download. */\n readonly stagingDirectory: string\n}\n\n/**\n * Checks whether a single path segment matches the SDK-managed staging directory name.\n * @param segment - Candidate path segment.\n *\n * @returns True when the segment is the reserved staging directory name under\n * local filesystem canonicalization.\n *\n * @internal\n */\nexport function isDownloadStagingDirectorySegment(segment: string | undefined): boolean {\n return (\n segment !== undefined &&\n canonicalLocalFilesystemSegment(segment) === CANONICAL_DOWNLOAD_STAGING_DIRECTORY_NAME\n )\n}\n\n/**\n * Creates a private SDK-managed staging directory under a local sync root.\n * @param rootRealPath - Resolved local sync root path.\n * @param path - Node path module used for platform-specific path operations.\n * @param randomUUID - UUID provider used to create unique staging entries.\n * @param statForDeviceCheck - Stat function used to verify filesystem devices.\n * @param beforeStagingMarkerWrite - Test hook called before marker creation.\n * @param activityEntryLimit - Maximum entry count inspected for stale staging activity.\n * @param afterManagedStagingMarkerWrite - Test hook called after managed-root marker creation.\n *\n * @returns The resolved managed and per-run staging directory paths.\n *\n * @internal\n */\nexport async function createDownloadStagingDirectory(\n rootRealPath: string,\n path: typeof import('node:path'),\n randomUUID: () => string,\n statForDeviceCheck: DeviceStatFn,\n beforeStagingMarkerWrite?: (directory: string) => Promise<void> | void,\n activityEntryLimit = DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT,\n afterManagedStagingMarkerWrite?: (directory: string) => Promise<void> | void,\n): Promise<DownloadStagingDirectory> {\n const { chmod, lstat, mkdir, readdir, realpath, rm } = await import('node:fs/promises')\n const managedDirectory = path.join(rootRealPath, DOWNLOAD_STAGING_DIRECTORY_NAME)\n const releaseManagedDirectoryCreation = retainDownloadStagingRootCreation(managedDirectory)\n try {\n try {\n await mkdir(managedDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE })\n } catch (err) {\n if (!hasErrorCode(err, 'EEXIST')) throw err\n }\n const managedStats = await lstat(managedDirectory)\n if (!managedStats.isDirectory()) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is not a directory`,\n )\n }\n const realManagedDirectory = await realpath(managedDirectory)\n assertPathInsideRoot(rootRealPath, realManagedDirectory, path)\n await assertDownloadPathSameDevice(\n rootRealPath,\n realManagedDirectory,\n statForDeviceCheck,\n 'unsafe local destination path: cannot stage download across filesystems',\n )\n\n if (!(await isManagedDownloadStagingRoot(realManagedDirectory))) {\n const entries = await readdir(realManagedDirectory)\n if (entries.length > 0 && !(await isManagedDownloadStagingRoot(realManagedDirectory))) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`,\n )\n }\n }\n await beforeStagingMarkerWrite?.(realManagedDirectory)\n await writeStagingMarker(realManagedDirectory, path)\n await afterManagedStagingMarkerWrite?.(realManagedDirectory)\n /* v8 ignore next -- best-effort chmod */\n await chmod(realManagedDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {})\n await reapStaleDownloadStagingDirectoriesOnce(\n realManagedDirectory,\n path,\n Date.now(),\n activityEntryLimit,\n )\n\n const stagingDirectory = path.join(\n realManagedDirectory,\n `${Date.now()}-${randomUUID()}${DOWNLOAD_STAGING_ENTRY_SUFFIX}`,\n )\n await mkdir(stagingDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE })\n /* v8 ignore next -- best-effort chmod */\n await chmod(stagingDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {})\n try {\n const realStagingDirectory = await realpath(stagingDirectory)\n assertPathInsideRoot(realManagedDirectory, realStagingDirectory, path)\n await assertDownloadPathSameDevice(\n rootRealPath,\n realStagingDirectory,\n statForDeviceCheck,\n 'unsafe local destination path: cannot stage download across filesystems',\n )\n await beforeStagingMarkerWrite?.(realStagingDirectory)\n await writeStagingMarker(realStagingDirectory, path)\n return {\n managedDirectory: realManagedDirectory,\n stagingDirectory: realStagingDirectory,\n }\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n throw err\n }\n } finally {\n releaseManagedDirectoryCreation()\n }\n}\n\n/**\n * Removes the SDK root marker and the now-empty SDK-managed staging root\n * once all per-run `.download` entries are gone.\n * @param rootRealPath - Resolved local sync root path.\n * @param managedDirectory - Resolved SDK-managed staging root path.\n * @param path - Node path module used for platform-specific path operations.\n *\n * @internal\n */\nexport async function removeDownloadStagingRootMarkerAndDirectory(\n rootRealPath: string,\n managedDirectory: string,\n path: typeof import('node:path'),\n): Promise<void> {\n try {\n await removeDownloadStagingRootMarkerAndDirectoryCore(rootRealPath, managedDirectory, path)\n } catch (err) {\n emitCleanupWarning(\n `failed to remove B2 SDK download staging root: ${localFilesystemErrorReason(err)}`,\n )\n }\n}\n\nasync function removeDownloadStagingRootMarkerAndDirectoryCore(\n rootRealPath: string,\n managedDirectory: string,\n path: typeof import('node:path'),\n): Promise<void> {\n const { lstat, readdir, realpath, rmdir, unlink } = await import('node:fs/promises')\n if (downloadStagingRootCreationIsActive(managedDirectory)) return\n\n const managedStats = await lstat(managedDirectory).catch((err: unknown) => {\n if (hasErrorCode(err, 'ENOENT')) return undefined\n throw err\n })\n if (managedStats === undefined || !managedStats.isDirectory()) return\n\n const realManagedDirectory = await realpath(managedDirectory).catch((err: unknown) => {\n if (hasErrorCode(err, 'ENOENT')) return undefined\n throw err\n })\n if (realManagedDirectory === undefined) return\n assertPathInsideRoot(rootRealPath, realManagedDirectory, path)\n if (downloadStagingRootCreationIsActive(realManagedDirectory)) return\n\n const markerPath = path.join(realManagedDirectory, DOWNLOAD_STAGING_MARKER_NAME)\n const markerStats = await lstat(markerPath).catch((err: unknown) => {\n if (hasErrorCode(err, 'ENOENT')) return undefined\n throw err\n })\n if (markerStats?.isFile() !== true) return\n\n await downloadStagingTestHooks.afterCleanupMarkerStat?.(realManagedDirectory)\n const entries = await readdir(realManagedDirectory, { withFileTypes: true }).catch(\n (err: unknown) => {\n if (hasErrorCode(err, 'ENOENT')) return undefined\n throw err\n },\n )\n if (entries === undefined) return\n if (entries.length !== 1 || entries[0]?.name !== DOWNLOAD_STAGING_MARKER_NAME) return\n const currentRealManagedDirectory = await realpath(managedDirectory).catch(() => undefined)\n if (currentRealManagedDirectory !== realManagedDirectory) return\n if (downloadStagingRootCreationIsActive(realManagedDirectory)) return\n\n await downloadStagingTestHooks.beforeCleanupMarkerUnlink?.(markerPath)\n const markerRemoved = await unlink(markerPath)\n .then(() => true)\n .catch((err: unknown) => {\n if (hasErrorCode(err, 'ENOENT')) return false\n throw err\n })\n if (!markerRemoved) return\n try {\n await rmdir(realManagedDirectory)\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) return\n if (hasErrorCode(err, 'ENOTEMPTY') || hasErrorCode(err, 'EEXIST')) {\n await restoreDownloadStagingRootMarker(realManagedDirectory, path)\n return\n }\n await restoreDownloadStagingRootMarker(realManagedDirectory, path).catch((restoreErr) => {\n emitCleanupWarning(\n `failed to restore B2 SDK download staging marker: ${localFilesystemErrorReason(\n restoreErr,\n )}`,\n )\n })\n throw err\n }\n}\n\n/**\n * Returns true when a scan-root entry is an SDK-managed download staging root.\n * @param directory - Candidate directory to inspect.\n *\n * @returns True when the directory contains SDK staging markers.\n *\n * @internal\n */\nexport async function isManagedDownloadStagingRoot(directory: string): Promise<boolean> {\n const { lstat, readdir } = await import('node:fs/promises')\n const path = await import('node:path')\n\n try {\n const markerStats = await lstat(path.join(directory, DOWNLOAD_STAGING_MARKER_NAME))\n if (markerStats.isFile()) return true\n } catch (err) {\n if (!hasErrorCode(err, 'ENOENT')) return false\n }\n\n let entries: import('node:fs').Dirent[]\n try {\n entries = await readdir(directory, { withFileTypes: true })\n } catch {\n return false\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith(DOWNLOAD_STAGING_ENTRY_SUFFIX)) continue\n try {\n const markerStats = await lstat(\n path.join(directory, entry.name, DOWNLOAD_STAGING_MARKER_NAME),\n )\n if (markerStats.isFile()) return true\n } catch {\n // Keep checking siblings; one corrupt entry should not hide user content.\n }\n }\n return false\n}\n\nconst PRIVATE_DOWNLOAD_FILE_MODE = 0o600\nconst PRIVATE_DOWNLOAD_DIRECTORY_MODE = 0o700\n\nasync function writeStagingMarker(\n directory: string,\n path: typeof import('node:path'),\n): Promise<void> {\n const { constants } = await import('node:fs')\n const { lstat, open } = await import('node:fs/promises')\n const markerPath = path.join(directory, DOWNLOAD_STAGING_MARKER_NAME)\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(\n markerPath,\n constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag(constants),\n PRIVATE_DOWNLOAD_FILE_MODE,\n )\n /* v8 ignore next -- best-effort chmod */\n await handle.chmod(PRIVATE_DOWNLOAD_FILE_MODE).catch(() => {})\n } catch (err) {\n const markerAlreadyExists = hasErrorCode(err, 'EEXIST')\n if (\n markerAlreadyExists ||\n /* v8 ignore next -- ELOOP depends on platform-specific O_NOFOLLOW behavior */\n hasErrorCode(err, 'ELOOP')\n ) {\n const markerStats = await lstat(markerPath).catch(() => undefined)\n if (markerStats?.isFile() === true) return\n throw new Error('unsafe local destination path: staging marker is not a regular file')\n }\n throw err\n } finally {\n /* v8 ignore next -- best-effort close */\n await handle?.close().catch(() => {})\n }\n}\n\nfunction retainDownloadStagingRootCreation(managedDirectory: string): () => void {\n activeManagedDirectoryCreations.set(\n managedDirectory,\n (activeManagedDirectoryCreations.get(managedDirectory) ?? 0) + 1,\n )\n return () => {\n const count = activeManagedDirectoryCreations.get(managedDirectory)\n /* v8 ignore next -- releases are paired with retains in a single closure */\n if (count === undefined || count <= 1) {\n activeManagedDirectoryCreations.delete(managedDirectory)\n } else {\n activeManagedDirectoryCreations.set(managedDirectory, count - 1)\n }\n }\n}\n\nfunction downloadStagingRootCreationIsActive(managedDirectory: string): boolean {\n return activeManagedDirectoryCreations.has(managedDirectory)\n}\n\nasync function restoreDownloadStagingRootMarker(\n managedDirectory: string,\n path: typeof import('node:path'),\n): Promise<void> {\n const { lstat, realpath } = await import('node:fs/promises')\n const managedStats = await lstat(managedDirectory).catch(() => undefined)\n if (managedStats?.isDirectory() !== true) return\n const currentRealManagedDirectory = await realpath(managedDirectory).catch(() => undefined)\n if (currentRealManagedDirectory !== managedDirectory) return\n await writeStagingMarker(managedDirectory, path)\n}\n\n/**\n * Verifies that a candidate path is on the same filesystem device as the root.\n * @param rootRealPath - Resolved local sync root path.\n * @param candidateRealPath - Resolved candidate path to compare.\n * @param statForDeviceCheck - Stat function used to read device IDs.\n * @param message - Error message used when devices differ.\n *\n * @internal\n */\nexport async function assertDownloadPathSameDevice(\n rootRealPath: string,\n candidateRealPath: string,\n statForDeviceCheck: DeviceStatFn,\n message: string,\n): Promise<void> {\n const [rootStats, candidateStats] = await Promise.all([\n statForDeviceCheck(rootRealPath),\n statForDeviceCheck(candidateRealPath),\n ])\n if (rootStats.dev !== candidateStats.dev) throw new Error(message)\n}\n\nasync function reapStaleDownloadStagingDirectoriesOnce(\n managedDirectory: string,\n path: typeof import('node:path'),\n nowMillis: number,\n activityEntryLimit: number,\n): Promise<void> {\n const previous = reapedManagedDirectories.get(managedDirectory)\n if (previous !== undefined) {\n await previous\n return\n }\n\n const next = reapStaleDownloadStagingDirectories(\n managedDirectory,\n path,\n nowMillis,\n activityEntryLimit,\n ).finally(() => {\n /* v8 ignore next -- this map entry cannot be replaced until the current cleanup settles */\n if (reapedManagedDirectories.get(managedDirectory) === next) {\n reapedManagedDirectories.delete(managedDirectory)\n }\n })\n reapedManagedDirectories.set(managedDirectory, next)\n await next\n}\n\nasync function reapStaleDownloadStagingDirectories(\n managedDirectory: string,\n path: typeof import('node:path'),\n nowMillis: number,\n activityEntryLimit: number,\n): Promise<void> {\n const { readdir, realpath, rm } = await import('node:fs/promises')\n let entries: import('node:fs').Dirent[]\n try {\n entries = await readdir(managedDirectory, { withFileTypes: true })\n } catch (err) {\n /* v8 ignore next -- concurrent managed-root removal is a filesystem race without a stable hook */\n if (hasErrorCode(err, 'ENOENT')) return\n /* v8 ignore next -- platform-specific root permission failures cannot be simulated portably */\n emitCleanupWarning(\n `failed to inspect B2 SDK download staging entries: ${localFilesystemErrorReason(err)}`,\n )\n return\n }\n\n const cleanupErrors: CleanupWarning[] = []\n await forEachWithConcurrency(entries, MAX_STAGING_CLEANUP_CONCURRENCY, async (entry) => {\n if (!entry.isDirectory() || !entry.name.endsWith(DOWNLOAD_STAGING_ENTRY_SUFFIX)) return\n const candidate = path.join(managedDirectory, entry.name)\n const activity = await readManagedStagingEntryActivity(candidate, path, activityEntryLimit)\n if (activity === undefined || !stagingActivityIsStale(activity, nowMillis)) return\n const realCandidate = await realpath(candidate).catch(() => {\n cleanupErrors.push({ entryName: entry.name, operation: 'inspect' })\n return undefined\n })\n /* v8 ignore next -- requires a concurrent delete between activity read and realpath */\n if (realCandidate === undefined) return\n try {\n assertPathInsideRoot(managedDirectory, realCandidate, path)\n const latestActivity = await readManagedStagingEntryActivity(\n candidate,\n path,\n activityEntryLimit,\n )\n if (\n latestActivity === undefined ||\n latestActivity.signature !== activity.signature ||\n !stagingActivityIsStale(latestActivity, nowMillis)\n ) {\n /* v8 ignore next -- requires concurrent writes between the first and second activity reads */\n return\n }\n await rm(realCandidate, { recursive: true, force: true })\n } catch {\n cleanupErrors.push({ entryName: entry.name, operation: 'remove' })\n }\n })\n\n if (cleanupErrors.length > 0) {\n const noun = cleanupErrors.length === 1 ? 'entry' : 'entries'\n emitCleanupWarning(\n `failed to reap ${cleanupErrors.length} stale B2 SDK download staging ${noun}: ${cleanupErrors\n .slice(0, MAX_CLEANUP_WARNING_ENTRIES)\n .map(formatCleanupWarning)\n .join('; ')}`,\n )\n }\n}\n\nasync function readManagedStagingEntryActivity(\n candidate: string,\n path: typeof import('node:path'),\n activityEntryLimit: number,\n): Promise<StagingActivitySnapshot | undefined> {\n const { lstat, readdir } = await import('node:fs/promises')\n\n try {\n const [directoryStats, markerStats] = await Promise.all([\n lstat(candidate),\n lstat(path.join(candidate, DOWNLOAD_STAGING_MARKER_NAME)),\n ])\n if (!directoryStats.isDirectory() || !markerStats.isFile()) return undefined\n\n const entries = await readdir(candidate, { withFileTypes: true })\n if (entries.length > activityEntryLimit) return undefined\n\n const statsParts = [\n `.:${stagingStatsSignature(directoryStats)}`,\n `${DOWNLOAD_STAGING_MARKER_NAME}:${stagingStatsSignature(markerStats)}`,\n ]\n let newestActivityMs = Math.max(\n stagingStatsActivityMs(directoryStats),\n stagingStatsActivityMs(markerStats),\n )\n\n for (const entry of [...entries].sort(compareDirectoryEntriesByName)) {\n const stats = await lstat(path.join(candidate, entry.name))\n statsParts.push(`${entry.name}:${stagingStatsSignature(stats)}`)\n newestActivityMs = Math.max(newestActivityMs, stagingStatsActivityMs(stats))\n }\n\n return { newestActivityMs, signature: statsParts.join('|') }\n } catch {\n return undefined\n }\n}\n\nfunction compareDirectoryEntriesByName(\n a: { readonly name: string },\n b: { readonly name: string },\n): number {\n if (a.name < b.name) return -1\n if (a.name > b.name) return 1\n /* v8 ignore next -- duplicate directory names cannot occur in a single readdir result */\n return 0\n}\n\nfunction stagingStatsActivityMs(stats: { readonly mtimeMs: number }) {\n return stats.mtimeMs\n}\n\nfunction stagingStatsSignature(stats: {\n readonly ctimeMs: number\n readonly dev: number | bigint\n readonly ino: number | bigint\n readonly mtimeMs: number\n readonly size: number\n}): string {\n return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`\n}\n\nfunction stagingActivityIsStale(activity: StagingActivitySnapshot, nowMillis: number): boolean {\n return nowMillis - activity.newestActivityMs >= STALE_DOWNLOAD_STAGING_AGE_MS\n}\n\nfunction formatCleanupWarning(error: CleanupWarning): string {\n return `${error.operation} ${safeWarningName(error.entryName)}`\n}\n\nfunction safeWarningName(name: string): string {\n const chars = Array.from(name)\n const bounded = chars.slice(0, 80).join('')\n const suffix = chars.length > 80 ? '...' : ''\n return JSON.stringify(`${bounded.replace(/[^A-Za-z0-9._-]/g, '?')}${suffix}`)\n}\n\nfunction canonicalLocalFilesystemSegment(segment: string): string {\n return segment.normalize('NFC').toLocaleLowerCase('en-US')\n}\n\nasync function forEachWithConcurrency<T>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T) => Promise<void>,\n): Promise<void> {\n let index = 0\n const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {\n while (index < items.length) {\n const item = items[index] as T\n index += 1\n await fn(item)\n }\n })\n await Promise.all(workers)\n}\n\nfunction emitCleanupWarning(message: string): void {\n const processLike = (\n globalThis as {\n process?: {\n emitWarning?: (warning: string, options?: { readonly code?: string }) => void\n }\n }\n ).process\n processLike?.emitWarning?.(message, { code: 'B2SDK_DOWNLOAD_STAGING_CLEANUP_FAILED' })\n}\n"],"mappings":"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"}
@@ -8,6 +8,18 @@ export declare const DOWNLOAD_STAGING_DIRECTORY_NAME = ".b2sdk-download-staging"
8
8
  export declare const DOWNLOAD_STAGING_MARKER_NAME = ".b2sdk-staging-marker.partial";
9
9
  /** @internal */
10
10
  export declare const DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT = 1024;
11
+ /** @internal */
12
+ export declare const downloadStagingTestHooks: {
13
+ afterCleanupMarkerStat?: (managedDirectory: string) => Promise<void> | void;
14
+ beforeCleanupMarkerUnlink?: (markerPath: string) => Promise<void> | void;
15
+ };
16
+ /** @internal */
17
+ export interface DownloadStagingDirectory {
18
+ /** Resolved SDK-managed staging root shared by download runs under one sync root. */
19
+ readonly managedDirectory: string;
20
+ /** Resolved per-run staging directory for one download. */
21
+ readonly stagingDirectory: string;
22
+ }
11
23
  /**
12
24
  * Checks whether a single path segment matches the SDK-managed staging directory name.
13
25
  * @param segment - Candidate path segment.
@@ -26,12 +38,23 @@ export declare function isDownloadStagingDirectorySegment(segment: string | unde
26
38
  * @param statForDeviceCheck - Stat function used to verify filesystem devices.
27
39
  * @param beforeStagingMarkerWrite - Test hook called before marker creation.
28
40
  * @param activityEntryLimit - Maximum entry count inspected for stale staging activity.
41
+ * @param afterManagedStagingMarkerWrite - Test hook called after managed-root marker creation.
29
42
  *
30
- * @returns The resolved staging directory path.
43
+ * @returns The resolved managed and per-run staging directory paths.
44
+ *
45
+ * @internal
46
+ */
47
+ export declare function createDownloadStagingDirectory(rootRealPath: string, path: typeof import('node:path'), randomUUID: () => string, statForDeviceCheck: DeviceStatFn, beforeStagingMarkerWrite?: (directory: string) => Promise<void> | void, activityEntryLimit?: number, afterManagedStagingMarkerWrite?: (directory: string) => Promise<void> | void): Promise<DownloadStagingDirectory>;
48
+ /**
49
+ * Removes the SDK root marker and the now-empty SDK-managed staging root
50
+ * once all per-run `.download` entries are gone.
51
+ * @param rootRealPath - Resolved local sync root path.
52
+ * @param managedDirectory - Resolved SDK-managed staging root path.
53
+ * @param path - Node path module used for platform-specific path operations.
31
54
  *
32
55
  * @internal
33
56
  */
34
- export declare function createDownloadStagingDirectory(rootRealPath: string, path: typeof import('node:path'), randomUUID: () => string, statForDeviceCheck: DeviceStatFn, beforeStagingMarkerWrite?: (directory: string) => Promise<void> | void, activityEntryLimit?: number): Promise<string>;
57
+ export declare function removeDownloadStagingRootMarkerAndDirectory(rootRealPath: string, managedDirectory: string, path: typeof import('node:path')): Promise<void>;
35
58
  /**
36
59
  * Returns true when a scan-root entry is an SDK-managed download staging root.
37
60
  * @param directory - Candidate directory to inspect.
@@ -1 +1 @@
1
- {"version":3,"file":"download-staging.d.ts","sourceRoot":"","sources":["../../src/sync/download-staging.js"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAA;AACnD,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;AAEzD,gBAAgB;AAChB,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AACxE,gBAAgB;AAChB,eAAO,MAAM,4BAA4B,kCAAkC,CAAA;AAS3E,gBAAgB;AAChB,eAAO,MAAM,qCAAqC,OAAO,CAAA;AAazD;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAKtF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,8BAA8B,CAClD,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,cAAc,WAAW,CAAC,EAChC,UAAU,EAAE,MAAM,MAAM,EACxB,kBAAkB,EAAE,YAAY,EAChC,wBAAwB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EACtE,kBAAkB,SAAwC,GACzD,OAAO,CAAC,MAAM,CAAC,CAkEjB;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BtF;AAkCD;;;;;;;;GAQG;AACH,wBAAsB,4BAA4B,CAChD,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,kBAAkB,EAAE,YAAY,EAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf"}
1
+ {"version":3,"file":"download-staging.d.ts","sourceRoot":"","sources":["../../src/sync/download-staging.js"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAA;AACnD,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;AAEzD,gBAAgB;AAChB,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AACxE,gBAAgB;AAChB,eAAO,MAAM,4BAA4B,kCAAkC,CAAA;AAS3E,gBAAgB;AAChB,eAAO,MAAM,qCAAqC,OAAO,CAAA;AAIzD,gBAAgB;AAChB,eAAO,MAAM,wBAAwB,EAAE;IACrC,sBAAsB,CAAC,EAAE,CAAC,gBAAgB,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC3E,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACpE,CAAA;AAYN,gBAAgB;AAChB,MAAM,WAAW,wBAAwB;IACvC,qFAAqF;IACrF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAClC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAKtF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,8BAA8B,CAClD,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,cAAc,WAAW,CAAC,EAChC,UAAU,EAAE,MAAM,MAAM,EACxB,kBAAkB,EAAE,YAAY,EAChC,wBAAwB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EACtE,kBAAkB,SAAwC,EAC1D,8BAA8B,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAC3E,OAAO,CAAC,wBAAwB,CAAC,CA2EnC;AAED;;;;;;;;GAQG;AACH,wBAAsB,2CAA2C,CAC/D,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,cAAc,WAAW,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAQf;AAuED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BtF;AAuED;;;;;;;;GAQG;AACH,wBAAsB,4BAA4B,CAChD,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,kBAAkB,EAAE,YAAY,EAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf"}
@@ -8,6 +8,18 @@ export declare const DOWNLOAD_STAGING_DIRECTORY_NAME = ".b2sdk-download-staging"
8
8
  export declare const DOWNLOAD_STAGING_MARKER_NAME = ".b2sdk-staging-marker.partial";
9
9
  /** @internal */
10
10
  export declare const DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT = 1024;
11
+ /** @internal */
12
+ export declare const downloadStagingTestHooks: {
13
+ afterCleanupMarkerStat?: (managedDirectory: string) => Promise<void> | void;
14
+ beforeCleanupMarkerUnlink?: (markerPath: string) => Promise<void> | void;
15
+ };
16
+ /** @internal */
17
+ export interface DownloadStagingDirectory {
18
+ /** Resolved SDK-managed staging root shared by download runs under one sync root. */
19
+ readonly managedDirectory: string;
20
+ /** Resolved per-run staging directory for one download. */
21
+ readonly stagingDirectory: string;
22
+ }
11
23
  /**
12
24
  * Checks whether a single path segment matches the SDK-managed staging directory name.
13
25
  * @param segment - Candidate path segment.
@@ -26,12 +38,23 @@ export declare function isDownloadStagingDirectorySegment(segment: string | unde
26
38
  * @param statForDeviceCheck - Stat function used to verify filesystem devices.
27
39
  * @param beforeStagingMarkerWrite - Test hook called before marker creation.
28
40
  * @param activityEntryLimit - Maximum entry count inspected for stale staging activity.
41
+ * @param afterManagedStagingMarkerWrite - Test hook called after managed-root marker creation.
29
42
  *
30
- * @returns The resolved staging directory path.
43
+ * @returns The resolved managed and per-run staging directory paths.
44
+ *
45
+ * @internal
46
+ */
47
+ export declare function createDownloadStagingDirectory(rootRealPath: string, path: typeof import('node:path'), randomUUID: () => string, statForDeviceCheck: DeviceStatFn, beforeStagingMarkerWrite?: (directory: string) => Promise<void> | void, activityEntryLimit?: number, afterManagedStagingMarkerWrite?: (directory: string) => Promise<void> | void): Promise<DownloadStagingDirectory>;
48
+ /**
49
+ * Removes the SDK root marker and the now-empty SDK-managed staging root
50
+ * once all per-run `.download` entries are gone.
51
+ * @param rootRealPath - Resolved local sync root path.
52
+ * @param managedDirectory - Resolved SDK-managed staging root path.
53
+ * @param path - Node path module used for platform-specific path operations.
31
54
  *
32
55
  * @internal
33
56
  */
34
- export declare function createDownloadStagingDirectory(rootRealPath: string, path: typeof import('node:path'), randomUUID: () => string, statForDeviceCheck: DeviceStatFn, beforeStagingMarkerWrite?: (directory: string) => Promise<void> | void, activityEntryLimit?: number): Promise<string>;
57
+ export declare function removeDownloadStagingRootMarkerAndDirectory(rootRealPath: string, managedDirectory: string, path: typeof import('node:path')): Promise<void>;
35
58
  /**
36
59
  * Returns true when a scan-root entry is an SDK-managed download staging root.
37
60
  * @param directory - Candidate directory to inspect.
@@ -1 +1 @@
1
- {"version":3,"file":"download-staging.d.ts","sourceRoot":"","sources":["../../src/sync/download-staging.js"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAA;AACnD,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;AAEzD,gBAAgB;AAChB,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AACxE,gBAAgB;AAChB,eAAO,MAAM,4BAA4B,kCAAkC,CAAA;AAS3E,gBAAgB;AAChB,eAAO,MAAM,qCAAqC,OAAO,CAAA;AAazD;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAKtF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,8BAA8B,CAClD,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,cAAc,WAAW,CAAC,EAChC,UAAU,EAAE,MAAM,MAAM,EACxB,kBAAkB,EAAE,YAAY,EAChC,wBAAwB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EACtE,kBAAkB,SAAwC,GACzD,OAAO,CAAC,MAAM,CAAC,CAkEjB;AAED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BtF;AAkCD;;;;;;;;GAQG;AACH,wBAAsB,4BAA4B,CAChD,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,kBAAkB,EAAE,YAAY,EAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf"}
1
+ {"version":3,"file":"download-staging.d.ts","sourceRoot":"","sources":["../../src/sync/download-staging.js"],"names":[],"mappings":"AAGA,KAAK,UAAU,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAA;AACnD,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;AAEzD,gBAAgB;AAChB,eAAO,MAAM,+BAA+B,4BAA4B,CAAA;AACxE,gBAAgB;AAChB,eAAO,MAAM,4BAA4B,kCAAkC,CAAA;AAS3E,gBAAgB;AAChB,eAAO,MAAM,qCAAqC,OAAO,CAAA;AAIzD,gBAAgB;AAChB,eAAO,MAAM,wBAAwB,EAAE;IACrC,sBAAsB,CAAC,EAAE,CAAC,gBAAgB,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC3E,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACpE,CAAA;AAYN,gBAAgB;AAChB,MAAM,WAAW,wBAAwB;IACvC,qFAAqF;IACrF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;CAClC;AAED;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAKtF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,8BAA8B,CAClD,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,cAAc,WAAW,CAAC,EAChC,UAAU,EAAE,MAAM,MAAM,EACxB,kBAAkB,EAAE,YAAY,EAChC,wBAAwB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EACtE,kBAAkB,SAAwC,EAC1D,8BAA8B,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,GAC3E,OAAO,CAAC,wBAAwB,CAAC,CA2EnC;AAED;;;;;;;;GAQG;AACH,wBAAsB,2CAA2C,CAC/D,YAAY,EAAE,MAAM,EACpB,gBAAgB,EAAE,MAAM,EACxB,IAAI,EAAE,cAAc,WAAW,CAAC,GAC/B,OAAO,CAAC,IAAI,CAAC,CAQf;AAuED;;;;;;;GAOG;AACH,wBAAsB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BtF;AAuED;;;;;;;;GAQG;AACH,wBAAsB,4BAA4B,CAChD,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,kBAAkB,EAAE,YAAY,EAChC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAMf"}