@backblaze-labs/b2-sdk 0.2.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 (590) hide show
  1. package/CHANGELOG.md +86 -1
  2. package/README.md +38 -7
  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/file.cjs.map +1 -1
  6. package/dist/auth/reauth-coalescer.cjs +65 -0
  7. package/dist/auth/reauth-coalescer.cjs.map +1 -0
  8. package/dist/auth/reauth-coalescer.d.cts +34 -0
  9. package/dist/auth/reauth-coalescer.d.cts.map +1 -0
  10. package/dist/auth/reauth-coalescer.d.ts +34 -0
  11. package/dist/auth/reauth-coalescer.d.ts.map +1 -0
  12. package/dist/auth/reauth-coalescer.js +65 -0
  13. package/dist/auth/reauth-coalescer.js.map +1 -0
  14. package/dist/backup/client.cjs +206 -0
  15. package/dist/backup/client.cjs.map +1 -0
  16. package/dist/backup/client.d.cts +238 -0
  17. package/dist/backup/client.d.cts.map +1 -0
  18. package/dist/backup/client.d.ts +238 -0
  19. package/dist/backup/client.d.ts.map +1 -0
  20. package/dist/backup/client.js +206 -0
  21. package/dist/backup/client.js.map +1 -0
  22. package/dist/backup/index.cjs +15 -0
  23. package/dist/backup/index.d.cts +22 -0
  24. package/dist/backup/index.d.cts.map +1 -0
  25. package/dist/backup/index.d.ts +22 -0
  26. package/dist/backup/index.d.ts.map +1 -0
  27. package/dist/backup/index.js +7 -0
  28. package/dist/backup/raw.cjs +168 -0
  29. package/dist/backup/raw.cjs.map +1 -0
  30. package/dist/backup/raw.d.cts +122 -0
  31. package/dist/backup/raw.d.cts.map +1 -0
  32. package/dist/backup/raw.d.ts +122 -0
  33. package/dist/backup/raw.d.ts.map +1 -0
  34. package/dist/backup/raw.js +168 -0
  35. package/dist/backup/raw.js.map +1 -0
  36. package/dist/bucket-validation.cjs +111 -0
  37. package/dist/bucket-validation.cjs.map +1 -0
  38. package/dist/bucket-validation.d.cts +27 -0
  39. package/dist/bucket-validation.d.cts.map +1 -0
  40. package/dist/bucket-validation.d.ts +27 -0
  41. package/dist/bucket-validation.d.ts.map +1 -0
  42. package/dist/bucket-validation.js +109 -0
  43. package/dist/bucket-validation.js.map +1 -0
  44. package/dist/bucket.cjs +125 -74
  45. package/dist/bucket.cjs.map +1 -1
  46. package/dist/bucket.d.cts +156 -61
  47. package/dist/bucket.d.cts.map +1 -1
  48. package/dist/bucket.d.ts +156 -61
  49. package/dist/bucket.d.ts.map +1 -1
  50. package/dist/bucket.js +125 -74
  51. package/dist/bucket.js.map +1 -1
  52. package/dist/client.cjs +29 -8
  53. package/dist/client.cjs.map +1 -1
  54. package/dist/client.d.cts +30 -9
  55. package/dist/client.d.cts.map +1 -1
  56. package/dist/client.d.ts +30 -9
  57. package/dist/client.d.ts.map +1 -1
  58. package/dist/client.js +29 -8
  59. package/dist/client.js.map +1 -1
  60. package/dist/copy/large.cjs +5 -3
  61. package/dist/copy/large.cjs.map +1 -1
  62. package/dist/copy/large.d.cts.map +1 -1
  63. package/dist/copy/large.d.ts.map +1 -1
  64. package/dist/copy/large.js +5 -3
  65. package/dist/copy/large.js.map +1 -1
  66. package/dist/download/checksum.cjs +6 -2
  67. package/dist/download/checksum.cjs.map +1 -1
  68. package/dist/download/checksum.js +6 -2
  69. package/dist/download/checksum.js.map +1 -1
  70. package/dist/download/parallel.cjs +52 -15
  71. package/dist/download/parallel.cjs.map +1 -1
  72. package/dist/download/parallel.d.cts +8 -1
  73. package/dist/download/parallel.d.cts.map +1 -1
  74. package/dist/download/parallel.d.ts +8 -1
  75. package/dist/download/parallel.d.ts.map +1 -1
  76. package/dist/download/parallel.js +52 -15
  77. package/dist/download/parallel.js.map +1 -1
  78. package/dist/download/single.cjs +179 -19
  79. package/dist/download/single.cjs.map +1 -1
  80. package/dist/download/single.d.cts +10 -4
  81. package/dist/download/single.d.cts.map +1 -1
  82. package/dist/download/single.d.ts +10 -4
  83. package/dist/download/single.d.ts.map +1 -1
  84. package/dist/download/single.js +179 -19
  85. package/dist/download/single.js.map +1 -1
  86. package/dist/errors/index.cjs +196 -3
  87. package/dist/errors/index.cjs.map +1 -1
  88. package/dist/errors/index.d.cts +134 -0
  89. package/dist/errors/index.d.cts.map +1 -1
  90. package/dist/errors/index.d.ts +134 -0
  91. package/dist/errors/index.d.ts.map +1 -1
  92. package/dist/errors/index.js +183 -4
  93. package/dist/errors/index.js.map +1 -1
  94. package/dist/http/non-idempotent-mutations.cjs +38 -0
  95. package/dist/http/non-idempotent-mutations.cjs.map +1 -0
  96. package/dist/http/non-idempotent-mutations.d.cts +25 -0
  97. package/dist/http/non-idempotent-mutations.d.cts.map +1 -0
  98. package/dist/http/non-idempotent-mutations.d.ts +25 -0
  99. package/dist/http/non-idempotent-mutations.d.ts.map +1 -0
  100. package/dist/http/non-idempotent-mutations.js +35 -0
  101. package/dist/http/non-idempotent-mutations.js.map +1 -0
  102. package/dist/http/retry.cjs +1 -1
  103. package/dist/http/retry.cjs.map +1 -1
  104. package/dist/http/retry.js +1 -1
  105. package/dist/http/retry.js.map +1 -1
  106. package/dist/http/transport.cjs +62 -23
  107. package/dist/http/transport.cjs.map +1 -1
  108. package/dist/http/transport.d.cts +30 -3
  109. package/dist/http/transport.d.cts.map +1 -1
  110. package/dist/http/transport.d.ts +30 -3
  111. package/dist/http/transport.d.ts.map +1 -1
  112. package/dist/http/transport.js +60 -22
  113. package/dist/http/transport.js.map +1 -1
  114. package/dist/http/user-agent.cjs +12 -1
  115. package/dist/http/user-agent.cjs.map +1 -1
  116. package/dist/http/user-agent.d.cts +8 -0
  117. package/dist/http/user-agent.d.cts.map +1 -1
  118. package/dist/http/user-agent.d.ts +8 -0
  119. package/dist/http/user-agent.d.ts.map +1 -1
  120. package/dist/http/user-agent.js +13 -3
  121. package/dist/http/user-agent.js.map +1 -1
  122. package/dist/index.cjs +50 -2
  123. package/dist/index.d.cts +15 -12
  124. package/dist/index.d.cts.map +1 -1
  125. package/dist/index.d.ts +15 -12
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +13 -10
  128. package/dist/notifications/index.cjs +3 -0
  129. package/dist/notifications/index.d.cts +2 -0
  130. package/dist/notifications/index.d.cts.map +1 -1
  131. package/dist/notifications/index.d.ts +2 -0
  132. package/dist/notifications/index.d.ts.map +1 -1
  133. package/dist/notifications/index.js +2 -1
  134. package/dist/object.cjs +13 -2
  135. package/dist/object.cjs.map +1 -1
  136. package/dist/object.d.cts +11 -3
  137. package/dist/object.d.cts.map +1 -1
  138. package/dist/object.d.ts +11 -3
  139. package/dist/object.d.ts.map +1 -1
  140. package/dist/object.js +13 -2
  141. package/dist/object.js.map +1 -1
  142. package/dist/partner/account-info.d.cts +43 -0
  143. package/dist/partner/account-info.d.cts.map +1 -0
  144. package/dist/partner/account-info.d.ts +43 -0
  145. package/dist/partner/account-info.d.ts.map +1 -0
  146. package/dist/partner/auth-clone.cjs +64 -0
  147. package/dist/partner/auth-clone.cjs.map +1 -0
  148. package/dist/partner/auth-clone.d.cts +27 -0
  149. package/dist/partner/auth-clone.d.cts.map +1 -0
  150. package/dist/partner/auth-clone.d.ts +27 -0
  151. package/dist/partner/auth-clone.d.ts.map +1 -0
  152. package/dist/partner/auth-clone.js +63 -0
  153. package/dist/partner/auth-clone.js.map +1 -0
  154. package/dist/partner/auth-core.cjs +156 -0
  155. package/dist/partner/auth-core.cjs.map +1 -0
  156. package/dist/partner/auth-core.d.cts +111 -0
  157. package/dist/partner/auth-core.d.cts.map +1 -0
  158. package/dist/partner/auth-core.d.ts +111 -0
  159. package/dist/partner/auth-core.d.ts.map +1 -0
  160. package/dist/partner/auth-core.js +156 -0
  161. package/dist/partner/auth-core.js.map +1 -0
  162. package/dist/partner/auth-shape.cjs +36 -0
  163. package/dist/partner/auth-shape.cjs.map +1 -0
  164. package/dist/partner/auth-shape.d.cts +12 -0
  165. package/dist/partner/auth-shape.d.cts.map +1 -0
  166. package/dist/partner/auth-shape.d.ts +12 -0
  167. package/dist/partner/auth-shape.d.ts.map +1 -0
  168. package/dist/partner/auth-shape.js +36 -0
  169. package/dist/partner/auth-shape.js.map +1 -0
  170. package/dist/partner/client.cjs +270 -0
  171. package/dist/partner/client.cjs.map +1 -0
  172. package/dist/partner/client.d.cts +357 -0
  173. package/dist/partner/client.d.cts.map +1 -0
  174. package/dist/partner/client.d.ts +357 -0
  175. package/dist/partner/client.d.ts.map +1 -0
  176. package/dist/partner/client.js +270 -0
  177. package/dist/partner/client.js.map +1 -0
  178. package/dist/partner/endpoint-url.cjs +109 -0
  179. package/dist/partner/endpoint-url.cjs.map +1 -0
  180. package/dist/partner/endpoint-url.d.cts +74 -0
  181. package/dist/partner/endpoint-url.d.cts.map +1 -0
  182. package/dist/partner/endpoint-url.d.ts +74 -0
  183. package/dist/partner/endpoint-url.d.ts.map +1 -0
  184. package/dist/partner/endpoint-url.js +106 -0
  185. package/dist/partner/endpoint-url.js.map +1 -0
  186. package/dist/partner/in-memory.cjs +135 -0
  187. package/dist/partner/in-memory.cjs.map +1 -0
  188. package/dist/partner/in-memory.d.cts +99 -0
  189. package/dist/partner/in-memory.d.cts.map +1 -0
  190. package/dist/partner/in-memory.d.ts +99 -0
  191. package/dist/partner/in-memory.d.ts.map +1 -0
  192. package/dist/partner/in-memory.js +135 -0
  193. package/dist/partner/in-memory.js.map +1 -0
  194. package/dist/partner/index.cjs +27 -0
  195. package/dist/partner/index.d.cts +32 -0
  196. package/dist/partner/index.d.cts.map +1 -0
  197. package/dist/partner/index.d.ts +32 -0
  198. package/dist/partner/index.d.ts.map +1 -0
  199. package/dist/partner/index.js +9 -0
  200. package/dist/partner/raw.cjs +400 -0
  201. package/dist/partner/raw.cjs.map +1 -0
  202. package/dist/partner/raw.d.cts +220 -0
  203. package/dist/partner/raw.d.cts.map +1 -0
  204. package/dist/partner/raw.d.ts +220 -0
  205. package/dist/partner/raw.d.ts.map +1 -0
  206. package/dist/partner/raw.js +398 -0
  207. package/dist/partner/raw.js.map +1 -0
  208. package/dist/partner/redaction-placeholders.cjs +10 -0
  209. package/dist/partner/redaction-placeholders.cjs.map +1 -0
  210. package/dist/partner/redaction-placeholders.d.cts +5 -0
  211. package/dist/partner/redaction-placeholders.d.cts.map +1 -0
  212. package/dist/partner/redaction-placeholders.d.ts +5 -0
  213. package/dist/partner/redaction-placeholders.d.ts.map +1 -0
  214. package/dist/partner/redaction-placeholders.js +9 -0
  215. package/dist/partner/redaction-placeholders.js.map +1 -0
  216. package/dist/partner/redaction.cjs +292 -0
  217. package/dist/partner/redaction.cjs.map +1 -0
  218. package/dist/partner/redaction.d.cts +158 -0
  219. package/dist/partner/redaction.d.cts.map +1 -0
  220. package/dist/partner/redaction.d.ts +158 -0
  221. package/dist/partner/redaction.d.ts.map +1 -0
  222. package/dist/partner/redaction.js +281 -0
  223. package/dist/partner/redaction.js.map +1 -0
  224. package/dist/raw/encoding.cjs +29 -10
  225. package/dist/raw/encoding.cjs.map +1 -1
  226. package/dist/raw/encoding.d.cts +3 -2
  227. package/dist/raw/encoding.d.cts.map +1 -1
  228. package/dist/raw/encoding.d.ts +3 -2
  229. package/dist/raw/encoding.d.ts.map +1 -1
  230. package/dist/raw/encoding.js +29 -10
  231. package/dist/raw/encoding.js.map +1 -1
  232. package/dist/raw/index.cjs +131 -44
  233. package/dist/raw/index.cjs.map +1 -1
  234. package/dist/raw/index.d.cts +21 -15
  235. package/dist/raw/index.d.cts.map +1 -1
  236. package/dist/raw/index.d.ts +21 -15
  237. package/dist/raw/index.d.ts.map +1 -1
  238. package/dist/raw/index.js +114 -45
  239. package/dist/raw/index.js.map +1 -1
  240. package/dist/raw/url.cjs +111 -0
  241. package/dist/raw/url.cjs.map +1 -0
  242. package/dist/raw/url.d.cts +41 -0
  243. package/dist/raw/url.d.cts.map +1 -0
  244. package/dist/raw/url.d.ts +41 -0
  245. package/dist/raw/url.d.ts.map +1 -0
  246. package/dist/raw/url.js +110 -0
  247. package/dist/raw/url.js.map +1 -0
  248. package/dist/s3/sigv4.cjs +15 -10
  249. package/dist/s3/sigv4.cjs.map +1 -1
  250. package/dist/s3/sigv4.js +15 -10
  251. package/dist/s3/sigv4.js.map +1 -1
  252. package/dist/simulator/index.cjs +1579 -257
  253. package/dist/simulator/index.cjs.map +1 -1
  254. package/dist/simulator/index.d.cts +124 -11
  255. package/dist/simulator/index.d.cts.map +1 -1
  256. package/dist/simulator/index.d.ts +124 -11
  257. package/dist/simulator/index.d.ts.map +1 -1
  258. package/dist/simulator/index.js +1568 -262
  259. package/dist/simulator/index.js.map +1 -1
  260. package/dist/simulator/partner-capabilities.cjs +77 -0
  261. package/dist/simulator/partner-capabilities.cjs.map +1 -0
  262. package/dist/simulator/partner-capabilities.d.cts +53 -0
  263. package/dist/simulator/partner-capabilities.d.cts.map +1 -0
  264. package/dist/simulator/partner-capabilities.d.ts +53 -0
  265. package/dist/simulator/partner-capabilities.d.ts.map +1 -0
  266. package/dist/simulator/partner-capabilities.js +73 -0
  267. package/dist/simulator/partner-capabilities.js.map +1 -0
  268. package/dist/simulator/partner.cjs +713 -0
  269. package/dist/simulator/partner.cjs.map +1 -0
  270. package/dist/simulator/partner.d.cts +186 -0
  271. package/dist/simulator/partner.d.cts.map +1 -0
  272. package/dist/simulator/partner.d.ts +186 -0
  273. package/dist/simulator/partner.d.ts.map +1 -0
  274. package/dist/simulator/partner.js +713 -0
  275. package/dist/simulator/partner.js.map +1 -0
  276. package/dist/simulator/validation.cjs +732 -25
  277. package/dist/simulator/validation.cjs.map +1 -1
  278. package/dist/simulator/validation.d.cts +145 -6
  279. package/dist/simulator/validation.d.cts.map +1 -1
  280. package/dist/simulator/validation.d.ts +145 -6
  281. package/dist/simulator/validation.d.ts.map +1 -1
  282. package/dist/simulator/validation.js +705 -26
  283. package/dist/simulator/validation.js.map +1 -1
  284. package/dist/streams/file-source.cjs +2 -2
  285. package/dist/streams/file-source.cjs.map +1 -1
  286. package/dist/streams/file-source.js +2 -2
  287. package/dist/streams/file-source.js.map +1 -1
  288. package/dist/streams/hash.cjs +246 -13
  289. package/dist/streams/hash.cjs.map +1 -1
  290. package/dist/streams/hash.d.cts +37 -12
  291. package/dist/streams/hash.d.cts.map +1 -1
  292. package/dist/streams/hash.d.ts +37 -12
  293. package/dist/streams/hash.d.ts.map +1 -1
  294. package/dist/streams/hash.js +246 -15
  295. package/dist/streams/hash.js.map +1 -1
  296. package/dist/streams/index.cjs +3 -1
  297. package/dist/streams/index.d.cts +1 -1
  298. package/dist/streams/index.d.cts.map +1 -1
  299. package/dist/streams/index.d.ts +1 -1
  300. package/dist/streams/index.d.ts.map +1 -1
  301. package/dist/streams/index.js +3 -3
  302. package/dist/streams/progress.cjs +63 -4
  303. package/dist/streams/progress.cjs.map +1 -1
  304. package/dist/streams/progress.d.cts +28 -2
  305. package/dist/streams/progress.d.cts.map +1 -1
  306. package/dist/streams/progress.d.ts +28 -2
  307. package/dist/streams/progress.d.ts.map +1 -1
  308. package/dist/streams/progress.js +63 -4
  309. package/dist/streams/progress.js.map +1 -1
  310. package/dist/streams/source.cjs +2 -1
  311. package/dist/streams/source.cjs.map +1 -1
  312. package/dist/streams/source.js +2 -1
  313. package/dist/streams/source.js.map +1 -1
  314. package/dist/sync/b2-sha1-reader.cjs +6 -2
  315. package/dist/sync/b2-sha1-reader.cjs.map +1 -1
  316. package/dist/sync/b2-sha1-reader.d.cts.map +1 -1
  317. package/dist/sync/b2-sha1-reader.d.ts.map +1 -1
  318. package/dist/sync/b2-sha1-reader.js +6 -2
  319. package/dist/sync/b2-sha1-reader.js.map +1 -1
  320. package/dist/sync/download-staging.cjs +156 -43
  321. package/dist/sync/download-staging.cjs.map +1 -1
  322. package/dist/sync/download-staging.d.cts +26 -2
  323. package/dist/sync/download-staging.d.cts.map +1 -1
  324. package/dist/sync/download-staging.d.ts +26 -2
  325. package/dist/sync/download-staging.d.ts.map +1 -1
  326. package/dist/sync/download-staging.js +155 -44
  327. package/dist/sync/download-staging.js.map +1 -1
  328. package/dist/sync/filters.cjs.map +1 -1
  329. package/dist/sync/filters.js.map +1 -1
  330. package/dist/sync/index.d.cts +1 -1
  331. package/dist/sync/index.d.cts.map +1 -1
  332. package/dist/sync/index.d.ts +1 -1
  333. package/dist/sync/index.d.ts.map +1 -1
  334. package/dist/sync/local-file-io.cjs +15 -6
  335. package/dist/sync/local-file-io.cjs.map +1 -1
  336. package/dist/sync/local-file-io.d.cts +3 -0
  337. package/dist/sync/local-file-io.d.cts.map +1 -1
  338. package/dist/sync/local-file-io.d.ts +3 -0
  339. package/dist/sync/local-file-io.d.ts.map +1 -1
  340. package/dist/sync/local-file-io.js +15 -6
  341. package/dist/sync/local-file-io.js.map +1 -1
  342. package/dist/sync/local-sha1.cjs +6 -15
  343. package/dist/sync/local-sha1.cjs.map +1 -1
  344. package/dist/sync/local-sha1.d.cts +0 -8
  345. package/dist/sync/local-sha1.d.cts.map +1 -1
  346. package/dist/sync/local-sha1.d.ts +0 -8
  347. package/dist/sync/local-sha1.d.ts.map +1 -1
  348. package/dist/sync/local-sha1.js +7 -15
  349. package/dist/sync/local-sha1.js.map +1 -1
  350. package/dist/sync/pairing.cjs +1 -0
  351. package/dist/sync/pairing.cjs.map +1 -1
  352. package/dist/sync/pairing.js +1 -0
  353. package/dist/sync/pairing.js.map +1 -1
  354. package/dist/sync/policies/compare.cjs +5 -3
  355. package/dist/sync/policies/compare.cjs.map +1 -1
  356. package/dist/sync/policies/compare.d.cts.map +1 -1
  357. package/dist/sync/policies/compare.d.ts.map +1 -1
  358. package/dist/sync/policies/compare.js +6 -4
  359. package/dist/sync/policies/compare.js.map +1 -1
  360. package/dist/sync/policies/index.cjs +4 -10
  361. package/dist/sync/policies/index.cjs.map +1 -1
  362. package/dist/sync/policies/index.js +4 -10
  363. package/dist/sync/policies/index.js.map +1 -1
  364. package/dist/sync/scanners/b2.cjs +4 -2
  365. package/dist/sync/scanners/b2.cjs.map +1 -1
  366. package/dist/sync/scanners/b2.d.cts.map +1 -1
  367. package/dist/sync/scanners/b2.d.ts.map +1 -1
  368. package/dist/sync/scanners/b2.js +4 -2
  369. package/dist/sync/scanners/b2.js.map +1 -1
  370. package/dist/sync/scanners/local.cjs +150 -33
  371. package/dist/sync/scanners/local.cjs.map +1 -1
  372. package/dist/sync/scanners/local.d.cts +6 -0
  373. package/dist/sync/scanners/local.d.cts.map +1 -1
  374. package/dist/sync/scanners/local.d.ts +6 -0
  375. package/dist/sync/scanners/local.d.ts.map +1 -1
  376. package/dist/sync/scanners/local.js +150 -33
  377. package/dist/sync/scanners/local.js.map +1 -1
  378. package/dist/sync/synchronizer.cjs +79 -31
  379. package/dist/sync/synchronizer.cjs.map +1 -1
  380. package/dist/sync/synchronizer.d.cts +1 -0
  381. package/dist/sync/synchronizer.d.cts.map +1 -1
  382. package/dist/sync/synchronizer.d.ts +1 -0
  383. package/dist/sync/synchronizer.d.ts.map +1 -1
  384. package/dist/sync/synchronizer.js +79 -31
  385. package/dist/sync/synchronizer.js.map +1 -1
  386. package/dist/sync/types.d.cts +19 -3
  387. package/dist/sync/types.d.cts.map +1 -1
  388. package/dist/sync/types.d.ts +19 -3
  389. package/dist/sync/types.d.ts.map +1 -1
  390. package/dist/types/auth.cjs +4 -0
  391. package/dist/types/auth.cjs.map +1 -1
  392. package/dist/types/auth.d.cts +4 -0
  393. package/dist/types/auth.d.cts.map +1 -1
  394. package/dist/types/auth.d.ts +4 -0
  395. package/dist/types/auth.d.ts.map +1 -1
  396. package/dist/types/auth.js +4 -0
  397. package/dist/types/auth.js.map +1 -1
  398. package/dist/types/backup.d.cts +85 -0
  399. package/dist/types/backup.d.cts.map +1 -0
  400. package/dist/types/backup.d.ts +85 -0
  401. package/dist/types/backup.d.ts.map +1 -0
  402. package/dist/types/bucket.cjs +81 -11
  403. package/dist/types/bucket.cjs.map +1 -1
  404. package/dist/types/bucket.d.cts +194 -41
  405. package/dist/types/bucket.d.cts.map +1 -1
  406. package/dist/types/bucket.d.ts +194 -41
  407. package/dist/types/bucket.d.ts.map +1 -1
  408. package/dist/types/bucket.js +67 -12
  409. package/dist/types/bucket.js.map +1 -1
  410. package/dist/types/download.cjs +62 -0
  411. package/dist/types/download.cjs.map +1 -0
  412. package/dist/types/download.d.cts +100 -16
  413. package/dist/types/download.d.cts.map +1 -1
  414. package/dist/types/download.d.ts +100 -16
  415. package/dist/types/download.d.ts.map +1 -1
  416. package/dist/types/download.js +61 -0
  417. package/dist/types/download.js.map +1 -0
  418. package/dist/types/encryption.cjs +76 -229
  419. package/dist/types/encryption.cjs.map +1 -1
  420. package/dist/types/encryption.d.cts +48 -6
  421. package/dist/types/encryption.d.cts.map +1 -1
  422. package/dist/types/encryption.d.ts +48 -6
  423. package/dist/types/encryption.d.ts.map +1 -1
  424. package/dist/types/encryption.js +74 -229
  425. package/dist/types/encryption.js.map +1 -1
  426. package/dist/types/errors.cjs +9 -0
  427. package/dist/types/errors.cjs.map +1 -1
  428. package/dist/types/errors.d.cts +1 -1
  429. package/dist/types/errors.d.cts.map +1 -1
  430. package/dist/types/errors.d.ts +1 -1
  431. package/dist/types/errors.d.ts.map +1 -1
  432. package/dist/types/errors.js +9 -0
  433. package/dist/types/errors.js.map +1 -1
  434. package/dist/types/file.cjs +3 -0
  435. package/dist/types/file.cjs.map +1 -1
  436. package/dist/types/file.d.cts +140 -18
  437. package/dist/types/file.d.cts.map +1 -1
  438. package/dist/types/file.d.ts +140 -18
  439. package/dist/types/file.d.ts.map +1 -1
  440. package/dist/types/file.js +3 -1
  441. package/dist/types/file.js.map +1 -1
  442. package/dist/types/ids.cjs +30 -0
  443. package/dist/types/ids.cjs.map +1 -1
  444. package/dist/types/ids.d.cts +32 -0
  445. package/dist/types/ids.d.cts.map +1 -1
  446. package/dist/types/ids.d.ts +32 -0
  447. package/dist/types/ids.d.ts.map +1 -1
  448. package/dist/types/ids.js +28 -1
  449. package/dist/types/ids.js.map +1 -1
  450. package/dist/types/index.d.cts +16 -12
  451. package/dist/types/index.d.cts.map +1 -1
  452. package/dist/types/index.d.ts +16 -12
  453. package/dist/types/index.d.ts.map +1 -1
  454. package/dist/types/key.d.cts +4 -3
  455. package/dist/types/key.d.cts.map +1 -1
  456. package/dist/types/key.d.ts +4 -3
  457. package/dist/types/key.d.ts.map +1 -1
  458. package/dist/types/lock.cjs.map +1 -1
  459. package/dist/types/lock.d.cts +14 -0
  460. package/dist/types/lock.d.cts.map +1 -1
  461. package/dist/types/lock.d.ts +14 -0
  462. package/dist/types/lock.d.ts.map +1 -1
  463. package/dist/types/lock.js.map +1 -1
  464. package/dist/types/notifications.cjs +31 -1
  465. package/dist/types/notifications.cjs.map +1 -1
  466. package/dist/types/notifications.d.cts +31 -4
  467. package/dist/types/notifications.d.cts.map +1 -1
  468. package/dist/types/notifications.d.ts +31 -4
  469. package/dist/types/notifications.d.ts.map +1 -1
  470. package/dist/types/notifications.js +30 -2
  471. package/dist/types/notifications.js.map +1 -1
  472. package/dist/types/partner.cjs +32 -0
  473. package/dist/types/partner.cjs.map +1 -0
  474. package/dist/types/partner.d.cts +386 -0
  475. package/dist/types/partner.d.cts.map +1 -0
  476. package/dist/types/partner.d.ts +386 -0
  477. package/dist/types/partner.d.ts.map +1 -0
  478. package/dist/types/partner.js +29 -0
  479. package/dist/types/partner.js.map +1 -0
  480. package/dist/types/upload.d.cts +35 -18
  481. package/dist/types/upload.d.cts.map +1 -1
  482. package/dist/types/upload.d.ts +35 -18
  483. package/dist/types/upload.d.ts.map +1 -1
  484. package/dist/upload/abort-scope.cjs +0 -45
  485. package/dist/upload/abort-scope.cjs.map +1 -1
  486. package/dist/upload/abort-scope.d.cts +0 -22
  487. package/dist/upload/abort-scope.d.cts.map +1 -1
  488. package/dist/upload/abort-scope.d.ts +0 -22
  489. package/dist/upload/abort-scope.d.ts.map +1 -1
  490. package/dist/upload/abort-scope.js +1 -44
  491. package/dist/upload/abort-scope.js.map +1 -1
  492. package/dist/upload/finish.cjs +3 -8
  493. package/dist/upload/finish.cjs.map +1 -1
  494. package/dist/upload/finish.d.cts.map +1 -1
  495. package/dist/upload/finish.d.ts.map +1 -1
  496. package/dist/upload/finish.js +1 -6
  497. package/dist/upload/finish.js.map +1 -1
  498. package/dist/upload/large.cjs +15 -8
  499. package/dist/upload/large.cjs.map +1 -1
  500. package/dist/upload/large.d.cts +31 -8
  501. package/dist/upload/large.d.cts.map +1 -1
  502. package/dist/upload/large.d.ts +31 -8
  503. package/dist/upload/large.d.ts.map +1 -1
  504. package/dist/upload/large.js +15 -8
  505. package/dist/upload/large.js.map +1 -1
  506. package/dist/upload/options.cjs.map +1 -1
  507. package/dist/upload/options.d.cts +24 -0
  508. package/dist/upload/options.d.cts.map +1 -1
  509. package/dist/upload/options.d.ts +24 -0
  510. package/dist/upload/options.d.ts.map +1 -1
  511. package/dist/upload/options.js.map +1 -1
  512. package/dist/upload/resume.cjs +6 -4
  513. package/dist/upload/resume.cjs.map +1 -1
  514. package/dist/upload/resume.d.cts +9 -5
  515. package/dist/upload/resume.d.cts.map +1 -1
  516. package/dist/upload/resume.d.ts +9 -5
  517. package/dist/upload/resume.d.ts.map +1 -1
  518. package/dist/upload/resume.js +6 -4
  519. package/dist/upload/resume.js.map +1 -1
  520. package/dist/upload/retry.cjs +25 -13
  521. package/dist/upload/retry.cjs.map +1 -1
  522. package/dist/upload/retry.d.cts.map +1 -1
  523. package/dist/upload/retry.d.ts.map +1 -1
  524. package/dist/upload/retry.js +25 -13
  525. package/dist/upload/retry.js.map +1 -1
  526. package/dist/upload/single.cjs +35 -30
  527. package/dist/upload/single.cjs.map +1 -1
  528. package/dist/upload/single.d.cts +12 -0
  529. package/dist/upload/single.d.cts.map +1 -1
  530. package/dist/upload/single.d.ts +12 -0
  531. package/dist/upload/single.d.ts.map +1 -1
  532. package/dist/upload/single.js +35 -30
  533. package/dist/upload/single.js.map +1 -1
  534. package/dist/upload/stream.cjs +18 -5
  535. package/dist/upload/stream.cjs.map +1 -1
  536. package/dist/upload/stream.d.cts +12 -0
  537. package/dist/upload/stream.d.cts.map +1 -1
  538. package/dist/upload/stream.d.ts +12 -0
  539. package/dist/upload/stream.d.ts.map +1 -1
  540. package/dist/upload/stream.js +15 -2
  541. package/dist/upload/stream.js.map +1 -1
  542. package/dist/util/abort.cjs +114 -0
  543. package/dist/util/abort.cjs.map +1 -0
  544. package/dist/util/abort.d.cts +65 -0
  545. package/dist/util/abort.d.cts.map +1 -0
  546. package/dist/util/abort.d.ts +65 -0
  547. package/dist/util/abort.d.ts.map +1 -0
  548. package/dist/util/abort.js +109 -0
  549. package/dist/util/abort.js.map +1 -0
  550. package/dist/util/defaults.cjs +3 -0
  551. package/dist/util/defaults.cjs.map +1 -1
  552. package/dist/util/defaults.d.cts +2 -0
  553. package/dist/util/defaults.d.cts.map +1 -1
  554. package/dist/util/defaults.d.ts +2 -0
  555. package/dist/util/defaults.d.ts.map +1 -1
  556. package/dist/util/defaults.js +3 -1
  557. package/dist/util/defaults.js.map +1 -1
  558. package/dist/util/md5.cjs +211 -0
  559. package/dist/util/md5.cjs.map +1 -0
  560. package/dist/util/md5.d.cts +43 -0
  561. package/dist/util/md5.d.cts.map +1 -0
  562. package/dist/util/md5.d.ts +43 -0
  563. package/dist/util/md5.d.ts.map +1 -0
  564. package/dist/util/md5.js +209 -0
  565. package/dist/util/md5.js.map +1 -0
  566. package/dist/util/normalize.cjs +5 -5
  567. package/dist/util/normalize.cjs.map +1 -1
  568. package/dist/util/normalize.d.cts +7 -7
  569. package/dist/util/normalize.d.cts.map +1 -1
  570. package/dist/util/normalize.d.ts +7 -7
  571. package/dist/util/normalize.d.ts.map +1 -1
  572. package/dist/util/normalize.js +5 -5
  573. package/dist/util/normalize.js.map +1 -1
  574. package/dist/util/paginator.cjs +7 -1
  575. package/dist/util/paginator.cjs.map +1 -1
  576. package/dist/util/paginator.d.cts +3 -1
  577. package/dist/util/paginator.d.cts.map +1 -1
  578. package/dist/util/paginator.d.ts +3 -1
  579. package/dist/util/paginator.d.ts.map +1 -1
  580. package/dist/util/paginator.js +7 -1
  581. package/dist/util/paginator.js.map +1 -1
  582. package/dist/version.cjs +56 -4
  583. package/dist/version.cjs.map +1 -1
  584. package/dist/version.d.cts +53 -4
  585. package/dist/version.d.cts.map +1 -1
  586. package/dist/version.d.ts +53 -4
  587. package/dist/version.d.ts.map +1 -1
  588. package/dist/version.js +52 -5
  589. package/dist/version.js.map +1 -1
  590. package/package.json +47 -18
@@ -1,3 +1,4 @@
1
+ import { localFilesystemErrorReason } from "./filesystem-errors.js";
1
2
  import { assertPathInsideRoot, hasErrorCode, noFollowFlag } from "./path-safety.js";
2
3
  //#region src/sync/download-staging.ts
3
4
  /** @internal */
@@ -6,12 +7,15 @@ var DOWNLOAD_STAGING_DIRECTORY_NAME = ".b2sdk-download-staging";
6
7
  var DOWNLOAD_STAGING_MARKER_NAME = ".b2sdk-staging-marker.partial";
7
8
  var CANONICAL_DOWNLOAD_STAGING_DIRECTORY_NAME = canonicalLocalFilesystemSegment(DOWNLOAD_STAGING_DIRECTORY_NAME);
8
9
  var DOWNLOAD_STAGING_ENTRY_SUFFIX = ".download";
9
- var STALE_DOWNLOAD_STAGING_AGE_MS = 1440 * 60 * 1e3;
10
+ var STALE_DOWNLOAD_STAGING_AGE_MS = 864e5;
10
11
  var MAX_STAGING_CLEANUP_CONCURRENCY = 8;
11
12
  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.
@@ -31,48 +35,123 @@ function isDownloadStagingDirectorySegment(segment) {
31
35
  * @param randomUUID - UUID provider used to create unique staging entries.
32
36
  * @param statForDeviceCheck - Stat function used to verify filesystem devices.
33
37
  * @param beforeStagingMarkerWrite - Test hook called before marker creation.
38
+ * @param activityEntryLimit - Maximum entry count inspected for stale staging activity.
39
+ * @param afterManagedStagingMarkerWrite - Test hook called after managed-root marker creation.
34
40
  *
35
- * @returns The resolved staging directory path.
41
+ * @returns The resolved managed and per-run staging directory paths.
36
42
  *
37
43
  * @internal
38
44
  */
39
- async function createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, beforeStagingMarkerWrite) {
45
+ async function createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, beforeStagingMarkerWrite, activityEntryLimit = DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT, afterManagedStagingMarkerWrite) {
40
46
  const { chmod, lstat, mkdir, readdir, realpath, rm } = await import("node:fs/promises");
41
47
  const managedDirectory = path.join(rootRealPath, DOWNLOAD_STAGING_DIRECTORY_NAME);
48
+ const releaseManagedDirectoryCreation = retainDownloadStagingRootCreation(managedDirectory);
42
49
  try {
43
- await mkdir(managedDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE });
50
+ try {
51
+ await mkdir(managedDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE });
52
+ } catch (err) {
53
+ if (!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
+ 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
+ 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);
44
106
  } catch (err) {
45
- if (!hasErrorCode(err, "EEXIST")) throw err;
107
+ emitCleanupWarning(`failed to remove B2 SDK download staging root: ${localFilesystemErrorReason(err)}`);
46
108
  }
47
- if (!(await lstat(managedDirectory)).isDirectory()) throw new Error(`unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is not a directory`);
48
- 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 (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 (hasErrorCode(err, "ENOENT")) return void 0;
120
+ throw err;
121
+ });
122
+ if (realManagedDirectory === void 0) return;
49
123
  assertPathInsideRoot(rootRealPath, realManagedDirectory, path);
50
- await assertDownloadPathSameDevice(rootRealPath, realManagedDirectory, statForDeviceCheck, "unsafe local destination path: cannot stage download across filesystems");
51
- if (!await isManagedDownloadStagingRoot(realManagedDirectory)) {
52
- 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`);
53
- }
54
- await beforeStagingMarkerWrite?.(realManagedDirectory);
55
- await writeStagingMarker(realManagedDirectory, path);
56
- /* v8 ignore next -- best-effort chmod */
57
- await chmod(realManagedDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {});
58
- await reapStaleDownloadStagingDirectoriesOnce(realManagedDirectory, path, Date.now());
59
- const stagingDirectory = path.join(realManagedDirectory, `${Date.now()}-${randomUUID()}${DOWNLOAD_STAGING_ENTRY_SUFFIX}`);
60
- await mkdir(stagingDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE });
61
- /* v8 ignore next -- best-effort chmod */
62
- 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 (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 (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 (hasErrorCode(err, "ENOENT")) return false;
142
+ throw err;
143
+ })) return;
63
144
  try {
64
- const realStagingDirectory = await realpath(stagingDirectory);
65
- assertPathInsideRoot(realManagedDirectory, realStagingDirectory, path);
66
- await assertDownloadPathSameDevice(rootRealPath, realStagingDirectory, statForDeviceCheck, "unsafe local destination path: cannot stage download across filesystems");
67
- await beforeStagingMarkerWrite?.(realStagingDirectory);
68
- await writeStagingMarker(realStagingDirectory, path);
69
- return realStagingDirectory;
145
+ await rmdir(realManagedDirectory);
70
146
  } catch (err) {
71
- /* v8 ignore next -- best-effort cleanup */
72
- await rm(stagingDirectory, {
73
- recursive: true,
74
- force: true
75
- }).catch(() => {});
147
+ if (hasErrorCode(err, "ENOENT")) return;
148
+ if (hasErrorCode(err, "ENOTEMPTY") || 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: ${localFilesystemErrorReason(restoreErr)}`);
154
+ });
76
155
  throw err;
77
156
  }
78
157
  }
@@ -118,7 +197,9 @@ async function writeStagingMarker(directory, path) {
118
197
  /* v8 ignore next -- best-effort chmod */
119
198
  await handle.chmod(PRIVATE_DOWNLOAD_FILE_MODE).catch(() => {});
120
199
  } catch (err) {
121
- if (hasErrorCode(err, "EEXIST") || hasErrorCode(err, "ELOOP")) {
200
+ if (hasErrorCode(err, "EEXIST") ||
201
+ /* v8 ignore next -- ELOOP depends on platform-specific O_NOFOLLOW behavior */
202
+ hasErrorCode(err, "ELOOP")) {
122
203
  if ((await lstat(markerPath).catch(() => void 0))?.isFile() === true) return;
123
204
  throw new Error("unsafe local destination path: staging marker is not a regular file");
124
205
  }
@@ -128,6 +209,24 @@ async function writeStagingMarker(directory, path) {
128
209
  await handle?.close().catch(() => {});
129
210
  }
130
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
+ }
131
230
  /**
132
231
  * Verifies that a candidate path is on the same filesystem device as the root.
133
232
  * @param rootRealPath - Resolved local sync root path.
@@ -141,33 +240,36 @@ async function assertDownloadPathSameDevice(rootRealPath, candidateRealPath, sta
141
240
  const [rootStats, candidateStats] = await Promise.all([statForDeviceCheck(rootRealPath), statForDeviceCheck(candidateRealPath)]);
142
241
  if (rootStats.dev !== candidateStats.dev) throw new Error(message);
143
242
  }
144
- async function reapStaleDownloadStagingDirectoriesOnce(managedDirectory, path, nowMillis) {
243
+ async function reapStaleDownloadStagingDirectoriesOnce(managedDirectory, path, nowMillis, activityEntryLimit) {
145
244
  const previous = reapedManagedDirectories.get(managedDirectory);
146
245
  if (previous !== void 0) {
147
246
  await previous;
148
247
  return;
149
248
  }
150
- const next = reapStaleDownloadStagingDirectories(managedDirectory, path, nowMillis).finally(() => {
249
+ const next = reapStaleDownloadStagingDirectories(managedDirectory, path, nowMillis, activityEntryLimit).finally(() => {
250
+ /* v8 ignore next -- this map entry cannot be replaced until the current cleanup settles */
151
251
  if (reapedManagedDirectories.get(managedDirectory) === next) reapedManagedDirectories.delete(managedDirectory);
152
252
  });
153
253
  reapedManagedDirectories.set(managedDirectory, next);
154
254
  await next;
155
255
  }
156
- async function reapStaleDownloadStagingDirectories(managedDirectory, path, nowMillis) {
256
+ async function reapStaleDownloadStagingDirectories(managedDirectory, path, nowMillis, activityEntryLimit) {
157
257
  const { readdir, realpath, rm } = await import("node:fs/promises");
158
258
  let entries;
159
259
  try {
160
260
  entries = await readdir(managedDirectory, { withFileTypes: true });
161
261
  } catch (err) {
262
+ /* v8 ignore next -- concurrent managed-root removal is a filesystem race without a stable hook */
162
263
  if (hasErrorCode(err, "ENOENT")) return;
163
- 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: ${localFilesystemErrorReason(err)}`);
164
266
  return;
165
267
  }
166
268
  const cleanupErrors = [];
167
269
  await forEachWithConcurrency(entries, MAX_STAGING_CLEANUP_CONCURRENCY, async (entry) => {
168
270
  if (!entry.isDirectory() || !entry.name.endsWith(DOWNLOAD_STAGING_ENTRY_SUFFIX)) return;
169
271
  const candidate = path.join(managedDirectory, entry.name);
170
- const activity = await readManagedStagingEntryActivity(candidate, path);
272
+ const activity = await readManagedStagingEntryActivity(candidate, path, activityEntryLimit);
171
273
  if (activity === void 0 || !stagingActivityIsStale(activity, nowMillis)) return;
172
274
  const realCandidate = await realpath(candidate).catch(() => {
173
275
  cleanupErrors.push({
@@ -175,11 +277,14 @@ async function reapStaleDownloadStagingDirectories(managedDirectory, path, nowMi
175
277
  operation: "inspect"
176
278
  });
177
279
  });
280
+ /* v8 ignore next -- requires a concurrent delete between activity read and realpath */
178
281
  if (realCandidate === void 0) return;
179
282
  try {
180
283
  assertPathInsideRoot(managedDirectory, realCandidate, path);
181
- const latestActivity = await readManagedStagingEntryActivity(candidate, path);
182
- if (latestActivity === void 0 || latestActivity.signature !== activity.signature || !stagingActivityIsStale(latestActivity, nowMillis)) return;
284
+ const latestActivity = await readManagedStagingEntryActivity(candidate, path, activityEntryLimit);
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;
183
288
  await rm(realCandidate, {
184
289
  recursive: true,
185
290
  force: true
@@ -196,16 +301,16 @@ async function reapStaleDownloadStagingDirectories(managedDirectory, path, nowMi
196
301
  emitCleanupWarning(`failed to reap ${cleanupErrors.length} stale B2 SDK download staging ${noun}: ${cleanupErrors.slice(0, MAX_CLEANUP_WARNING_ENTRIES).map(formatCleanupWarning).join("; ")}`);
197
302
  }
198
303
  }
199
- async function readManagedStagingEntryActivity(candidate, path) {
304
+ async function readManagedStagingEntryActivity(candidate, path, activityEntryLimit) {
200
305
  const { lstat, readdir } = await import("node:fs/promises");
201
306
  try {
202
307
  const [directoryStats, markerStats] = await Promise.all([lstat(candidate), lstat(path.join(candidate, DOWNLOAD_STAGING_MARKER_NAME))]);
203
308
  if (!directoryStats.isDirectory() || !markerStats.isFile()) return void 0;
204
309
  const entries = await readdir(candidate, { withFileTypes: true });
205
- if (entries.length > 1024) return void 0;
310
+ if (entries.length > activityEntryLimit) return void 0;
206
311
  const statsParts = [`.:${stagingStatsSignature(directoryStats)}`, `${DOWNLOAD_STAGING_MARKER_NAME}:${stagingStatsSignature(markerStats)}`];
207
312
  let newestActivityMs = Math.max(stagingStatsActivityMs(directoryStats), stagingStatsActivityMs(markerStats));
208
- 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)) {
209
314
  const stats = await lstat(path.join(candidate, entry.name));
210
315
  statsParts.push(`${entry.name}:${stagingStatsSignature(stats)}`);
211
316
  newestActivityMs = Math.max(newestActivityMs, stagingStatsActivityMs(stats));
@@ -218,6 +323,12 @@ async function readManagedStagingEntryActivity(candidate, path) {
218
323
  return;
219
324
  }
220
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
+ }
221
332
  function stagingStatsActivityMs(stats) {
222
333
  return stats.mtimeMs;
223
334
  }
@@ -245,7 +356,7 @@ async function forEachWithConcurrency(items, concurrency, fn) {
245
356
  while (index < items.length) {
246
357
  const item = items[index];
247
358
  index += 1;
248
- if (item !== void 0) await fn(item);
359
+ await fn(item);
249
360
  }
250
361
  });
251
362
  await Promise.all(workers);
@@ -254,6 +365,6 @@ function emitCleanupWarning(message) {
254
365
  globalThis.process?.emitWarning?.(message, { code: "B2SDK_DOWNLOAD_STAGING_CLEANUP_FAILED" });
255
366
  }
256
367
  //#endregion
257
- export { DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT, DOWNLOAD_STAGING_DIRECTORY_NAME, DOWNLOAD_STAGING_MARKER_NAME, assertDownloadPathSameDevice, createDownloadStagingDirectory, isDownloadStagingDirectorySegment, isManagedDownloadStagingRoot };
368
+ export { DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT, DOWNLOAD_STAGING_DIRECTORY_NAME, DOWNLOAD_STAGING_MARKER_NAME, assertDownloadPathSameDevice, createDownloadStagingDirectory, downloadStagingTestHooks, isDownloadStagingDirectorySegment, isManagedDownloadStagingRoot, removeDownloadStagingRootMarkerAndDirectory };
258
369
 
259
370
  //# sourceMappingURL=download-staging.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"download-staging.js","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 *\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): 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(realManagedDirectory, path, Date.now())\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): Promise<void> {\n const previous = reapedManagedDirectories.get(managedDirectory)\n if (previous !== undefined) {\n await previous\n return\n }\n\n const next = reapStaleDownloadStagingDirectories(managedDirectory, path, nowMillis).finally(\n () => {\n if (reapedManagedDirectories.get(managedDirectory) === next) {\n reapedManagedDirectories.delete(managedDirectory)\n }\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): 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)\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(candidate, path)\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): 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 > DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT) 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.js","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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"filters.cjs","names":[],"sources":["../../src/sync/filters.ts"],"sourcesContent":["import {\n pathExceedsSafeRegExpInput,\n regexpMatchesSyncPath,\n validateSyncFilters,\n} from './regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from './scan-events.ts'\nimport type { SyncFilterOptions, SyncFilterPattern, SyncPath, SyncScanOptions } from './types.ts'\n\n/**\n * Tests whether a relative sync path is included by the configured include/exclude filters.\n * Exclude filters win over include filters when both match the same path.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the path should remain in the sync scan.\n */\nexport function pathPassesSyncFilters(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n\n if (normalizedPathSkippedByRegExpInputLimit(path, filters)) return false\n\n const include = filters?.include ?? []\n const exclude = filters?.exclude ?? []\n\n if (include.length > 0) {\n if (!include.some((pattern) => matchesPattern(path, pattern))) return false\n }\n\n return !exclude.some((pattern) => matchesPattern(path, pattern))\n}\n\n/**\n * Tests whether a directory may contain paths admitted by the configured filters.\n *\n * @param relativePath - Folder-relative directory path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the scanner should descend into the directory.\n */\nexport function directoryMayContainSyncPaths(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n if (path === '') return true\n\n const exclude = filters?.exclude ?? []\n if (exclude.some((pattern) => stringPatternExcludesAllDescendants(path, pattern))) {\n return false\n }\n\n const include = filters?.include ?? []\n return include.length === 0 || include.some((pattern) => patternMayMatchDescendant(path, pattern))\n}\n\n/**\n * Returns the safe literal prefix that B2 listing can use for include filters.\n * Exclude filters are not considered because they cannot narrow a B2 prefix.\n *\n * @param filters - Optional include and exclude filters.\n *\n * @returns A folder-relative literal prefix, or an empty string when no safe narrowing exists.\n */\nexport function literalPrefixForSyncFilters(filters: SyncFilterOptions | undefined): string {\n validateSyncFilters(filters)\n const include = filters?.include ?? []\n let commonPrefix: string | undefined\n\n for (const pattern of include) {\n if (patternIsRegExp(pattern)) return ''\n\n const glob = normalizePath(pattern)\n if (!glob.includes('/')) return ''\n\n const prefix = literalPrefixForGlob(glob)\n if (prefix === '') return ''\n commonPrefix = commonPrefix === undefined ? prefix : commonLiteralPrefix(commonPrefix, prefix)\n }\n\n return commonPrefix ?? ''\n}\n\n/**\n * Filters an async iterable of sync paths while preserving the original item type.\n *\n * @typeParam T - Concrete sync path shape yielded by the source folder.\n *\n * @param paths - Async iterable of folder scan results.\n * @param filters - Optional include and exclude filters.\n *\n * @returns A filtered async generator of sync paths.\n */\nexport async function* filterSyncPaths<T extends SyncPath>(\n paths: AsyncIterable<T>,\n filters: SyncScanOptions | undefined,\n): AsyncGenerator<T> {\n for await (const path of paths) {\n if (pathPassesSyncFilters(path.relativePath, filters)) {\n yield path\n } else if (pathSkippedByRegExpInputLimit(path.relativePath, filters)) {\n emitScannerSkip(filters, regexpInputTooLongSkip(normalizePath(path.relativePath)))\n }\n }\n}\n\n/**\n * Tests whether a path is skipped because RegExp filters are configured and the normalized path\n * exceeds the SDK RegExp input guard.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when any RegExp filter is present and the path is too long to evaluate.\n */\nexport function pathSkippedByRegExpInputLimit(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n return normalizedPathSkippedByRegExpInputLimit(normalizePath(relativePath), filters)\n}\n\nfunction normalizedPathSkippedByRegExpInputLimit(\n normalizedPath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n if (!pathExceedsSafeRegExpInput(normalizedPath) || !filtersContainRegExp(filters)) {\n return false\n }\n return true\n}\n\nfunction matchesPattern(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) {\n return regexpMatchesSyncPath(relativePath, pattern)\n }\n\n const glob = normalizePath(pattern)\n if (glob === '') return relativePath === ''\n\n const segments = splitPath(relativePath)\n if (!glob.includes('/')) {\n return segments.some((segment) => matchSegmentGlob(segment, glob))\n }\n\n return matchPathGlob(segments, splitPath(glob))\n}\n\nfunction stringPatternExcludesAllDescendants(\n relativePath: string,\n pattern: SyncFilterPattern,\n): boolean {\n if (patternIsRegExp(pattern)) return false\n\n const glob = normalizePath(pattern)\n if (glob === '') return false\n if (!glob.includes('/')) return matchesPattern(relativePath, pattern)\n\n const globSegments = splitPath(glob)\n return globSegments.at(-1) === '**' && matchPathGlob(splitPath(relativePath), globSegments)\n}\n\nfunction filtersContainRegExp(filters: SyncFilterOptions | undefined): boolean {\n return (\n filters?.include?.some(patternIsRegExp) === true ||\n filters?.exclude?.some(patternIsRegExp) === true\n )\n}\n\nfunction patternMayMatchDescendant(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) return true\n\n const glob = normalizePath(pattern)\n if (glob === '' || !glob.includes('/')) return true\n\n const pathSegments = splitPath(relativePath)\n const globSegments = splitPath(glob)\n const length = Math.min(pathSegments.length, globSegments.length)\n\n for (let i = 0; i < length; i++) {\n const globSegment = globSegments[i]\n if (globSegment === '**' || globSegment === undefined || hasGlobWildcard(globSegment)) {\n return true\n }\n if (globSegment !== pathSegments[i]) {\n return false\n }\n }\n\n return true\n}\n\nfunction matchPathGlob(pathSegments: readonly string[], globSegments: readonly string[]): boolean {\n let reachable = new Array<boolean>(pathSegments.length + 1).fill(false)\n reachable[0] = true\n\n for (const globSegment of globSegments) {\n const next = new Array<boolean>(pathSegments.length + 1).fill(false)\n\n if (globSegment === '**') {\n // A whole-segment `**` consumes zero or more complete path segments.\n let canReach = false\n for (let i = 0; i <= pathSegments.length; i++) {\n canReach = canReach || reachable[i] === true\n next[i] = canReach\n }\n } else {\n for (let i = 0; i < pathSegments.length; i++) {\n if (reachable[i] === true && matchSegmentGlob(pathSegments[i] ?? '', globSegment)) {\n next[i + 1] = true\n }\n }\n }\n\n reachable = next\n }\n\n return reachable[pathSegments.length] === true\n}\n\nfunction matchSegmentGlob(segment: string, glob: string): boolean {\n let segmentIndex = 0\n let globIndex = 0\n let starIndex = -1\n let starMatchIndex = 0\n\n while (segmentIndex < segment.length) {\n const globChar = glob[globIndex]\n if (globChar === '?' || globChar === segment[segmentIndex]) {\n globIndex++\n segmentIndex++\n } else if (globChar === '*') {\n // Repeated `*` in one segment is still one segment-local wildcard.\n while (glob[globIndex + 1] === '*') globIndex++\n starIndex = globIndex\n starMatchIndex = segmentIndex\n globIndex++\n } else if (starIndex !== -1) {\n globIndex = starIndex + 1\n starMatchIndex++\n segmentIndex = starMatchIndex\n } else {\n return false\n }\n }\n\n while (glob[globIndex] === '*') globIndex++\n return globIndex === glob.length\n}\n\nfunction literalPrefixForGlob(glob: string): string {\n const segments = splitPath(glob)\n const literalSegments: string[] = []\n let firstWildcardIndex = segments.length\n\n for (const [index, segment] of segments.entries()) {\n if (segment === '**' || hasGlobWildcard(segment)) {\n firstWildcardIndex = index\n break\n }\n literalSegments.push(segment)\n }\n\n if (literalSegments.length === 0) return ''\n const prefix = literalSegments.join('/')\n const wildcardTail = segments.slice(firstWildcardIndex)\n const tailMayMatchBarePrefix =\n wildcardTail.length > 0 && wildcardTail.every((segment) => segment === '**')\n if (tailMayMatchBarePrefix) return prefix\n return literalSegments.length < segments.length ? `${prefix}/` : prefix\n}\n\nfunction commonLiteralPrefix(a: string, b: string): string {\n let end = 0\n const max = Math.min(a.length, b.length)\n while (end < max && a[end] === b[end]) end++\n return trimTrailingHighSurrogate(a.slice(0, end))\n}\n\nfunction trimTrailingHighSurrogate(value: string): string {\n const lastCodeUnit = value.charCodeAt(value.length - 1)\n return lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff ? value.slice(0, -1) : value\n}\n\nfunction hasGlobWildcard(glob: string): boolean {\n return glob.includes('*') || glob.includes('?')\n}\n\nfunction patternIsRegExp(pattern: SyncFilterPattern): pattern is RegExp {\n return typeof pattern !== 'string'\n}\n\nfunction splitPath(path: string): string[] {\n if (path === '') return []\n return path.split('/').filter((segment) => segment !== '')\n}\n\nfunction normalizePath(path: string): string {\n let normalized = path.split('\\\\').join('/')\n while (normalized.startsWith('./')) normalized = normalized.slice(2)\n while (normalized.startsWith('/')) normalized = normalized.slice(1)\n while (normalized.endsWith('/') && normalized.length > 1) {\n normalized = normalized.slice(0, -1)\n }\n return normalized\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,SAAgB,sBACd,cACA,SACS;CACT,sBAAA,oBAAoB,OAAO;CAC3B,MAAM,OAAO,cAAc,YAAY;CAEvC,IAAI,wCAAwC,MAAM,OAAO,GAAG,OAAO;CAEnE,MAAM,UAAU,SAAS,WAAW,CAAC;CACrC,MAAM,UAAU,SAAS,WAAW,CAAC;CAErC,IAAI,QAAQ,SAAS;MACf,CAAC,QAAQ,MAAM,YAAY,eAAe,MAAM,OAAO,CAAC,GAAG,OAAO;CAAA;CAGxE,OAAO,CAAC,QAAQ,MAAM,YAAY,eAAe,MAAM,OAAO,CAAC;AACjE;;;;;;;;;AAUA,SAAgB,6BACd,cACA,SACS;CACT,sBAAA,oBAAoB,OAAO;CAC3B,MAAM,OAAO,cAAc,YAAY;CACvC,IAAI,SAAS,IAAI,OAAO;CAGxB,KADgB,SAAS,WAAW,CAAC,EAAA,CACzB,MAAM,YAAY,oCAAoC,MAAM,OAAO,CAAC,GAC9E,OAAO;CAGT,MAAM,UAAU,SAAS,WAAW,CAAC;CACrC,OAAO,QAAQ,WAAW,KAAK,QAAQ,MAAM,YAAY,0BAA0B,MAAM,OAAO,CAAC;AACnG;;;;;;;;;AAUA,SAAgB,4BAA4B,SAAgD;CAC1F,sBAAA,oBAAoB,OAAO;CAC3B,MAAM,UAAU,SAAS,WAAW,CAAC;CACrC,IAAI;CAEJ,KAAK,MAAM,WAAW,SAAS;EAC7B,IAAI,gBAAgB,OAAO,GAAG,OAAO;EAErC,MAAM,OAAO,cAAc,OAAO;EAClC,IAAI,CAAC,KAAK,SAAS,GAAG,GAAG,OAAO;EAEhC,MAAM,SAAS,qBAAqB,IAAI;EACxC,IAAI,WAAW,IAAI,OAAO;EAC1B,eAAe,iBAAiB,KAAA,IAAY,SAAS,oBAAoB,cAAc,MAAM;CAC/F;CAEA,OAAO,gBAAgB;AACzB;;;;;;;;;;;AAYA,gBAAuB,gBACrB,OACA,SACmB;CACnB,WAAW,MAAM,QAAQ,OACvB,IAAI,sBAAsB,KAAK,cAAc,OAAO,GAClD,MAAM;MACD,IAAI,8BAA8B,KAAK,cAAc,OAAO,GACjE,oBAAA,gBAAgB,SAAS,oBAAA,uBAAuB,cAAc,KAAK,YAAY,CAAC,CAAC;AAGvF;;;;;;;;;;AAWA,SAAgB,8BACd,cACA,SACS;CACT,sBAAA,oBAAoB,OAAO;CAC3B,OAAO,wCAAwC,cAAc,YAAY,GAAG,OAAO;AACrF;AAEA,SAAS,wCACP,gBACA,SACS;CACT,IAAI,CAAC,sBAAA,2BAA2B,cAAc,KAAK,CAAC,qBAAqB,OAAO,GAC9E,OAAO;CAET,OAAO;AACT;AAEA,SAAS,eAAe,cAAsB,SAAqC;CACjF,IAAI,gBAAgB,OAAO,GACzB,OAAO,sBAAA,sBAAsB,cAAc,OAAO;CAGpD,MAAM,OAAO,cAAc,OAAO;CAClC,IAAI,SAAS,IAAI,OAAO,iBAAiB;CAEzC,MAAM,WAAW,UAAU,YAAY;CACvC,IAAI,CAAC,KAAK,SAAS,GAAG,GACpB,OAAO,SAAS,MAAM,YAAY,iBAAiB,SAAS,IAAI,CAAC;CAGnE,OAAO,cAAc,UAAU,UAAU,IAAI,CAAC;AAChD;AAEA,SAAS,oCACP,cACA,SACS;CACT,IAAI,gBAAgB,OAAO,GAAG,OAAO;CAErC,MAAM,OAAO,cAAc,OAAO;CAClC,IAAI,SAAS,IAAI,OAAO;CACxB,IAAI,CAAC,KAAK,SAAS,GAAG,GAAG,OAAO,eAAe,cAAc,OAAO;CAEpE,MAAM,eAAe,UAAU,IAAI;CACnC,OAAO,aAAa,GAAG,EAAE,MAAM,QAAQ,cAAc,UAAU,YAAY,GAAG,YAAY;AAC5F;AAEA,SAAS,qBAAqB,SAAiD;CAC7E,OACE,SAAS,SAAS,KAAK,eAAe,MAAM,QAC5C,SAAS,SAAS,KAAK,eAAe,MAAM;AAEhD;AAEA,SAAS,0BAA0B,cAAsB,SAAqC;CAC5F,IAAI,gBAAgB,OAAO,GAAG,OAAO;CAErC,MAAM,OAAO,cAAc,OAAO;CAClC,IAAI,SAAS,MAAM,CAAC,KAAK,SAAS,GAAG,GAAG,OAAO;CAE/C,MAAM,eAAe,UAAU,YAAY;CAC3C,MAAM,eAAe,UAAU,IAAI;CACnC,MAAM,SAAS,KAAK,IAAI,aAAa,QAAQ,aAAa,MAAM;CAEhE,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAAK;EAC/B,MAAM,cAAc,aAAa;EACjC,IAAI,gBAAgB,QAAQ,gBAAgB,KAAA,KAAa,gBAAgB,WAAW,GAClF,OAAO;EAET,IAAI,gBAAgB,aAAa,IAC/B,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,SAAS,cAAc,cAAiC,cAA0C;CAChG,IAAI,YAAY,IAAI,MAAe,aAAa,SAAS,CAAC,CAAC,CAAC,KAAK,KAAK;CACtE,UAAU,KAAK;CAEf,KAAK,MAAM,eAAe,cAAc;EACtC,MAAM,OAAO,IAAI,MAAe,aAAa,SAAS,CAAC,CAAC,CAAC,KAAK,KAAK;EAEnE,IAAI,gBAAgB,MAAM;GAExB,IAAI,WAAW;GACf,KAAK,IAAI,IAAI,GAAG,KAAK,aAAa,QAAQ,KAAK;IAC7C,WAAW,YAAY,UAAU,OAAO;IACxC,KAAK,KAAK;GACZ;EACF,OACE,KAAK,IAAI,IAAI,GAAG,IAAI,aAAa,QAAQ,KACvC,IAAI,UAAU,OAAO,QAAQ,iBAAiB,aAAa,MAAM,IAAI,WAAW,GAC9E,KAAK,IAAI,KAAK;EAKpB,YAAY;CACd;CAEA,OAAO,UAAU,aAAa,YAAY;AAC5C;AAEA,SAAS,iBAAiB,SAAiB,MAAuB;CAChE,IAAI,eAAe;CACnB,IAAI,YAAY;CAChB,IAAI,YAAY;CAChB,IAAI,iBAAiB;CAErB,OAAO,eAAe,QAAQ,QAAQ;EACpC,MAAM,WAAW,KAAK;EACtB,IAAI,aAAa,OAAO,aAAa,QAAQ,eAAe;GAC1D;GACA;EACF,OAAO,IAAI,aAAa,KAAK;GAE3B,OAAO,KAAK,YAAY,OAAO,KAAK;GACpC,YAAY;GACZ,iBAAiB;GACjB;EACF,OAAO,IAAI,cAAc,IAAI;GAC3B,YAAY,YAAY;GACxB;GACA,eAAe;EACjB,OACE,OAAO;CAEX;CAEA,OAAO,KAAK,eAAe,KAAK;CAChC,OAAO,cAAc,KAAK;AAC5B;AAEA,SAAS,qBAAqB,MAAsB;CAClD,MAAM,WAAW,UAAU,IAAI;CAC/B,MAAM,kBAA4B,CAAC;CACnC,IAAI,qBAAqB,SAAS;CAElC,KAAK,MAAM,CAAC,OAAO,YAAY,SAAS,QAAQ,GAAG;EACjD,IAAI,YAAY,QAAQ,gBAAgB,OAAO,GAAG;GAChD,qBAAqB;GACrB;EACF;EACA,gBAAgB,KAAK,OAAO;CAC9B;CAEA,IAAI,gBAAgB,WAAW,GAAG,OAAO;CACzC,MAAM,SAAS,gBAAgB,KAAK,GAAG;CACvC,MAAM,eAAe,SAAS,MAAM,kBAAkB;CAGtD,IADE,aAAa,SAAS,KAAK,aAAa,OAAO,YAAY,YAAY,IAAI,GACjD,OAAO;CACnC,OAAO,gBAAgB,SAAS,SAAS,SAAS,GAAG,OAAO,KAAK;AACnE;AAEA,SAAS,oBAAoB,GAAW,GAAmB;CACzD,IAAI,MAAM;CACV,MAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;CACvC,OAAO,MAAM,OAAO,EAAE,SAAS,EAAE,MAAM;CACvC,OAAO,0BAA0B,EAAE,MAAM,GAAG,GAAG,CAAC;AAClD;AAEA,SAAS,0BAA0B,OAAuB;CACxD,MAAM,eAAe,MAAM,WAAW,MAAM,SAAS,CAAC;CACtD,OAAO,gBAAgB,SAAU,gBAAgB,QAAS,MAAM,MAAM,GAAG,EAAE,IAAI;AACjF;AAEA,SAAS,gBAAgB,MAAuB;CAC9C,OAAO,KAAK,SAAS,GAAG,KAAK,KAAK,SAAS,GAAG;AAChD;AAEA,SAAS,gBAAgB,SAA+C;CACtE,OAAO,OAAO,YAAY;AAC5B;AAEA,SAAS,UAAU,MAAwB;CACzC,IAAI,SAAS,IAAI,OAAO,CAAC;CACzB,OAAO,KAAK,MAAM,GAAG,CAAC,CAAC,QAAQ,YAAY,YAAY,EAAE;AAC3D;AAEA,SAAS,cAAc,MAAsB;CAC3C,IAAI,aAAa,KAAK,MAAM,IAAI,CAAC,CAAC,KAAK,GAAG;CAC1C,OAAO,WAAW,WAAW,IAAI,GAAG,aAAa,WAAW,MAAM,CAAC;CACnE,OAAO,WAAW,WAAW,GAAG,GAAG,aAAa,WAAW,MAAM,CAAC;CAClE,OAAO,WAAW,SAAS,GAAG,KAAK,WAAW,SAAS,GACrD,aAAa,WAAW,MAAM,GAAG,EAAE;CAErC,OAAO;AACT"}
1
+ {"version":3,"file":"filters.cjs","names":[],"sources":["../../src/sync/filters.ts"],"sourcesContent":["import {\n pathExceedsSafeRegExpInput,\n regexpMatchesSyncPath,\n validateSyncFilters,\n} from './regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from './scan-events.ts'\nimport type { SyncFilterOptions, SyncFilterPattern, SyncPath, SyncScanOptions } from './types.ts'\n\n/**\n * Tests whether a relative sync path is included by the configured include/exclude filters.\n * Exclude filters win over include filters when both match the same path.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the path should remain in the sync scan.\n */\nexport function pathPassesSyncFilters(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n\n if (normalizedPathSkippedByRegExpInputLimit(path, filters)) return false\n\n const include = filters?.include ?? []\n const exclude = filters?.exclude ?? []\n\n if (include.length > 0) {\n if (!include.some((pattern) => matchesPattern(path, pattern))) return false\n }\n\n return !exclude.some((pattern) => matchesPattern(path, pattern))\n}\n\n/**\n * Tests whether a directory may contain paths admitted by the configured filters.\n *\n * @param relativePath - Folder-relative directory path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the scanner should descend into the directory.\n */\nexport function directoryMayContainSyncPaths(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n if (path === '') return true\n\n const exclude = filters?.exclude ?? []\n if (exclude.some((pattern) => stringPatternExcludesAllDescendants(path, pattern))) {\n return false\n }\n\n const include = filters?.include ?? []\n return include.length === 0 || include.some((pattern) => patternMayMatchDescendant(path, pattern))\n}\n\n/**\n * Returns the safe literal prefix that B2 listing can use for include filters.\n * Exclude filters are not considered because they cannot narrow a B2 prefix.\n *\n * @param filters - Optional include and exclude filters.\n *\n * @returns A folder-relative literal prefix, or an empty string when no safe narrowing exists.\n */\nexport function literalPrefixForSyncFilters(filters: SyncFilterOptions | undefined): string {\n validateSyncFilters(filters)\n const include = filters?.include ?? []\n let commonPrefix: string | undefined\n\n for (const pattern of include) {\n if (patternIsRegExp(pattern)) return ''\n\n const glob = normalizePath(pattern)\n if (!glob.includes('/')) return ''\n\n const prefix = literalPrefixForGlob(glob)\n if (prefix === '') return ''\n commonPrefix = commonPrefix === undefined ? prefix : commonLiteralPrefix(commonPrefix, prefix)\n }\n\n return commonPrefix ?? ''\n}\n\n/**\n * Filters an async iterable of sync paths while preserving the original item type.\n *\n * @typeParam T - Concrete sync path shape yielded by the source folder.\n *\n * @param paths - Async iterable of folder scan results.\n * @param filters - Optional include and exclude filters.\n *\n * @returns A filtered async generator of sync paths.\n */\nexport async function* filterSyncPaths<T extends SyncPath>(\n paths: AsyncIterable<T>,\n filters: SyncScanOptions | undefined,\n): AsyncGenerator<T> {\n for await (const path of paths) {\n if (pathPassesSyncFilters(path.relativePath, filters)) {\n yield path\n } else if (pathSkippedByRegExpInputLimit(path.relativePath, filters)) {\n emitScannerSkip(filters, regexpInputTooLongSkip(normalizePath(path.relativePath)))\n }\n }\n}\n\n/**\n * Tests whether a path is skipped because RegExp filters are configured and the normalized path\n * exceeds the SDK RegExp input guard.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when any RegExp filter is present and the path is too long to evaluate.\n */\nexport function pathSkippedByRegExpInputLimit(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n return normalizedPathSkippedByRegExpInputLimit(normalizePath(relativePath), filters)\n}\n\nfunction normalizedPathSkippedByRegExpInputLimit(\n normalizedPath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n if (!pathExceedsSafeRegExpInput(normalizedPath) || !filtersContainRegExp(filters)) {\n return false\n }\n return true\n}\n\nfunction matchesPattern(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) {\n return regexpMatchesSyncPath(relativePath, pattern)\n }\n\n const glob = normalizePath(pattern)\n if (glob === '') return relativePath === ''\n\n const segments = splitPath(relativePath)\n if (!glob.includes('/')) {\n return segments.some((segment) => matchSegmentGlob(segment, glob))\n }\n\n return matchPathGlob(segments, splitPath(glob))\n}\n\nfunction stringPatternExcludesAllDescendants(\n relativePath: string,\n pattern: SyncFilterPattern,\n): boolean {\n if (patternIsRegExp(pattern)) return false\n\n const glob = normalizePath(pattern)\n if (glob === '') return false\n if (!glob.includes('/')) return matchesPattern(relativePath, pattern)\n\n const globSegments = splitPath(glob)\n return globSegments.at(-1) === '**' && matchPathGlob(splitPath(relativePath), globSegments)\n}\n\nfunction filtersContainRegExp(filters: SyncFilterOptions | undefined): boolean {\n return (\n filters?.include?.some(patternIsRegExp) === true ||\n filters?.exclude?.some(patternIsRegExp) === true\n )\n}\n\nfunction patternMayMatchDescendant(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) return true\n\n const glob = normalizePath(pattern)\n if (glob === '' || !glob.includes('/')) return true\n\n const pathSegments = splitPath(relativePath)\n const globSegments = splitPath(glob)\n const length = Math.min(pathSegments.length, globSegments.length)\n\n for (let i = 0; i < length; i++) {\n const globSegment = globSegments[i]\n if (globSegment === '**' || globSegment === undefined || hasGlobWildcard(globSegment)) {\n return true\n }\n if (globSegment !== pathSegments[i]) {\n return false\n }\n }\n\n return true\n}\n\nfunction matchPathGlob(pathSegments: readonly string[], globSegments: readonly string[]): boolean {\n let reachable = new Array<boolean>(pathSegments.length + 1).fill(false)\n reachable[0] = true\n\n for (const globSegment of globSegments) {\n const next = new Array<boolean>(pathSegments.length + 1).fill(false)\n\n if (globSegment === '**') {\n // A whole-segment `**` consumes zero or more complete path segments.\n let canReach = false\n for (let i = 0; i <= pathSegments.length; i++) {\n canReach = canReach || reachable[i] === true\n next[i] = canReach\n }\n } else {\n for (let i = 0; i < pathSegments.length; i++) {\n if (reachable[i] === true && matchSegmentGlob(pathSegments[i] ?? '', globSegment)) {\n next[i + 1] = true\n }\n }\n }\n\n reachable = next\n }\n\n return reachable[pathSegments.length] === true\n}\n\nfunction matchSegmentGlob(segment: string, glob: string): boolean {\n let segmentIndex = 0\n let globIndex = 0\n let starIndex = -1\n let starMatchIndex = 0\n\n while (segmentIndex < segment.length) {\n const globChar = glob[globIndex]\n if (globChar === '?' || globChar === segment[segmentIndex]) {\n globIndex++\n segmentIndex++\n } else if (globChar === '*') {\n // Repeated `*` in one segment is still one segment-local wildcard.\n while (glob[globIndex + 1] === '*') globIndex++\n starIndex = globIndex\n starMatchIndex = segmentIndex\n globIndex++\n } else if (starIndex !== -1) {\n globIndex = starIndex + 1\n starMatchIndex++\n segmentIndex = starMatchIndex\n } else {\n return false\n }\n }\n\n while (glob[globIndex] === '*') globIndex++\n return globIndex === glob.length\n}\n\nfunction literalPrefixForGlob(glob: string): string {\n const segments = splitPath(glob)\n const literalSegments: string[] = []\n let firstWildcardIndex = segments.length\n\n for (const [index, segment] of segments.entries()) {\n if (segment === '**' || hasGlobWildcard(segment)) {\n firstWildcardIndex = index\n break\n }\n literalSegments.push(segment)\n }\n\n if (literalSegments.length === 0) return ''\n const prefix = literalSegments.join('/')\n const wildcardTail = segments.slice(firstWildcardIndex)\n const tailMayMatchBarePrefix =\n wildcardTail.length > 0 && wildcardTail.every((segment) => segment === '**')\n if (tailMayMatchBarePrefix) return prefix\n return literalSegments.length < segments.length ? `${prefix}/` : prefix\n}\n\nfunction commonLiteralPrefix(a: string, b: string): string {\n let end = 0\n const max = Math.min(a.length, b.length)\n while (end < max && a[end] === b[end]) end++\n return trimTrailingHighSurrogate(a.slice(0, end))\n}\n\nfunction trimTrailingHighSurrogate(value: string): string {\n const lastCodeUnit = value.charCodeAt(value.length - 1)\n return lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff ? value.slice(0, -1) : value\n}\n\nfunction hasGlobWildcard(glob: string): boolean {\n return glob.includes('*') || glob.includes('?')\n}\n\nfunction patternIsRegExp(pattern: SyncFilterPattern): pattern is RegExp {\n return typeof pattern !== 'string'\n}\n\nfunction splitPath(path: string): string[] {\n if (path === '') return []\n return path.split('/').filter((segment) => segment !== '')\n}\n\nfunction normalizePath(path: string): string {\n let normalized = path.split('\\\\').join('/')\n while (normalized.startsWith('./')) normalized = normalized.slice(2)\n while (normalized.startsWith('/')) normalized = normalized.slice(1)\n while (normalized.endsWith('/') && normalized.length > 1) {\n normalized = normalized.slice(0, -1)\n }\n return normalized\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"filters.js","names":[],"sources":["../../src/sync/filters.ts"],"sourcesContent":["import {\n pathExceedsSafeRegExpInput,\n regexpMatchesSyncPath,\n validateSyncFilters,\n} from './regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from './scan-events.ts'\nimport type { SyncFilterOptions, SyncFilterPattern, SyncPath, SyncScanOptions } from './types.ts'\n\n/**\n * Tests whether a relative sync path is included by the configured include/exclude filters.\n * Exclude filters win over include filters when both match the same path.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the path should remain in the sync scan.\n */\nexport function pathPassesSyncFilters(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n\n if (normalizedPathSkippedByRegExpInputLimit(path, filters)) return false\n\n const include = filters?.include ?? []\n const exclude = filters?.exclude ?? []\n\n if (include.length > 0) {\n if (!include.some((pattern) => matchesPattern(path, pattern))) return false\n }\n\n return !exclude.some((pattern) => matchesPattern(path, pattern))\n}\n\n/**\n * Tests whether a directory may contain paths admitted by the configured filters.\n *\n * @param relativePath - Folder-relative directory path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the scanner should descend into the directory.\n */\nexport function directoryMayContainSyncPaths(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n if (path === '') return true\n\n const exclude = filters?.exclude ?? []\n if (exclude.some((pattern) => stringPatternExcludesAllDescendants(path, pattern))) {\n return false\n }\n\n const include = filters?.include ?? []\n return include.length === 0 || include.some((pattern) => patternMayMatchDescendant(path, pattern))\n}\n\n/**\n * Returns the safe literal prefix that B2 listing can use for include filters.\n * Exclude filters are not considered because they cannot narrow a B2 prefix.\n *\n * @param filters - Optional include and exclude filters.\n *\n * @returns A folder-relative literal prefix, or an empty string when no safe narrowing exists.\n */\nexport function literalPrefixForSyncFilters(filters: SyncFilterOptions | undefined): string {\n validateSyncFilters(filters)\n const include = filters?.include ?? []\n let commonPrefix: string | undefined\n\n for (const pattern of include) {\n if (patternIsRegExp(pattern)) return ''\n\n const glob = normalizePath(pattern)\n if (!glob.includes('/')) return ''\n\n const prefix = literalPrefixForGlob(glob)\n if (prefix === '') return ''\n commonPrefix = commonPrefix === undefined ? prefix : commonLiteralPrefix(commonPrefix, prefix)\n }\n\n return commonPrefix ?? ''\n}\n\n/**\n * Filters an async iterable of sync paths while preserving the original item type.\n *\n * @typeParam T - Concrete sync path shape yielded by the source folder.\n *\n * @param paths - Async iterable of folder scan results.\n * @param filters - Optional include and exclude filters.\n *\n * @returns A filtered async generator of sync paths.\n */\nexport async function* filterSyncPaths<T extends SyncPath>(\n paths: AsyncIterable<T>,\n filters: SyncScanOptions | undefined,\n): AsyncGenerator<T> {\n for await (const path of paths) {\n if (pathPassesSyncFilters(path.relativePath, filters)) {\n yield path\n } else if (pathSkippedByRegExpInputLimit(path.relativePath, filters)) {\n emitScannerSkip(filters, regexpInputTooLongSkip(normalizePath(path.relativePath)))\n }\n }\n}\n\n/**\n * Tests whether a path is skipped because RegExp filters are configured and the normalized path\n * exceeds the SDK RegExp input guard.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when any RegExp filter is present and the path is too long to evaluate.\n */\nexport function pathSkippedByRegExpInputLimit(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n return normalizedPathSkippedByRegExpInputLimit(normalizePath(relativePath), filters)\n}\n\nfunction normalizedPathSkippedByRegExpInputLimit(\n normalizedPath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n if (!pathExceedsSafeRegExpInput(normalizedPath) || !filtersContainRegExp(filters)) {\n return false\n }\n return true\n}\n\nfunction matchesPattern(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) {\n return regexpMatchesSyncPath(relativePath, pattern)\n }\n\n const glob = normalizePath(pattern)\n if (glob === '') return relativePath === ''\n\n const segments = splitPath(relativePath)\n if (!glob.includes('/')) {\n return segments.some((segment) => matchSegmentGlob(segment, glob))\n }\n\n return matchPathGlob(segments, splitPath(glob))\n}\n\nfunction stringPatternExcludesAllDescendants(\n relativePath: string,\n pattern: SyncFilterPattern,\n): boolean {\n if (patternIsRegExp(pattern)) return false\n\n const glob = normalizePath(pattern)\n if (glob === '') return false\n if (!glob.includes('/')) return matchesPattern(relativePath, pattern)\n\n const globSegments = splitPath(glob)\n return globSegments.at(-1) === '**' && matchPathGlob(splitPath(relativePath), globSegments)\n}\n\nfunction filtersContainRegExp(filters: SyncFilterOptions | undefined): boolean {\n return (\n filters?.include?.some(patternIsRegExp) === true ||\n filters?.exclude?.some(patternIsRegExp) === true\n )\n}\n\nfunction patternMayMatchDescendant(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) return true\n\n const glob = normalizePath(pattern)\n if (glob === '' || !glob.includes('/')) return true\n\n const pathSegments = splitPath(relativePath)\n const globSegments = splitPath(glob)\n const length = Math.min(pathSegments.length, globSegments.length)\n\n for (let i = 0; i < length; i++) {\n const globSegment = globSegments[i]\n if (globSegment === '**' || globSegment === undefined || hasGlobWildcard(globSegment)) {\n return true\n }\n if (globSegment !== pathSegments[i]) {\n return false\n }\n }\n\n return true\n}\n\nfunction matchPathGlob(pathSegments: readonly string[], globSegments: readonly string[]): boolean {\n let reachable = new Array<boolean>(pathSegments.length + 1).fill(false)\n reachable[0] = true\n\n for (const globSegment of globSegments) {\n const next = new Array<boolean>(pathSegments.length + 1).fill(false)\n\n if (globSegment === '**') {\n // A whole-segment `**` consumes zero or more complete path segments.\n let canReach = false\n for (let i = 0; i <= pathSegments.length; i++) {\n canReach = canReach || reachable[i] === true\n next[i] = canReach\n }\n } else {\n for (let i = 0; i < pathSegments.length; i++) {\n if (reachable[i] === true && matchSegmentGlob(pathSegments[i] ?? '', globSegment)) {\n next[i + 1] = true\n }\n }\n }\n\n reachable = next\n }\n\n return reachable[pathSegments.length] === true\n}\n\nfunction matchSegmentGlob(segment: string, glob: string): boolean {\n let segmentIndex = 0\n let globIndex = 0\n let starIndex = -1\n let starMatchIndex = 0\n\n while (segmentIndex < segment.length) {\n const globChar = glob[globIndex]\n if (globChar === '?' || globChar === segment[segmentIndex]) {\n globIndex++\n segmentIndex++\n } else if (globChar === '*') {\n // Repeated `*` in one segment is still one segment-local wildcard.\n while (glob[globIndex + 1] === '*') globIndex++\n starIndex = globIndex\n starMatchIndex = segmentIndex\n globIndex++\n } else if (starIndex !== -1) {\n globIndex = starIndex + 1\n starMatchIndex++\n segmentIndex = starMatchIndex\n } else {\n return false\n }\n }\n\n while (glob[globIndex] === '*') globIndex++\n return globIndex === glob.length\n}\n\nfunction literalPrefixForGlob(glob: string): string {\n const segments = splitPath(glob)\n const literalSegments: string[] = []\n let firstWildcardIndex = segments.length\n\n for (const [index, segment] of segments.entries()) {\n if (segment === '**' || hasGlobWildcard(segment)) {\n firstWildcardIndex = index\n break\n }\n literalSegments.push(segment)\n }\n\n if (literalSegments.length === 0) return ''\n const prefix = literalSegments.join('/')\n const wildcardTail = segments.slice(firstWildcardIndex)\n const tailMayMatchBarePrefix =\n wildcardTail.length > 0 && wildcardTail.every((segment) => segment === '**')\n if (tailMayMatchBarePrefix) return prefix\n return literalSegments.length < segments.length ? `${prefix}/` : prefix\n}\n\nfunction commonLiteralPrefix(a: string, b: string): string {\n let end = 0\n const max = Math.min(a.length, b.length)\n while (end < max && a[end] === b[end]) end++\n return trimTrailingHighSurrogate(a.slice(0, end))\n}\n\nfunction trimTrailingHighSurrogate(value: string): string {\n const lastCodeUnit = value.charCodeAt(value.length - 1)\n return lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff ? value.slice(0, -1) : value\n}\n\nfunction hasGlobWildcard(glob: string): boolean {\n return glob.includes('*') || glob.includes('?')\n}\n\nfunction patternIsRegExp(pattern: SyncFilterPattern): pattern is RegExp {\n return typeof pattern !== 'string'\n}\n\nfunction splitPath(path: string): string[] {\n if (path === '') return []\n return path.split('/').filter((segment) => segment !== '')\n}\n\nfunction normalizePath(path: string): string {\n let normalized = path.split('\\\\').join('/')\n while (normalized.startsWith('./')) normalized = normalized.slice(2)\n while (normalized.startsWith('/')) normalized = normalized.slice(1)\n while (normalized.endsWith('/') && normalized.length > 1) {\n normalized = normalized.slice(0, -1)\n }\n return normalized\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"filters.js","names":[],"sources":["../../src/sync/filters.ts"],"sourcesContent":["import {\n pathExceedsSafeRegExpInput,\n regexpMatchesSyncPath,\n validateSyncFilters,\n} from './regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from './scan-events.ts'\nimport type { SyncFilterOptions, SyncFilterPattern, SyncPath, SyncScanOptions } from './types.ts'\n\n/**\n * Tests whether a relative sync path is included by the configured include/exclude filters.\n * Exclude filters win over include filters when both match the same path.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the path should remain in the sync scan.\n */\nexport function pathPassesSyncFilters(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n\n if (normalizedPathSkippedByRegExpInputLimit(path, filters)) return false\n\n const include = filters?.include ?? []\n const exclude = filters?.exclude ?? []\n\n if (include.length > 0) {\n if (!include.some((pattern) => matchesPattern(path, pattern))) return false\n }\n\n return !exclude.some((pattern) => matchesPattern(path, pattern))\n}\n\n/**\n * Tests whether a directory may contain paths admitted by the configured filters.\n *\n * @param relativePath - Folder-relative directory path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the scanner should descend into the directory.\n */\nexport function directoryMayContainSyncPaths(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n if (path === '') return true\n\n const exclude = filters?.exclude ?? []\n if (exclude.some((pattern) => stringPatternExcludesAllDescendants(path, pattern))) {\n return false\n }\n\n const include = filters?.include ?? []\n return include.length === 0 || include.some((pattern) => patternMayMatchDescendant(path, pattern))\n}\n\n/**\n * Returns the safe literal prefix that B2 listing can use for include filters.\n * Exclude filters are not considered because they cannot narrow a B2 prefix.\n *\n * @param filters - Optional include and exclude filters.\n *\n * @returns A folder-relative literal prefix, or an empty string when no safe narrowing exists.\n */\nexport function literalPrefixForSyncFilters(filters: SyncFilterOptions | undefined): string {\n validateSyncFilters(filters)\n const include = filters?.include ?? []\n let commonPrefix: string | undefined\n\n for (const pattern of include) {\n if (patternIsRegExp(pattern)) return ''\n\n const glob = normalizePath(pattern)\n if (!glob.includes('/')) return ''\n\n const prefix = literalPrefixForGlob(glob)\n if (prefix === '') return ''\n commonPrefix = commonPrefix === undefined ? prefix : commonLiteralPrefix(commonPrefix, prefix)\n }\n\n return commonPrefix ?? ''\n}\n\n/**\n * Filters an async iterable of sync paths while preserving the original item type.\n *\n * @typeParam T - Concrete sync path shape yielded by the source folder.\n *\n * @param paths - Async iterable of folder scan results.\n * @param filters - Optional include and exclude filters.\n *\n * @returns A filtered async generator of sync paths.\n */\nexport async function* filterSyncPaths<T extends SyncPath>(\n paths: AsyncIterable<T>,\n filters: SyncScanOptions | undefined,\n): AsyncGenerator<T> {\n for await (const path of paths) {\n if (pathPassesSyncFilters(path.relativePath, filters)) {\n yield path\n } else if (pathSkippedByRegExpInputLimit(path.relativePath, filters)) {\n emitScannerSkip(filters, regexpInputTooLongSkip(normalizePath(path.relativePath)))\n }\n }\n}\n\n/**\n * Tests whether a path is skipped because RegExp filters are configured and the normalized path\n * exceeds the SDK RegExp input guard.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when any RegExp filter is present and the path is too long to evaluate.\n */\nexport function pathSkippedByRegExpInputLimit(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n return normalizedPathSkippedByRegExpInputLimit(normalizePath(relativePath), filters)\n}\n\nfunction normalizedPathSkippedByRegExpInputLimit(\n normalizedPath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n if (!pathExceedsSafeRegExpInput(normalizedPath) || !filtersContainRegExp(filters)) {\n return false\n }\n return true\n}\n\nfunction matchesPattern(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) {\n return regexpMatchesSyncPath(relativePath, pattern)\n }\n\n const glob = normalizePath(pattern)\n if (glob === '') return relativePath === ''\n\n const segments = splitPath(relativePath)\n if (!glob.includes('/')) {\n return segments.some((segment) => matchSegmentGlob(segment, glob))\n }\n\n return matchPathGlob(segments, splitPath(glob))\n}\n\nfunction stringPatternExcludesAllDescendants(\n relativePath: string,\n pattern: SyncFilterPattern,\n): boolean {\n if (patternIsRegExp(pattern)) return false\n\n const glob = normalizePath(pattern)\n if (glob === '') return false\n if (!glob.includes('/')) return matchesPattern(relativePath, pattern)\n\n const globSegments = splitPath(glob)\n return globSegments.at(-1) === '**' && matchPathGlob(splitPath(relativePath), globSegments)\n}\n\nfunction filtersContainRegExp(filters: SyncFilterOptions | undefined): boolean {\n return (\n filters?.include?.some(patternIsRegExp) === true ||\n filters?.exclude?.some(patternIsRegExp) === true\n )\n}\n\nfunction patternMayMatchDescendant(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) return true\n\n const glob = normalizePath(pattern)\n if (glob === '' || !glob.includes('/')) return true\n\n const pathSegments = splitPath(relativePath)\n const globSegments = splitPath(glob)\n const length = Math.min(pathSegments.length, globSegments.length)\n\n for (let i = 0; i < length; i++) {\n const globSegment = globSegments[i]\n if (globSegment === '**' || globSegment === undefined || hasGlobWildcard(globSegment)) {\n return true\n }\n if (globSegment !== pathSegments[i]) {\n return false\n }\n }\n\n return true\n}\n\nfunction matchPathGlob(pathSegments: readonly string[], globSegments: readonly string[]): boolean {\n let reachable = new Array<boolean>(pathSegments.length + 1).fill(false)\n reachable[0] = true\n\n for (const globSegment of globSegments) {\n const next = new Array<boolean>(pathSegments.length + 1).fill(false)\n\n if (globSegment === '**') {\n // A whole-segment `**` consumes zero or more complete path segments.\n let canReach = false\n for (let i = 0; i <= pathSegments.length; i++) {\n canReach = canReach || reachable[i] === true\n next[i] = canReach\n }\n } else {\n for (let i = 0; i < pathSegments.length; i++) {\n if (reachable[i] === true && matchSegmentGlob(pathSegments[i] ?? '', globSegment)) {\n next[i + 1] = true\n }\n }\n }\n\n reachable = next\n }\n\n return reachable[pathSegments.length] === true\n}\n\nfunction matchSegmentGlob(segment: string, glob: string): boolean {\n let segmentIndex = 0\n let globIndex = 0\n let starIndex = -1\n let starMatchIndex = 0\n\n while (segmentIndex < segment.length) {\n const globChar = glob[globIndex]\n if (globChar === '?' || globChar === segment[segmentIndex]) {\n globIndex++\n segmentIndex++\n } else if (globChar === '*') {\n // Repeated `*` in one segment is still one segment-local wildcard.\n while (glob[globIndex + 1] === '*') globIndex++\n starIndex = globIndex\n starMatchIndex = segmentIndex\n globIndex++\n } else if (starIndex !== -1) {\n globIndex = starIndex + 1\n starMatchIndex++\n segmentIndex = starMatchIndex\n } else {\n return false\n }\n }\n\n while (glob[globIndex] === '*') globIndex++\n return globIndex === glob.length\n}\n\nfunction literalPrefixForGlob(glob: string): string {\n const segments = splitPath(glob)\n const literalSegments: string[] = []\n let firstWildcardIndex = segments.length\n\n for (const [index, segment] of segments.entries()) {\n if (segment === '**' || hasGlobWildcard(segment)) {\n firstWildcardIndex = index\n break\n }\n literalSegments.push(segment)\n }\n\n if (literalSegments.length === 0) return ''\n const prefix = literalSegments.join('/')\n const wildcardTail = segments.slice(firstWildcardIndex)\n const tailMayMatchBarePrefix =\n wildcardTail.length > 0 && wildcardTail.every((segment) => segment === '**')\n if (tailMayMatchBarePrefix) return prefix\n return literalSegments.length < segments.length ? `${prefix}/` : prefix\n}\n\nfunction commonLiteralPrefix(a: string, b: string): string {\n let end = 0\n const max = Math.min(a.length, b.length)\n while (end < max && a[end] === b[end]) end++\n return trimTrailingHighSurrogate(a.slice(0, end))\n}\n\nfunction trimTrailingHighSurrogate(value: string): string {\n const lastCodeUnit = value.charCodeAt(value.length - 1)\n return lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff ? value.slice(0, -1) : value\n}\n\nfunction hasGlobWildcard(glob: string): boolean {\n return glob.includes('*') || glob.includes('?')\n}\n\nfunction patternIsRegExp(pattern: SyncFilterPattern): pattern is RegExp {\n return typeof pattern !== 'string'\n}\n\nfunction splitPath(path: string): string[] {\n if (path === '') return []\n return path.split('/').filter((segment) => segment !== '')\n}\n\nfunction normalizePath(path: string): string {\n let normalized = path.split('\\\\').join('/')\n while (normalized.startsWith('./')) normalized = normalized.slice(2)\n while (normalized.startsWith('/')) normalized = normalized.slice(1)\n while (normalized.endsWith('/') && normalized.length > 1) {\n normalized = normalized.slice(0, -1)\n }\n return normalized\n}\n"],"mappings":"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"}
@@ -22,5 +22,5 @@ export type { SyncSha1PendingState, SyncSha1State, SyncSha1UnavailableState, Syn
22
22
  export { isUntrustedSha1, parseSyncContentSha1, selectB2ComparableSha1, syncSha1StateOf, untrustedSha1, untrustedSha1Prefix, } from './sha1-metadata.js';
23
23
  export type { B2SyncFolder, LocalSyncFolder, SupportedSynchronizerConfig, SynchronizerB2Config, SynchronizerConfig, SynchronizerDownConfig, SynchronizerUpConfig, } from './synchronizer.js';
24
24
  export { synchronize } from './synchronizer.js';
25
- export type { B2SyncPath, CompareMode, KeepMode, LocalFileIdentity, LocalSyncPath, SyncActionEvent, SyncActionEventType, SyncCompareEvent, SyncDirection, SyncEncryptionProvider, SyncErrorEvent, SyncEvent, SyncEventType, SyncFilterOptions, SyncFilterPattern, SyncFolder, SyncOptions, SyncPath, SyncScanOptions, SyncSkipEvent, SyncSkipReason, } from './types.js';
25
+ export type { B2SyncPath, CompareMode, KeepMode, LocalFileIdentity, LocalSymlinkPolicy, LocalSyncPath, SyncActionEvent, SyncActionEventType, SyncCompareEvent, SyncDirection, SyncEncryptionProvider, SyncErrorEvent, SyncEvent, SyncEventType, SyncFilterOptions, SyncFilterPattern, SyncFolder, SyncOptions, SyncPath, SyncScanOptions, SyncSkipEvent, SyncSkipReason, } from './types.js';
26
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY,EACV,UAAU,EACV,cAAc,GACf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,4BAA4B,EAC5B,eAAe,EACf,2BAA2B,EAC3B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EACL,gBAAgB,EAChB,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACV,YAAY,EACZ,eAAe,EACf,2BAA2B,EAC3B,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,sBAAsB,EACtB,cAAc,EACd,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,eAAe,EACf,aAAa,EACb,cAAc,GACf,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY,EACV,UAAU,EACV,cAAc,GACf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,4BAA4B,EAC5B,eAAe,EACf,2BAA2B,EAC3B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EACL,gBAAgB,EAChB,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACV,YAAY,EACZ,eAAe,EACf,2BAA2B,EAC3B,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,sBAAsB,EACtB,cAAc,EACd,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,eAAe,EACf,aAAa,EACb,cAAc,GACf,MAAM,YAAY,CAAA"}