@milaboratories/pl-drivers 1.12.7 → 1.12.9

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 (271) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +7 -13
  2. package/dist/clients/constructors.cjs +6 -7
  3. package/dist/clients/constructors.cjs.map +1 -1
  4. package/dist/clients/constructors.d.ts.map +1 -0
  5. package/dist/clients/constructors.js +1 -2
  6. package/dist/clients/constructors.js.map +1 -1
  7. package/dist/clients/crc32c.cjs +1 -2
  8. package/dist/clients/crc32c.cjs.map +1 -1
  9. package/dist/clients/crc32c.js +1 -1
  10. package/dist/clients/download.cjs +6 -6
  11. package/dist/clients/download.cjs.map +1 -1
  12. package/dist/clients/download.d.ts.map +1 -0
  13. package/dist/clients/download.js +2 -2
  14. package/dist/clients/download.js.map +1 -1
  15. package/dist/clients/logs.cjs +5 -4
  16. package/dist/clients/logs.cjs.map +1 -1
  17. package/dist/clients/logs.d.ts.map +1 -0
  18. package/dist/clients/logs.js +3 -2
  19. package/dist/clients/logs.js.map +1 -1
  20. package/dist/clients/ls_api.cjs +4 -4
  21. package/dist/clients/ls_api.cjs.map +1 -1
  22. package/dist/clients/ls_api.d.ts.map +1 -0
  23. package/dist/clients/ls_api.js +2 -2
  24. package/dist/clients/ls_api.js.map +1 -1
  25. package/dist/clients/progress.cjs +7 -5
  26. package/dist/clients/progress.cjs.map +1 -1
  27. package/dist/clients/progress.d.ts.map +1 -0
  28. package/dist/clients/progress.js +5 -3
  29. package/dist/clients/progress.js.map +1 -1
  30. package/dist/clients/upload.cjs +22 -9
  31. package/dist/clients/upload.cjs.map +1 -1
  32. package/dist/clients/upload.d.ts.map +1 -0
  33. package/dist/clients/upload.js +18 -5
  34. package/dist/clients/upload.js.map +1 -1
  35. package/dist/drivers/download_blob/blob_key.cjs +3 -4
  36. package/dist/drivers/download_blob/blob_key.cjs.map +1 -1
  37. package/dist/drivers/download_blob/blob_key.js +2 -3
  38. package/dist/drivers/download_blob/blob_key.js.map +1 -1
  39. package/dist/drivers/download_blob/download_blob.cjs +13 -14
  40. package/dist/drivers/download_blob/download_blob.cjs.map +1 -1
  41. package/dist/drivers/download_blob/download_blob.d.ts.map +1 -0
  42. package/dist/drivers/download_blob/download_blob.js +1 -2
  43. package/dist/drivers/download_blob/download_blob.js.map +1 -1
  44. package/dist/drivers/download_blob/download_blob_task.cjs +4 -5
  45. package/dist/drivers/download_blob/download_blob_task.cjs.map +1 -1
  46. package/dist/drivers/download_blob/download_blob_task.js +1 -2
  47. package/dist/drivers/download_blob/download_blob_task.js.map +1 -1
  48. package/dist/drivers/download_blob/sparse_cache/cache.cjs +4 -5
  49. package/dist/drivers/download_blob/sparse_cache/cache.cjs.map +1 -1
  50. package/dist/drivers/download_blob/sparse_cache/cache.js +1 -2
  51. package/dist/drivers/download_blob/sparse_cache/cache.js.map +1 -1
  52. package/dist/drivers/download_blob/sparse_cache/file.cjs +2 -3
  53. package/dist/drivers/download_blob/sparse_cache/file.cjs.map +1 -1
  54. package/dist/drivers/download_blob/sparse_cache/file.js +1 -2
  55. package/dist/drivers/download_blob/sparse_cache/file.js.map +1 -1
  56. package/dist/drivers/download_blob/sparse_cache/ranges.cjs +3 -4
  57. package/dist/drivers/download_blob/sparse_cache/ranges.cjs.map +1 -1
  58. package/dist/drivers/download_blob/sparse_cache/ranges.js +1 -2
  59. package/dist/drivers/download_blob/sparse_cache/ranges.js.map +1 -1
  60. package/dist/drivers/download_blob_url/driver.cjs +8 -9
  61. package/dist/drivers/download_blob_url/driver.cjs.map +1 -1
  62. package/dist/drivers/download_blob_url/driver.d.ts.map +1 -0
  63. package/dist/drivers/download_blob_url/driver.js +1 -2
  64. package/dist/drivers/download_blob_url/driver.js.map +1 -1
  65. package/dist/drivers/download_blob_url/driver_id.cjs +1 -2
  66. package/dist/drivers/download_blob_url/driver_id.cjs.map +1 -1
  67. package/dist/drivers/download_blob_url/driver_id.js +1 -1
  68. package/dist/drivers/download_blob_url/snapshot.cjs +2 -3
  69. package/dist/drivers/download_blob_url/snapshot.cjs.map +1 -1
  70. package/dist/drivers/download_blob_url/snapshot.d.ts +2 -2
  71. package/dist/drivers/download_blob_url/snapshot.d.ts.map +1 -0
  72. package/dist/drivers/download_blob_url/snapshot.js +1 -2
  73. package/dist/drivers/download_blob_url/snapshot.js.map +1 -1
  74. package/dist/drivers/download_blob_url/task.cjs +5 -6
  75. package/dist/drivers/download_blob_url/task.cjs.map +1 -1
  76. package/dist/drivers/download_blob_url/task.d.ts.map +1 -0
  77. package/dist/drivers/download_blob_url/task.js +1 -2
  78. package/dist/drivers/download_blob_url/task.js.map +1 -1
  79. package/dist/drivers/download_url/driver.cjs +7 -8
  80. package/dist/drivers/download_url/driver.cjs.map +1 -1
  81. package/dist/drivers/download_url/driver.d.ts.map +1 -0
  82. package/dist/drivers/download_url/driver.js +1 -2
  83. package/dist/drivers/download_url/driver.js.map +1 -1
  84. package/dist/drivers/download_url/task.cjs +4 -5
  85. package/dist/drivers/download_url/task.cjs.map +1 -1
  86. package/dist/drivers/download_url/task.d.ts.map +1 -0
  87. package/dist/drivers/download_url/task.js +1 -2
  88. package/dist/drivers/download_url/task.js.map +1 -1
  89. package/dist/drivers/helpers/download_local_handle.cjs +1 -2
  90. package/dist/drivers/helpers/download_local_handle.cjs.map +1 -1
  91. package/dist/drivers/helpers/download_local_handle.js +1 -1
  92. package/dist/drivers/helpers/download_remote_handle.cjs +3 -4
  93. package/dist/drivers/helpers/download_remote_handle.cjs.map +1 -1
  94. package/dist/drivers/helpers/download_remote_handle.js +1 -2
  95. package/dist/drivers/helpers/download_remote_handle.js.map +1 -1
  96. package/dist/drivers/helpers/files_cache.cjs +2 -3
  97. package/dist/drivers/helpers/files_cache.cjs.map +1 -1
  98. package/dist/drivers/helpers/files_cache.js +1 -2
  99. package/dist/drivers/helpers/files_cache.js.map +1 -1
  100. package/dist/drivers/helpers/helpers.cjs +1 -2
  101. package/dist/drivers/helpers/helpers.cjs.map +1 -1
  102. package/dist/drivers/helpers/helpers.d.ts.map +1 -0
  103. package/dist/drivers/helpers/helpers.js +1 -1
  104. package/dist/drivers/helpers/helpers.js.map +1 -1
  105. package/dist/drivers/helpers/logs_handle.cjs +2 -3
  106. package/dist/drivers/helpers/logs_handle.cjs.map +1 -1
  107. package/dist/drivers/helpers/logs_handle.js +1 -2
  108. package/dist/drivers/helpers/logs_handle.js.map +1 -1
  109. package/dist/drivers/helpers/ls_remote_import_handle.cjs +4 -6
  110. package/dist/drivers/helpers/ls_remote_import_handle.cjs.map +1 -1
  111. package/dist/drivers/helpers/ls_remote_import_handle.js +3 -5
  112. package/dist/drivers/helpers/ls_remote_import_handle.js.map +1 -1
  113. package/dist/drivers/helpers/ls_storage_entry.cjs +2 -3
  114. package/dist/drivers/helpers/ls_storage_entry.cjs.map +1 -1
  115. package/dist/drivers/helpers/ls_storage_entry.js +1 -2
  116. package/dist/drivers/helpers/ls_storage_entry.js.map +1 -1
  117. package/dist/drivers/helpers/polling_ops.d.ts.map +1 -0
  118. package/dist/drivers/helpers/read_file.cjs +2 -3
  119. package/dist/drivers/helpers/read_file.cjs.map +1 -1
  120. package/dist/drivers/helpers/read_file.js +1 -2
  121. package/dist/drivers/helpers/read_file.js.map +1 -1
  122. package/dist/drivers/logs.cjs +3 -4
  123. package/dist/drivers/logs.cjs.map +1 -1
  124. package/dist/drivers/logs.d.ts.map +1 -0
  125. package/dist/drivers/logs.js +1 -2
  126. package/dist/drivers/logs.js.map +1 -1
  127. package/dist/drivers/logs_stream.cjs +4 -5
  128. package/dist/drivers/logs_stream.cjs.map +1 -1
  129. package/dist/drivers/logs_stream.d.ts.map +1 -0
  130. package/dist/drivers/logs_stream.js +1 -2
  131. package/dist/drivers/logs_stream.js.map +1 -1
  132. package/dist/drivers/ls.cjs +7 -8
  133. package/dist/drivers/ls.cjs.map +1 -1
  134. package/dist/drivers/ls.d.ts.map +1 -0
  135. package/dist/drivers/ls.js +1 -2
  136. package/dist/drivers/ls.js.map +1 -1
  137. package/dist/drivers/types.cjs +2 -3
  138. package/dist/drivers/types.cjs.map +1 -1
  139. package/dist/drivers/types.d.ts +4 -4
  140. package/dist/drivers/types.d.ts.map +1 -0
  141. package/dist/drivers/types.js +1 -2
  142. package/dist/drivers/types.js.map +1 -1
  143. package/dist/drivers/upload.cjs +5 -6
  144. package/dist/drivers/upload.cjs.map +1 -1
  145. package/dist/drivers/upload.d.ts.map +1 -0
  146. package/dist/drivers/upload.js +1 -2
  147. package/dist/drivers/upload.js.map +1 -1
  148. package/dist/drivers/upload_task.cjs +4 -5
  149. package/dist/drivers/upload_task.cjs.map +1 -1
  150. package/dist/drivers/upload_task.d.ts.map +1 -0
  151. package/dist/drivers/upload_task.js +1 -2
  152. package/dist/drivers/upload_task.js.map +1 -1
  153. package/dist/drivers/urls/url.cjs +2 -3
  154. package/dist/drivers/urls/url.cjs.map +1 -1
  155. package/dist/drivers/urls/url.js +1 -2
  156. package/dist/drivers/urls/url.js.map +1 -1
  157. package/dist/drivers/virtual_storages.cjs +2 -3
  158. package/dist/drivers/virtual_storages.cjs.map +1 -1
  159. package/dist/drivers/virtual_storages.d.ts.map +1 -0
  160. package/dist/drivers/virtual_storages.js +1 -2
  161. package/dist/drivers/virtual_storages.js.map +1 -1
  162. package/dist/helpers/download.cjs +3 -4
  163. package/dist/helpers/download.cjs.map +1 -1
  164. package/dist/helpers/download.d.ts.map +1 -0
  165. package/dist/helpers/download.js +1 -2
  166. package/dist/helpers/download.js.map +1 -1
  167. package/dist/helpers/download_errors.cjs +1 -2
  168. package/dist/helpers/download_errors.cjs.map +1 -1
  169. package/dist/helpers/download_errors.d.ts.map +1 -0
  170. package/dist/helpers/download_errors.js +1 -1
  171. package/dist/helpers/validate.cjs +2 -3
  172. package/dist/helpers/validate.cjs.map +1 -1
  173. package/dist/helpers/validate.d.ts.map +1 -0
  174. package/dist/helpers/validate.js +1 -2
  175. package/dist/helpers/validate.js.map +1 -1
  176. package/dist/index.cjs +22 -23
  177. package/dist/index.js +1 -2
  178. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.cjs +32 -27
  179. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.cjs.map +1 -1
  180. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.cjs +4 -5
  181. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.cjs.map +1 -1
  182. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.d.ts +6 -6
  183. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.d.ts.map +1 -0
  184. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.js +2 -3
  185. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.js.map +1 -1
  186. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.d.ts +11 -5
  187. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.d.ts.map +1 -0
  188. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.js +32 -31
  189. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.js.map +1 -1
  190. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.cjs +31 -24
  191. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.cjs.map +1 -1
  192. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.cjs +7 -8
  193. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.cjs.map +1 -1
  194. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.js +5 -6
  195. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.js.map +1 -1
  196. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts +16 -16
  197. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts.map +1 -0
  198. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.js +30 -27
  199. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.js.map +1 -1
  200. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.cjs +42 -28
  201. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.cjs.map +1 -1
  202. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.cjs +4 -5
  203. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.cjs.map +1 -1
  204. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.d.ts +6 -6
  205. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.d.ts.map +1 -0
  206. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.js +2 -3
  207. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.js.map +1 -1
  208. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.d.ts +8 -0
  209. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.d.ts.map +1 -0
  210. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.js +40 -26
  211. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.js.map +1 -1
  212. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.cjs +32 -196
  213. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.cjs.map +1 -1
  214. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.cjs +23 -45
  215. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.cjs.map +1 -1
  216. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.d.ts +40 -70
  217. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.d.ts.map +1 -0
  218. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.js +21 -43
  219. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.js.map +1 -1
  220. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.d.ts +52 -137
  221. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.d.ts.map +1 -0
  222. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.js +31 -195
  223. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.js.map +1 -1
  224. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.cjs +228 -46
  225. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.cjs.map +1 -1
  226. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.cjs +24 -11
  227. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.cjs.map +1 -1
  228. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.js +22 -9
  229. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.js.map +1 -1
  230. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.d.ts.map +1 -0
  231. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.js +227 -45
  232. package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.js.map +1 -1
  233. package/dist/proto-grpc/google/protobuf/duration.cjs +2 -3
  234. package/dist/proto-grpc/google/protobuf/duration.cjs.map +1 -1
  235. package/dist/proto-grpc/google/protobuf/duration.d.ts.map +1 -0
  236. package/dist/proto-grpc/google/protobuf/duration.js +1 -2
  237. package/dist/proto-grpc/google/protobuf/duration.js.map +1 -1
  238. package/dist/proto-grpc/google/protobuf/timestamp.cjs +2 -3
  239. package/dist/proto-grpc/google/protobuf/timestamp.cjs.map +1 -1
  240. package/dist/proto-grpc/google/protobuf/timestamp.d.ts.map +1 -0
  241. package/dist/proto-grpc/google/protobuf/timestamp.js +1 -2
  242. package/dist/proto-grpc/google/protobuf/timestamp.js.map +1 -1
  243. package/dist/proto-rest/downloadapi.d.ts +8 -3
  244. package/dist/proto-rest/downloadapi.d.ts.map +1 -0
  245. package/dist/proto-rest/index.d.ts.map +1 -0
  246. package/dist/proto-rest/progressapi.d.ts +4 -2
  247. package/dist/proto-rest/progressapi.d.ts.map +1 -0
  248. package/dist/proto-rest/streamingapi.d.ts +64 -203
  249. package/dist/proto-rest/streamingapi.d.ts.map +1 -0
  250. package/package.json +10 -10
  251. package/src/clients/download.ts +1 -0
  252. package/src/clients/logs.ts +2 -0
  253. package/src/clients/ls_api.ts +1 -0
  254. package/src/clients/progress.ts +1 -1
  255. package/src/clients/upload.ts +14 -1
  256. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts +2 -2
  257. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts +24 -11
  258. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.ts +8 -8
  259. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts +31 -18
  260. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts +2 -2
  261. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts +23 -1
  262. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.ts +39 -78
  263. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts +72 -263
  264. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.ts +41 -14
  265. package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts +286 -44
  266. package/src/proto-grpc/google/protobuf/descriptor.ts +2 -5
  267. package/src/proto-rest/downloadapi.ts +8 -3
  268. package/src/proto-rest/lsapi.ts +23 -18
  269. package/src/proto-rest/progressapi.ts +4 -0
  270. package/src/proto-rest/streamingapi.ts +65 -211
  271. package/src/proto-rest/uploadapi.ts +141 -39
@@ -1 +1 @@
1
- {"version":3,"file":"upload.js","names":[],"sources":["../../src/drivers/upload.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { ResourceId, ResourceType } from \"@milaboratories/pl-client\";\nimport type { Watcher, ComputableCtx } from \"@milaboratories/computable\";\nimport { Computable, PollingComputableHooks } from \"@milaboratories/computable\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { asyncPool, TaskProcessor } from \"@milaboratories/ts-helpers\";\nimport type * as sdk from \"@milaboratories/pl-model-common\";\nimport type { ClientProgress } from \"../clients/progress\";\nimport type { ClientUpload } from \"../clients/upload\";\nimport type { PlTreeEntry, PlTreeEntryAccessor, PlTreeNodeAccessor } from \"@milaboratories/pl-tree\";\nimport {\n isPlTreeEntry,\n isPlTreeEntryAccessor,\n makeResourceSnapshot,\n} from \"@milaboratories/pl-tree\";\nimport { scheduler } from \"node:timers/promises\";\nimport type { PollingOps } from \"./helpers/polling_ops\";\nimport type { ImportResourceSnapshot } from \"./types\";\nimport { IndexResourceSnapshot, UploadResourceSnapshot } from \"./types\";\nimport { nonRecoverableError, UploadTask } from \"./upload_task\";\nimport { WrongResourceTypeError } from \"./helpers/helpers\";\n\nexport function makeBlobImportSnapshot(\n entryOrAccessor: PlTreeEntry | PlTreeNodeAccessor | PlTreeEntryAccessor,\n ctx: ComputableCtx,\n): ImportResourceSnapshot {\n const node = isPlTreeEntry(entryOrAccessor)\n ? ctx.accessor(entryOrAccessor).node()\n : isPlTreeEntryAccessor(entryOrAccessor)\n ? entryOrAccessor.node()\n : entryOrAccessor;\n\n if (node.resourceType.name.startsWith(\"BlobUpload\"))\n return makeResourceSnapshot(node, UploadResourceSnapshot);\n return makeResourceSnapshot(node, IndexResourceSnapshot);\n}\n\nexport type UploadDriverOps = PollingOps & {\n /** How much parts of a file can be multipart-uploaded to S3 at once. */\n nConcurrentPartUploads: number;\n /** How much upload/indexing statuses of blobs can the driver ask\n * from the platform gRPC at once. */\n nConcurrentGetProgresses: number;\n};\n\n// TODO: add abort signal to Upload Tasks.\n\n/** Uploads blobs in a queue and holds counters, so it can stop not-needed\n * uploads.\n * Handles both Index and Upload blobs,\n * the client needs to pass concrete blobs from `handle` field. */\nexport class UploadDriver {\n private readonly idToProgress: Map<ResourceId, UploadTask> = new Map();\n\n /** Holds a queue that upload blobs. */\n private readonly uploadQueue: TaskProcessor;\n private readonly hooks: PollingComputableHooks;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly signer: Signer,\n private readonly clientBlob: ClientUpload,\n private readonly clientProgress: ClientProgress,\n private readonly opts: UploadDriverOps = {\n nConcurrentPartUploads: 10,\n nConcurrentGetProgresses: 10,\n pollingInterval: 1000,\n stopPollingDelay: 1000,\n },\n ) {\n this.uploadQueue = new TaskProcessor(this.logger, 1, {\n type: \"exponentialWithMaxDelayBackoff\",\n initialDelay: 20,\n maxDelay: 15000, // 15 seconds\n backoffMultiplier: 1.5,\n jitter: 0.5,\n });\n\n this.hooks = new PollingComputableHooks(\n () => this.startUpdating(),\n () => this.stopUpdating(),\n { stopDebounce: opts.stopPollingDelay },\n (resolve, reject) => this.scheduleOnNextState(resolve, reject),\n );\n }\n\n /** Returns a progress id and schedules an upload task if it's necessary. */\n getProgressId(\n handleResource: ImportResourceSnapshot | PlTreeEntry,\n ): Computable<sdk.ImportProgress>;\n getProgressId(\n handleResource: ImportResourceSnapshot | PlTreeEntry,\n ctx: ComputableCtx,\n ): sdk.ImportProgress;\n getProgressId(\n handleResource: ImportResourceSnapshot | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<sdk.ImportProgress> | sdk.ImportProgress {\n if (ctx == undefined) return Computable.make((ctx) => this.getProgressId(handleResource, ctx));\n\n const rInfo: ImportResourceSnapshot = isPlTreeEntry(handleResource)\n ? makeBlobImportSnapshot(handleResource, ctx)\n : handleResource;\n\n const callerId = randomUUID();\n ctx.attacheHooks(this.hooks);\n ctx.addOnDestroy(() => this.release(rInfo.id, callerId));\n\n const result = this.getProgressIdNoCtx(ctx.watcher, rInfo, callerId);\n\n return result;\n }\n\n private getProgressIdNoCtx(\n w: Watcher,\n res: ImportResourceSnapshot,\n callerId: string,\n ): sdk.ImportProgress {\n validateResourceType(\"getProgressId\", res.type);\n\n const task = this.idToProgress.get(res.id);\n\n if (task != undefined) {\n task.setDoneIfOutputSet(res);\n return task.getProgress(w, callerId);\n }\n\n const newTask = new UploadTask(\n this.logger,\n this.clientBlob,\n this.clientProgress,\n this.opts.nConcurrentPartUploads,\n this.signer,\n res,\n );\n\n this.idToProgress.set(res.id, newTask);\n\n if (newTask.shouldScheduleUpload()) {\n this.uploadQueue.push({\n fn: () => newTask.uploadBlobTask(),\n recoverableErrorPredicate: (e) => !nonRecoverableError(e),\n });\n }\n\n newTask.setDoneIfOutputSet(res);\n return newTask.getProgress(w, callerId);\n }\n\n /** Decrement counters for the file and remove an uploading if counter == 0. */\n private async release(id: ResourceId, callerId: string) {\n const task = this.idToProgress.get(id);\n if (task === undefined) return;\n\n const deleted = task.decCounter(callerId);\n if (deleted) this.idToProgress.delete(id);\n }\n\n /** Must be called when the driver is closing. */\n public async releaseAll() {\n this.uploadQueue.stop();\n }\n\n private scheduledOnNextState: ScheduledRefresh[] = [];\n\n private scheduleOnNextState(resolve: () => void, reject: (err: any) => void): void {\n this.scheduledOnNextState.push({ resolve, reject });\n }\n\n /** Called from observer */\n private startUpdating(): void {\n this.keepRunning = true;\n if (this.currentLoop === undefined) this.currentLoop = this.mainLoop();\n }\n\n /** Called from observer */\n private stopUpdating(): void {\n this.keepRunning = false;\n }\n\n /** Stops polling loop and waits for it to finish */\n public async terminate(): Promise<void> {\n this.stopUpdating();\n if (this.currentLoop !== undefined) {\n await this.currentLoop;\n }\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.terminate();\n }\n\n /** If true, main loop will continue polling pl state. */\n private keepRunning = false;\n /** Actual state of main loop. */\n private currentLoop: Promise<void> | undefined = undefined;\n\n private async mainLoop() {\n while (this.keepRunning) {\n const toNotify = this.scheduledOnNextState;\n this.scheduledOnNextState = [];\n\n try {\n await asyncPool(\n this.opts.nConcurrentGetProgresses,\n this.getAllNotDoneProgresses().map((p) => async () => await p.updateStatus()),\n );\n\n toNotify.forEach((n) => n.resolve());\n } catch (e: any) {\n console.error(e);\n toNotify.forEach((n) => n.reject(e));\n }\n\n if (!this.keepRunning) break;\n await scheduler.wait(this.opts.pollingInterval);\n }\n\n this.currentLoop = undefined;\n }\n\n private getAllNotDoneProgresses(): Array<UploadTask> {\n return Array.from(this.idToProgress.entries())\n .filter(([_, p]) => !isProgressDone(p.progress))\n .map(([_, p]) => p);\n }\n}\n\nfunction isProgressDone(p: sdk.ImportProgress) {\n return p.done && (p.status?.progress ?? 0.0) >= 1.0;\n}\n\ntype ScheduledRefresh = {\n resolve: () => void;\n reject: (err: any) => void;\n};\n\nfunction validateResourceType(methodName: string, rType: ResourceType) {\n if (!rType.name.startsWith(\"BlobUpload\") && !rType.name.startsWith(\"BlobIndex\")) {\n throw new WrongResourceTypeError(\n `${methodName}: wrong resource type: ${rType.name}, ` +\n `expected: a resource of either type 'BlobUpload' or 'BlobIndex'.`,\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"upload.js","names":[],"sources":["../../src/drivers/upload.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { ResourceId, ResourceType } from \"@milaboratories/pl-client\";\nimport type { Watcher, ComputableCtx } from \"@milaboratories/computable\";\nimport { Computable, PollingComputableHooks } from \"@milaboratories/computable\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { asyncPool, TaskProcessor } from \"@milaboratories/ts-helpers\";\nimport type * as sdk from \"@milaboratories/pl-model-common\";\nimport type { ClientProgress } from \"../clients/progress\";\nimport type { ClientUpload } from \"../clients/upload\";\nimport type { PlTreeEntry, PlTreeEntryAccessor, PlTreeNodeAccessor } from \"@milaboratories/pl-tree\";\nimport {\n isPlTreeEntry,\n isPlTreeEntryAccessor,\n makeResourceSnapshot,\n} from \"@milaboratories/pl-tree\";\nimport { scheduler } from \"node:timers/promises\";\nimport type { PollingOps } from \"./helpers/polling_ops\";\nimport type { ImportResourceSnapshot } from \"./types\";\nimport { IndexResourceSnapshot, UploadResourceSnapshot } from \"./types\";\nimport { nonRecoverableError, UploadTask } from \"./upload_task\";\nimport { WrongResourceTypeError } from \"./helpers/helpers\";\n\nexport function makeBlobImportSnapshot(\n entryOrAccessor: PlTreeEntry | PlTreeNodeAccessor | PlTreeEntryAccessor,\n ctx: ComputableCtx,\n): ImportResourceSnapshot {\n const node = isPlTreeEntry(entryOrAccessor)\n ? ctx.accessor(entryOrAccessor).node()\n : isPlTreeEntryAccessor(entryOrAccessor)\n ? entryOrAccessor.node()\n : entryOrAccessor;\n\n if (node.resourceType.name.startsWith(\"BlobUpload\"))\n return makeResourceSnapshot(node, UploadResourceSnapshot);\n return makeResourceSnapshot(node, IndexResourceSnapshot);\n}\n\nexport type UploadDriverOps = PollingOps & {\n /** How much parts of a file can be multipart-uploaded to S3 at once. */\n nConcurrentPartUploads: number;\n /** How much upload/indexing statuses of blobs can the driver ask\n * from the platform gRPC at once. */\n nConcurrentGetProgresses: number;\n};\n\n// TODO: add abort signal to Upload Tasks.\n\n/** Uploads blobs in a queue and holds counters, so it can stop not-needed\n * uploads.\n * Handles both Index and Upload blobs,\n * the client needs to pass concrete blobs from `handle` field. */\nexport class UploadDriver {\n private readonly idToProgress: Map<ResourceId, UploadTask> = new Map();\n\n /** Holds a queue that upload blobs. */\n private readonly uploadQueue: TaskProcessor;\n private readonly hooks: PollingComputableHooks;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly signer: Signer,\n private readonly clientBlob: ClientUpload,\n private readonly clientProgress: ClientProgress,\n private readonly opts: UploadDriverOps = {\n nConcurrentPartUploads: 10,\n nConcurrentGetProgresses: 10,\n pollingInterval: 1000,\n stopPollingDelay: 1000,\n },\n ) {\n this.uploadQueue = new TaskProcessor(this.logger, 1, {\n type: \"exponentialWithMaxDelayBackoff\",\n initialDelay: 20,\n maxDelay: 15000, // 15 seconds\n backoffMultiplier: 1.5,\n jitter: 0.5,\n });\n\n this.hooks = new PollingComputableHooks(\n () => this.startUpdating(),\n () => this.stopUpdating(),\n { stopDebounce: opts.stopPollingDelay },\n (resolve, reject) => this.scheduleOnNextState(resolve, reject),\n );\n }\n\n /** Returns a progress id and schedules an upload task if it's necessary. */\n getProgressId(\n handleResource: ImportResourceSnapshot | PlTreeEntry,\n ): Computable<sdk.ImportProgress>;\n getProgressId(\n handleResource: ImportResourceSnapshot | PlTreeEntry,\n ctx: ComputableCtx,\n ): sdk.ImportProgress;\n getProgressId(\n handleResource: ImportResourceSnapshot | PlTreeEntry,\n ctx?: ComputableCtx,\n ): Computable<sdk.ImportProgress> | sdk.ImportProgress {\n if (ctx == undefined) return Computable.make((ctx) => this.getProgressId(handleResource, ctx));\n\n const rInfo: ImportResourceSnapshot = isPlTreeEntry(handleResource)\n ? makeBlobImportSnapshot(handleResource, ctx)\n : handleResource;\n\n const callerId = randomUUID();\n ctx.attacheHooks(this.hooks);\n ctx.addOnDestroy(() => this.release(rInfo.id, callerId));\n\n const result = this.getProgressIdNoCtx(ctx.watcher, rInfo, callerId);\n\n return result;\n }\n\n private getProgressIdNoCtx(\n w: Watcher,\n res: ImportResourceSnapshot,\n callerId: string,\n ): sdk.ImportProgress {\n validateResourceType(\"getProgressId\", res.type);\n\n const task = this.idToProgress.get(res.id);\n\n if (task != undefined) {\n task.setDoneIfOutputSet(res);\n return task.getProgress(w, callerId);\n }\n\n const newTask = new UploadTask(\n this.logger,\n this.clientBlob,\n this.clientProgress,\n this.opts.nConcurrentPartUploads,\n this.signer,\n res,\n );\n\n this.idToProgress.set(res.id, newTask);\n\n if (newTask.shouldScheduleUpload()) {\n this.uploadQueue.push({\n fn: () => newTask.uploadBlobTask(),\n recoverableErrorPredicate: (e) => !nonRecoverableError(e),\n });\n }\n\n newTask.setDoneIfOutputSet(res);\n return newTask.getProgress(w, callerId);\n }\n\n /** Decrement counters for the file and remove an uploading if counter == 0. */\n private async release(id: ResourceId, callerId: string) {\n const task = this.idToProgress.get(id);\n if (task === undefined) return;\n\n const deleted = task.decCounter(callerId);\n if (deleted) this.idToProgress.delete(id);\n }\n\n /** Must be called when the driver is closing. */\n public async releaseAll() {\n this.uploadQueue.stop();\n }\n\n private scheduledOnNextState: ScheduledRefresh[] = [];\n\n private scheduleOnNextState(resolve: () => void, reject: (err: any) => void): void {\n this.scheduledOnNextState.push({ resolve, reject });\n }\n\n /** Called from observer */\n private startUpdating(): void {\n this.keepRunning = true;\n if (this.currentLoop === undefined) this.currentLoop = this.mainLoop();\n }\n\n /** Called from observer */\n private stopUpdating(): void {\n this.keepRunning = false;\n }\n\n /** Stops polling loop and waits for it to finish */\n public async terminate(): Promise<void> {\n this.stopUpdating();\n if (this.currentLoop !== undefined) {\n await this.currentLoop;\n }\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.terminate();\n }\n\n /** If true, main loop will continue polling pl state. */\n private keepRunning = false;\n /** Actual state of main loop. */\n private currentLoop: Promise<void> | undefined = undefined;\n\n private async mainLoop() {\n while (this.keepRunning) {\n const toNotify = this.scheduledOnNextState;\n this.scheduledOnNextState = [];\n\n try {\n await asyncPool(\n this.opts.nConcurrentGetProgresses,\n this.getAllNotDoneProgresses().map((p) => async () => await p.updateStatus()),\n );\n\n toNotify.forEach((n) => n.resolve());\n } catch (e: any) {\n console.error(e);\n toNotify.forEach((n) => n.reject(e));\n }\n\n if (!this.keepRunning) break;\n await scheduler.wait(this.opts.pollingInterval);\n }\n\n this.currentLoop = undefined;\n }\n\n private getAllNotDoneProgresses(): Array<UploadTask> {\n return Array.from(this.idToProgress.entries())\n .filter(([_, p]) => !isProgressDone(p.progress))\n .map(([_, p]) => p);\n }\n}\n\nfunction isProgressDone(p: sdk.ImportProgress) {\n return p.done && (p.status?.progress ?? 0.0) >= 1.0;\n}\n\ntype ScheduledRefresh = {\n resolve: () => void;\n reject: (err: any) => void;\n};\n\nfunction validateResourceType(methodName: string, rType: ResourceType) {\n if (!rType.name.startsWith(\"BlobUpload\") && !rType.name.startsWith(\"BlobIndex\")) {\n throw new WrongResourceTypeError(\n `${methodName}: wrong resource type: ${rType.name}, ` +\n `expected: a resource of either type 'BlobUpload' or 'BlobIndex'.`,\n );\n }\n}\n"],"mappings":"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"}
@@ -1,12 +1,11 @@
1
- const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
- const require_upload = require('../clients/upload.cjs');
3
- const require_types = require('./types.cjs');
1
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_upload = require("../clients/upload.cjs");
3
+ const require_types = require("./types.cjs");
4
4
  let _milaboratories_pl_client = require("@milaboratories/pl-client");
5
5
  let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
6
6
  let _milaboratories_computable = require("@milaboratories/computable");
7
7
  let node_assert = require("node:assert");
8
8
  node_assert = require_runtime.__toESM(node_assert);
9
-
10
9
  //#region src/drivers/upload_task.ts
11
10
  /** Holds all info needed to upload a file and a status of uploading
12
11
  * and indexing. Also, has a method to update a status of the progress.
@@ -255,7 +254,6 @@ function decreaseConcurrency(logger, current, min) {
255
254
  if (newConcurrency != current) logger.info(`uploadTask.decreaseConcurrency: decreased from ${current} to ${newConcurrency}`);
256
255
  return newConcurrency;
257
256
  }
258
-
259
257
  //#endregion
260
258
  exports.UploadTask = UploadTask;
261
259
  exports.isMyUpload = isMyUpload;
@@ -264,4 +262,5 @@ exports.isTerminalUploadError = isTerminalUploadError;
264
262
  exports.isUpload = isUpload;
265
263
  exports.nonRecoverableError = nonRecoverableError;
266
264
  exports.uploadBlob = uploadBlob;
265
+
267
266
  //# sourceMappingURL=upload_task.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload_task.cjs","names":["ChangeSource","CallersCounter","ImportFileHandleUploadData","MTimeError","UnexpectedEOF","NoFileForUploading","BadRequestError"],"sources":["../../src/drivers/upload_task.ts"],"sourcesContent":["import type { Watcher } from \"@milaboratories/computable\";\nimport { ChangeSource } from \"@milaboratories/computable\";\nimport {\n isTimeoutError,\n resourceIdToString,\n stringifyWithResourceId,\n} from \"@milaboratories/pl-client\";\nimport type * as sdk from \"@milaboratories/pl-model-common\";\nimport type { AsyncPoolController, MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { asyncPool, CallersCounter } from \"@milaboratories/ts-helpers\";\nimport type { ClientProgress, ProgressStatus } from \"../clients/progress\";\nimport type { ClientUpload } from \"../clients/upload\";\nimport { BadRequestError, MTimeError, NoFileForUploading, UnexpectedEOF } from \"../clients/upload\";\nimport type { ImportResourceSnapshot } from \"./types\";\nimport { ImportFileHandleUploadData } from \"./types\";\nimport assert from \"node:assert\";\nimport { ResourceInfo } from \"@milaboratories/pl-tree\";\n\n/** Holds all info needed to upload a file and a status of uploading\n * and indexing. Also, has a method to update a status of the progress.\n * And holds a change source. */\nexport class UploadTask {\n private readonly change: ChangeSource = new ChangeSource();\n private readonly counter: CallersCounter = new CallersCounter();\n private nMaxUploads: number;\n private nPartsWithThisUploadSpeed = 0;\n private nPartsToIncreaseUpload = 10; // how many parts we have to wait to increase concurrency, 50 mb, 10 parts by 5 mb each.\n\n /** If this is upload progress this field will be defined */\n private uploadData?: ImportFileHandleUploadData;\n public progress: sdk.ImportProgress;\n\n /** If failed, then getting a progress is terminally failed. */\n public failed?: boolean;\n\n /** True if the blob was existed.\n * At this case, the task will show progress == 1.0. */\n private alreadyExisted = false;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly clientBlob: ClientUpload,\n private readonly clientProgress: ClientProgress,\n private readonly maxNConcurrentPartsUpload: number,\n signer: Signer,\n public readonly res: ImportResourceSnapshot,\n ) {\n this.nMaxUploads = this.maxNConcurrentPartsUpload;\n const { uploadData, progress } = newProgress(res, signer);\n this.uploadData = uploadData;\n this.progress = progress;\n }\n\n public getProgress(w: Watcher, callerId: string) {\n this.incCounter(w, callerId);\n\n if (this.failed) {\n this.logger.error(`Uploading terminally failed: ${this.progress.lastError}`);\n throw new Error(this.progress.lastError);\n }\n\n return cloneProgress(this.progress);\n }\n\n public shouldScheduleUpload(): boolean {\n return isMyUpload(this.progress);\n }\n\n /** Uploads a blob if it's not BlobIndex. */\n public async uploadBlobTask() {\n try {\n await uploadBlob(\n this.logger,\n this.clientBlob,\n this.res,\n this.uploadData!,\n this.isComputableDone.bind(this),\n {\n nPartsWithThisUploadSpeed: this.nPartsWithThisUploadSpeed,\n nPartsToIncreaseUpload: this.nPartsToIncreaseUpload,\n currentSpeed: this.nMaxUploads,\n maxSpeed: this.maxNConcurrentPartsUpload,\n },\n );\n this.change.markChanged(`blob upload for ${resourceIdToString(this.res.id)} finished`);\n } catch (e: any) {\n if (isTerminalUploadError(e)) {\n this.logger.warn(`terminal error while uploading a blob: ${e}`);\n this.change.markChanged(\n `blob upload for ${resourceIdToString(this.res.id)} aborted: ${e.code}`,\n );\n this.setDone(true);\n\n return;\n }\n\n this.logger.error(`error while uploading a blob: ${e}`);\n this.change.markChanged(`blob upload for ${resourceIdToString(this.res.id)} failed`);\n\n if (nonRecoverableError(e)) {\n this.setTerminalError(e);\n return;\n }\n\n this.setRetriableError(e);\n\n if (isHeadersTimeoutError(e)) {\n // we probably have a slow internet, we need to slow things a bit.\n this.nMaxUploads = decreaseConcurrency(this.logger, this.nMaxUploads, 1);\n }\n\n throw e;\n }\n }\n\n public async updateStatus() {\n try {\n // we do it with timeout in case we have slow internet.\n const status = await this.clientProgress.getStatus(this.res, { timeout: 10000 });\n\n const oldStatus = this.progress.status;\n const newStatus = doneProgressIfExisted(this.alreadyExisted, protoToStatus(status));\n this.progress.status = newStatus;\n this.setDone(status.done);\n\n if (status.done || this.progress.status.progress != oldStatus?.progress) {\n this.change.markChanged(`upload status for ${resourceIdToString(this.res.id)} changed`);\n }\n } catch (e: any) {\n this.setRetriableError(e);\n\n if (isTimeoutError(e)) {\n this.logger.warn(`deadline exceeded while getting a status of BlobImport: ${e.message}`);\n return;\n }\n\n if (isTerminalUploadError(e)) {\n this.logger.warn(\n `terminal error while updating BlobImport status: ${e}, ${stringifyWithResourceId(this.res)}`,\n );\n this.change.markChanged(\n `upload status for ${resourceIdToString(this.res.id)} aborted: ${e.code}`,\n );\n this.setDone(true);\n return;\n }\n\n this.logger.error(`retryable error while updating a status of BlobImport: ${e}`);\n // It was a terminal error, but when a connection drops,\n // this will stop the whole task, so we make it retryable.\n // It was like that:\n // this.change.markChanged();\n // this.setTerminalError(e);\n }\n }\n\n /** Set non-terminal error, that task can be retried. */\n private setRetriableError(e: unknown) {\n this.progress.lastError = String(e);\n }\n\n /** Set a terminal error, the task will throw a error instead of a progress. */\n private setTerminalError(e: unknown) {\n this.progress.lastError = String(e);\n this.progress.done = false;\n this.failed = true;\n }\n\n public setDoneIfOutputSet(res: ImportResourceSnapshot) {\n if (isImportResourceOutputSet(res)) {\n this.setDone(true);\n this.alreadyExisted = true;\n }\n }\n\n private setDone(done: boolean) {\n this.progress.done = done;\n if (done) this.progress.lastError = undefined;\n }\n\n public incCounter(w: Watcher, callerId: string) {\n this.change.attachWatcher(w);\n this.counter.inc(callerId);\n }\n\n public decCounter(callerId: string) {\n return this.counter.dec(callerId);\n }\n\n private isComputableDone() {\n return this.counter.isZero();\n }\n}\n\n/** Uploads a blob if it's not BlobIndex. */\nexport async function uploadBlob(\n logger: MiLogger,\n clientBlob: ClientUpload,\n res: ResourceInfo,\n uploadData: ImportFileHandleUploadData,\n isDoneFn: () => boolean,\n speed: {\n nPartsWithThisUploadSpeed: number;\n nPartsToIncreaseUpload: number;\n currentSpeed: number;\n maxSpeed: number;\n },\n) {\n assert(isUpload(res), \"the upload operation can be done only for BlobUploads\");\n const timeout = 10000; // 10 sec instead of standard 5 sec, things might be slow with a slow connection.\n\n if (isDoneFn()) return;\n const parts = await clientBlob.initUpload(res, { timeout });\n logger.info(\n `started to upload blob ${res.id},` +\n ` parts overall: ${parts.overall}, parts remained: ${parts.toUpload.length},` +\n ` number of concurrent uploads: ${speed.currentSpeed}`,\n );\n\n const partUploadFn = (part: bigint) => async (controller: AsyncPoolController) => {\n if (isDoneFn()) return;\n await clientBlob.partUpload(\n res,\n uploadData.localPath,\n BigInt(uploadData.modificationTime),\n part,\n parts.checksumAlgorithm,\n parts.checksumHeader,\n { timeout },\n );\n logger.info(`uploaded chunk ${part}/${parts.overall} of resource: ${res.id}`);\n\n // if we had a network freeze, it will be increased slowly.\n speed.nPartsWithThisUploadSpeed++;\n if (speed.nPartsWithThisUploadSpeed >= speed.nPartsToIncreaseUpload) {\n speed.nPartsWithThisUploadSpeed = 0;\n speed.currentSpeed = increaseConcurrency(logger, speed.currentSpeed, speed.maxSpeed);\n controller.setConcurrency(speed.currentSpeed);\n }\n };\n\n await asyncPool(speed.currentSpeed, parts.toUpload.map(partUploadFn));\n\n if (isDoneFn()) return;\n await clientBlob.finalize(res, { timeout });\n\n logger.info(`uploading of resource ${res.id} finished.`);\n}\n\nfunction newProgress(res: ImportResourceSnapshot, signer: Signer) {\n let isUploadSignMatch: boolean | undefined;\n let uploadData: ImportFileHandleUploadData | undefined;\n if (isUpload(res)) {\n uploadData = ImportFileHandleUploadData.parse(res.data);\n isUploadSignMatch = isSignMatch(signer, uploadData.localPath, uploadData.pathSignature);\n }\n\n return {\n uploadData: uploadData,\n progress: {\n done: false,\n status: undefined,\n isUpload: isUpload(res),\n isUploadSignMatch: isUploadSignMatch,\n lastError: undefined,\n } satisfies sdk.ImportProgress,\n };\n}\n\n/** Returns true if we need to upload the blob that got from this progress. */\nexport function isMyUpload(p: sdk.ImportProgress): boolean {\n return p.isUpload && (p.isUploadSignMatch ?? false);\n}\n\n/** Creates a deep copy of progress,\n * since we do not want to pass a mutable object\n * to API, it led to bugs before.\n * We do not use '...' cloning syntax\n * for the compiler to fail here if we change API. */\nfunction cloneProgress(progress: sdk.ImportProgress): sdk.ImportProgress {\n const cloned: sdk.ImportProgress = {\n done: progress.done,\n isUpload: progress.isUpload,\n isUploadSignMatch: progress.isUploadSignMatch,\n lastError: progress.lastError,\n };\n\n if (progress.status)\n cloned.status = {\n progress: progress.status.progress,\n bytesProcessed: progress.status.bytesProcessed,\n bytesTotal: progress.status.bytesTotal,\n };\n\n return progress;\n}\n\nfunction isImportResourceOutputSet(res: ImportResourceSnapshot) {\n return \"blob\" in res.fields\n ? res.fields.blob !== undefined\n : res.fields.incarnation !== undefined;\n}\n\nexport function isUpload(res: ResourceInfo) {\n return res.type.name.startsWith(\"BlobUpload\");\n}\n\nexport function isSignMatch(signer: Signer, path: string, signature: string): boolean {\n try {\n signer.verify(path, signature);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction protoToStatus(proto: ProgressStatus): sdk.ImportStatus {\n return {\n progress: proto.progress ?? 0,\n bytesProcessed: Number(proto.bytesProcessed),\n bytesTotal: Number(proto.bytesTotal),\n };\n}\n\n/** Special hack: if we didn't even start to upload the blob\n * to backend because the result was already there,\n * the backend does show us a status that nothing were uploaded,\n * but we need to show the client that everything is OK.\n * Thus, here we set progress to be 1.0 and all bytes are become processed. */\nfunction doneProgressIfExisted(alreadyExisted: boolean, status: sdk.ImportStatus) {\n if (alreadyExisted && status.bytesTotal != 0 && status.bytesProcessed == 0) {\n return {\n progress: 1.0,\n bytesProcessed: Number(status.bytesTotal),\n bytesTotal: Number(status.bytesTotal),\n };\n }\n\n return status;\n}\n\nexport function isTerminalUploadError(e: any) {\n if (e?.name !== \"RpcError\") return false;\n switch (e.code) {\n case \"NOT_FOUND\":\n case \"ABORTED\":\n case \"ALREADY_EXISTS\":\n return true;\n default:\n return false;\n }\n}\n\nexport function nonRecoverableError(e: any) {\n return (\n e instanceof MTimeError ||\n e instanceof UnexpectedEOF ||\n e instanceof NoFileForUploading ||\n e instanceof BadRequestError\n );\n}\n\nfunction isHeadersTimeoutError(e: any) {\n return (e as Error)?.message.includes(`UND_ERR_HEADERS_TIMEOUT`);\n}\n\n/** It's called for every upload success so if everyone is succeeded, we'll double the concurrency. */\nfunction increaseConcurrency(logger: MiLogger, current: number, max: number): number {\n const newConcurrency = Math.min(current + 2, max);\n if (newConcurrency != current)\n logger.info(`uploadTask.increaseConcurrency: increased from ${current} to ${newConcurrency}`);\n\n return newConcurrency;\n}\n\n/** When a error happens, this will half the concurrency level, so the next time\n * we'll try to upload blobs slower. */\nfunction decreaseConcurrency(logger: MiLogger, current: number, min: number): number {\n const newConcurrency = Math.max(Math.round(current / 2), min);\n if (newConcurrency != current)\n logger.info(`uploadTask.decreaseConcurrency: decreased from ${current} to ${newConcurrency}`);\n\n return newConcurrency;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"upload_task.cjs","names":["ChangeSource","CallersCounter","ImportFileHandleUploadData","MTimeError","UnexpectedEOF","NoFileForUploading","BadRequestError"],"sources":["../../src/drivers/upload_task.ts"],"sourcesContent":["import type { Watcher } from \"@milaboratories/computable\";\nimport { ChangeSource } from \"@milaboratories/computable\";\nimport {\n isTimeoutError,\n resourceIdToString,\n stringifyWithResourceId,\n} from \"@milaboratories/pl-client\";\nimport type * as sdk from \"@milaboratories/pl-model-common\";\nimport type { AsyncPoolController, MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { asyncPool, CallersCounter } from \"@milaboratories/ts-helpers\";\nimport type { ClientProgress, ProgressStatus } from \"../clients/progress\";\nimport type { ClientUpload } from \"../clients/upload\";\nimport { BadRequestError, MTimeError, NoFileForUploading, UnexpectedEOF } from \"../clients/upload\";\nimport type { ImportResourceSnapshot } from \"./types\";\nimport { ImportFileHandleUploadData } from \"./types\";\nimport assert from \"node:assert\";\nimport { ResourceInfo } from \"@milaboratories/pl-tree\";\n\n/** Holds all info needed to upload a file and a status of uploading\n * and indexing. Also, has a method to update a status of the progress.\n * And holds a change source. */\nexport class UploadTask {\n private readonly change: ChangeSource = new ChangeSource();\n private readonly counter: CallersCounter = new CallersCounter();\n private nMaxUploads: number;\n private nPartsWithThisUploadSpeed = 0;\n private nPartsToIncreaseUpload = 10; // how many parts we have to wait to increase concurrency, 50 mb, 10 parts by 5 mb each.\n\n /** If this is upload progress this field will be defined */\n private uploadData?: ImportFileHandleUploadData;\n public progress: sdk.ImportProgress;\n\n /** If failed, then getting a progress is terminally failed. */\n public failed?: boolean;\n\n /** True if the blob was existed.\n * At this case, the task will show progress == 1.0. */\n private alreadyExisted = false;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly clientBlob: ClientUpload,\n private readonly clientProgress: ClientProgress,\n private readonly maxNConcurrentPartsUpload: number,\n signer: Signer,\n public readonly res: ImportResourceSnapshot,\n ) {\n this.nMaxUploads = this.maxNConcurrentPartsUpload;\n const { uploadData, progress } = newProgress(res, signer);\n this.uploadData = uploadData;\n this.progress = progress;\n }\n\n public getProgress(w: Watcher, callerId: string) {\n this.incCounter(w, callerId);\n\n if (this.failed) {\n this.logger.error(`Uploading terminally failed: ${this.progress.lastError}`);\n throw new Error(this.progress.lastError);\n }\n\n return cloneProgress(this.progress);\n }\n\n public shouldScheduleUpload(): boolean {\n return isMyUpload(this.progress);\n }\n\n /** Uploads a blob if it's not BlobIndex. */\n public async uploadBlobTask() {\n try {\n await uploadBlob(\n this.logger,\n this.clientBlob,\n this.res,\n this.uploadData!,\n this.isComputableDone.bind(this),\n {\n nPartsWithThisUploadSpeed: this.nPartsWithThisUploadSpeed,\n nPartsToIncreaseUpload: this.nPartsToIncreaseUpload,\n currentSpeed: this.nMaxUploads,\n maxSpeed: this.maxNConcurrentPartsUpload,\n },\n );\n this.change.markChanged(`blob upload for ${resourceIdToString(this.res.id)} finished`);\n } catch (e: any) {\n if (isTerminalUploadError(e)) {\n this.logger.warn(`terminal error while uploading a blob: ${e}`);\n this.change.markChanged(\n `blob upload for ${resourceIdToString(this.res.id)} aborted: ${e.code}`,\n );\n this.setDone(true);\n\n return;\n }\n\n this.logger.error(`error while uploading a blob: ${e}`);\n this.change.markChanged(`blob upload for ${resourceIdToString(this.res.id)} failed`);\n\n if (nonRecoverableError(e)) {\n this.setTerminalError(e);\n return;\n }\n\n this.setRetriableError(e);\n\n if (isHeadersTimeoutError(e)) {\n // we probably have a slow internet, we need to slow things a bit.\n this.nMaxUploads = decreaseConcurrency(this.logger, this.nMaxUploads, 1);\n }\n\n throw e;\n }\n }\n\n public async updateStatus() {\n try {\n // we do it with timeout in case we have slow internet.\n const status = await this.clientProgress.getStatus(this.res, { timeout: 10000 });\n\n const oldStatus = this.progress.status;\n const newStatus = doneProgressIfExisted(this.alreadyExisted, protoToStatus(status));\n this.progress.status = newStatus;\n this.setDone(status.done);\n\n if (status.done || this.progress.status.progress != oldStatus?.progress) {\n this.change.markChanged(`upload status for ${resourceIdToString(this.res.id)} changed`);\n }\n } catch (e: any) {\n this.setRetriableError(e);\n\n if (isTimeoutError(e)) {\n this.logger.warn(`deadline exceeded while getting a status of BlobImport: ${e.message}`);\n return;\n }\n\n if (isTerminalUploadError(e)) {\n this.logger.warn(\n `terminal error while updating BlobImport status: ${e}, ${stringifyWithResourceId(this.res)}`,\n );\n this.change.markChanged(\n `upload status for ${resourceIdToString(this.res.id)} aborted: ${e.code}`,\n );\n this.setDone(true);\n return;\n }\n\n this.logger.error(`retryable error while updating a status of BlobImport: ${e}`);\n // It was a terminal error, but when a connection drops,\n // this will stop the whole task, so we make it retryable.\n // It was like that:\n // this.change.markChanged();\n // this.setTerminalError(e);\n }\n }\n\n /** Set non-terminal error, that task can be retried. */\n private setRetriableError(e: unknown) {\n this.progress.lastError = String(e);\n }\n\n /** Set a terminal error, the task will throw a error instead of a progress. */\n private setTerminalError(e: unknown) {\n this.progress.lastError = String(e);\n this.progress.done = false;\n this.failed = true;\n }\n\n public setDoneIfOutputSet(res: ImportResourceSnapshot) {\n if (isImportResourceOutputSet(res)) {\n this.setDone(true);\n this.alreadyExisted = true;\n }\n }\n\n private setDone(done: boolean) {\n this.progress.done = done;\n if (done) this.progress.lastError = undefined;\n }\n\n public incCounter(w: Watcher, callerId: string) {\n this.change.attachWatcher(w);\n this.counter.inc(callerId);\n }\n\n public decCounter(callerId: string) {\n return this.counter.dec(callerId);\n }\n\n private isComputableDone() {\n return this.counter.isZero();\n }\n}\n\n/** Uploads a blob if it's not BlobIndex. */\nexport async function uploadBlob(\n logger: MiLogger,\n clientBlob: ClientUpload,\n res: ResourceInfo,\n uploadData: ImportFileHandleUploadData,\n isDoneFn: () => boolean,\n speed: {\n nPartsWithThisUploadSpeed: number;\n nPartsToIncreaseUpload: number;\n currentSpeed: number;\n maxSpeed: number;\n },\n) {\n assert(isUpload(res), \"the upload operation can be done only for BlobUploads\");\n const timeout = 10000; // 10 sec instead of standard 5 sec, things might be slow with a slow connection.\n\n if (isDoneFn()) return;\n const parts = await clientBlob.initUpload(res, { timeout });\n logger.info(\n `started to upload blob ${res.id},` +\n ` parts overall: ${parts.overall}, parts remained: ${parts.toUpload.length},` +\n ` number of concurrent uploads: ${speed.currentSpeed}`,\n );\n\n const partUploadFn = (part: bigint) => async (controller: AsyncPoolController) => {\n if (isDoneFn()) return;\n await clientBlob.partUpload(\n res,\n uploadData.localPath,\n BigInt(uploadData.modificationTime),\n part,\n parts.checksumAlgorithm,\n parts.checksumHeader,\n { timeout },\n );\n logger.info(`uploaded chunk ${part}/${parts.overall} of resource: ${res.id}`);\n\n // if we had a network freeze, it will be increased slowly.\n speed.nPartsWithThisUploadSpeed++;\n if (speed.nPartsWithThisUploadSpeed >= speed.nPartsToIncreaseUpload) {\n speed.nPartsWithThisUploadSpeed = 0;\n speed.currentSpeed = increaseConcurrency(logger, speed.currentSpeed, speed.maxSpeed);\n controller.setConcurrency(speed.currentSpeed);\n }\n };\n\n await asyncPool(speed.currentSpeed, parts.toUpload.map(partUploadFn));\n\n if (isDoneFn()) return;\n await clientBlob.finalize(res, { timeout });\n\n logger.info(`uploading of resource ${res.id} finished.`);\n}\n\nfunction newProgress(res: ImportResourceSnapshot, signer: Signer) {\n let isUploadSignMatch: boolean | undefined;\n let uploadData: ImportFileHandleUploadData | undefined;\n if (isUpload(res)) {\n uploadData = ImportFileHandleUploadData.parse(res.data);\n isUploadSignMatch = isSignMatch(signer, uploadData.localPath, uploadData.pathSignature);\n }\n\n return {\n uploadData: uploadData,\n progress: {\n done: false,\n status: undefined,\n isUpload: isUpload(res),\n isUploadSignMatch: isUploadSignMatch,\n lastError: undefined,\n } satisfies sdk.ImportProgress,\n };\n}\n\n/** Returns true if we need to upload the blob that got from this progress. */\nexport function isMyUpload(p: sdk.ImportProgress): boolean {\n return p.isUpload && (p.isUploadSignMatch ?? false);\n}\n\n/** Creates a deep copy of progress,\n * since we do not want to pass a mutable object\n * to API, it led to bugs before.\n * We do not use '...' cloning syntax\n * for the compiler to fail here if we change API. */\nfunction cloneProgress(progress: sdk.ImportProgress): sdk.ImportProgress {\n const cloned: sdk.ImportProgress = {\n done: progress.done,\n isUpload: progress.isUpload,\n isUploadSignMatch: progress.isUploadSignMatch,\n lastError: progress.lastError,\n };\n\n if (progress.status)\n cloned.status = {\n progress: progress.status.progress,\n bytesProcessed: progress.status.bytesProcessed,\n bytesTotal: progress.status.bytesTotal,\n };\n\n return progress;\n}\n\nfunction isImportResourceOutputSet(res: ImportResourceSnapshot) {\n return \"blob\" in res.fields\n ? res.fields.blob !== undefined\n : res.fields.incarnation !== undefined;\n}\n\nexport function isUpload(res: ResourceInfo) {\n return res.type.name.startsWith(\"BlobUpload\");\n}\n\nexport function isSignMatch(signer: Signer, path: string, signature: string): boolean {\n try {\n signer.verify(path, signature);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction protoToStatus(proto: ProgressStatus): sdk.ImportStatus {\n return {\n progress: proto.progress ?? 0,\n bytesProcessed: Number(proto.bytesProcessed),\n bytesTotal: Number(proto.bytesTotal),\n };\n}\n\n/** Special hack: if we didn't even start to upload the blob\n * to backend because the result was already there,\n * the backend does show us a status that nothing were uploaded,\n * but we need to show the client that everything is OK.\n * Thus, here we set progress to be 1.0 and all bytes are become processed. */\nfunction doneProgressIfExisted(alreadyExisted: boolean, status: sdk.ImportStatus) {\n if (alreadyExisted && status.bytesTotal != 0 && status.bytesProcessed == 0) {\n return {\n progress: 1.0,\n bytesProcessed: Number(status.bytesTotal),\n bytesTotal: Number(status.bytesTotal),\n };\n }\n\n return status;\n}\n\nexport function isTerminalUploadError(e: any) {\n if (e?.name !== \"RpcError\") return false;\n switch (e.code) {\n case \"NOT_FOUND\":\n case \"ABORTED\":\n case \"ALREADY_EXISTS\":\n return true;\n default:\n return false;\n }\n}\n\nexport function nonRecoverableError(e: any) {\n return (\n e instanceof MTimeError ||\n e instanceof UnexpectedEOF ||\n e instanceof NoFileForUploading ||\n e instanceof BadRequestError\n );\n}\n\nfunction isHeadersTimeoutError(e: any) {\n return (e as Error)?.message.includes(`UND_ERR_HEADERS_TIMEOUT`);\n}\n\n/** It's called for every upload success so if everyone is succeeded, we'll double the concurrency. */\nfunction increaseConcurrency(logger: MiLogger, current: number, max: number): number {\n const newConcurrency = Math.min(current + 2, max);\n if (newConcurrency != current)\n logger.info(`uploadTask.increaseConcurrency: increased from ${current} to ${newConcurrency}`);\n\n return newConcurrency;\n}\n\n/** When a error happens, this will half the concurrency level, so the next time\n * we'll try to upload blobs slower. */\nfunction decreaseConcurrency(logger: MiLogger, current: number, min: number): number {\n const newConcurrency = Math.max(Math.round(current / 2), min);\n if (newConcurrency != current)\n logger.info(`uploadTask.decreaseConcurrency: decreased from ${current} to ${newConcurrency}`);\n\n return newConcurrency;\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload_task.d.ts","names":[],"sources":["../../src/drivers/upload_task.ts"],"mappings":";;;;;;;;;AAqBA;;;AAAA,cAAa,UAAA;EAAA,iBAmBQ,MAAA;EAAA,iBACA,UAAA;EAAA,iBACA,cAAA;EAAA,iBACA,yBAAA;EAAA,SAED,GAAA,EAAK,sBAAA;EAAA,iBAvBN,MAAA;EAAA,iBACA,OAAA;EAAA,QACT,WAAA;EAAA,QACA,yBAAA;EAAA,QACA,sBAAA;EA8IuB;EAAA,QA3IvB,UAAA;EACD,QAAA,EAAU,GAAA,CAAI,cAAA;EAsJO;EAnJrB,MAAA;EAQY;;EAAA,QAJX,cAAA;cAGW,MAAA,EAAQ,QAAA,EACR,UAAA,EAAY,YAAA,EACZ,cAAA,EAAgB,cAAA,EAChB,yBAAA,UACjB,MAAA,EAAQ,MAAA,EACQ,GAAA,EAAK,sBAAA;EAQhB,WAAA,CAAY,CAAA,EAAG,OAAA,EAAS,QAAA,WAAgB,GAAA,CAAA,cAAA;EAWxC,oBAAA,CAAA;EAzCU;EA8CJ,cAAA,CAAA,GAAc,OAAA;EA8Cd,YAAA,CAAA,GAAY,OAAA;EAzFjB;EAAA,QAmIA,iBAAA;EA/HD;EAAA,QAoIC,gBAAA;EAMD,kBAAA,CAAmB,GAAA,EAAK,sBAAA;EAAA,QAOvB,OAAA;EAKD,UAAA,CAAW,CAAA,EAAG,OAAA,EAAS,QAAA;EAKvB,UAAA,CAAW,QAAA;EAAA,QAIV,gBAAA;AAAA;;iBAMY,UAAA,CACpB,MAAA,EAAQ,QAAA,EACR,UAAA,EAAY,YAAA,EACZ,GAAA,EAAK,YAAA,EACL,UAAA,EAAY,0BAAA,EACZ,QAAA,iBACA,KAAA;EACE,yBAAA;EACA,sBAAA;EACA,YAAA;EACA,QAAA;AAAA,IACD,OAAA;;iBAgEa,UAAA,CAAW,CAAA,EAAG,GAAA,CAAI,cAAA;AAAA,iBAiClB,QAAA,CAAS,GAAA,EAAK,YAAA;AAAA,iBAId,WAAA,CAAY,MAAA,EAAQ,MAAA,EAAQ,IAAA,UAAc,SAAA;AAAA,iBAkC1C,qBAAA,CAAsB,CAAA;AAAA,iBAYtB,mBAAA,CAAoB,CAAA,QAAM,CAAA,IAAA,UAAA,GAAA,aAAA,GAAA,kBAAA,GAAA,eAAA"}
@@ -4,7 +4,6 @@ import { isTimeoutError, resourceIdToString, stringifyWithResourceId } from "@mi
4
4
  import { CallersCounter, asyncPool } from "@milaboratories/ts-helpers";
5
5
  import { ChangeSource } from "@milaboratories/computable";
6
6
  import assert from "node:assert";
7
-
8
7
  //#region src/drivers/upload_task.ts
9
8
  /** Holds all info needed to upload a file and a status of uploading
10
9
  * and indexing. Also, has a method to update a status of the progress.
@@ -253,7 +252,7 @@ function decreaseConcurrency(logger, current, min) {
253
252
  if (newConcurrency != current) logger.info(`uploadTask.decreaseConcurrency: decreased from ${current} to ${newConcurrency}`);
254
253
  return newConcurrency;
255
254
  }
256
-
257
255
  //#endregion
258
256
  export { UploadTask, isMyUpload, isSignMatch, isTerminalUploadError, isUpload, nonRecoverableError, uploadBlob };
257
+
259
258
  //# sourceMappingURL=upload_task.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload_task.js","names":[],"sources":["../../src/drivers/upload_task.ts"],"sourcesContent":["import type { Watcher } from \"@milaboratories/computable\";\nimport { ChangeSource } from \"@milaboratories/computable\";\nimport {\n isTimeoutError,\n resourceIdToString,\n stringifyWithResourceId,\n} from \"@milaboratories/pl-client\";\nimport type * as sdk from \"@milaboratories/pl-model-common\";\nimport type { AsyncPoolController, MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { asyncPool, CallersCounter } from \"@milaboratories/ts-helpers\";\nimport type { ClientProgress, ProgressStatus } from \"../clients/progress\";\nimport type { ClientUpload } from \"../clients/upload\";\nimport { BadRequestError, MTimeError, NoFileForUploading, UnexpectedEOF } from \"../clients/upload\";\nimport type { ImportResourceSnapshot } from \"./types\";\nimport { ImportFileHandleUploadData } from \"./types\";\nimport assert from \"node:assert\";\nimport { ResourceInfo } from \"@milaboratories/pl-tree\";\n\n/** Holds all info needed to upload a file and a status of uploading\n * and indexing. Also, has a method to update a status of the progress.\n * And holds a change source. */\nexport class UploadTask {\n private readonly change: ChangeSource = new ChangeSource();\n private readonly counter: CallersCounter = new CallersCounter();\n private nMaxUploads: number;\n private nPartsWithThisUploadSpeed = 0;\n private nPartsToIncreaseUpload = 10; // how many parts we have to wait to increase concurrency, 50 mb, 10 parts by 5 mb each.\n\n /** If this is upload progress this field will be defined */\n private uploadData?: ImportFileHandleUploadData;\n public progress: sdk.ImportProgress;\n\n /** If failed, then getting a progress is terminally failed. */\n public failed?: boolean;\n\n /** True if the blob was existed.\n * At this case, the task will show progress == 1.0. */\n private alreadyExisted = false;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly clientBlob: ClientUpload,\n private readonly clientProgress: ClientProgress,\n private readonly maxNConcurrentPartsUpload: number,\n signer: Signer,\n public readonly res: ImportResourceSnapshot,\n ) {\n this.nMaxUploads = this.maxNConcurrentPartsUpload;\n const { uploadData, progress } = newProgress(res, signer);\n this.uploadData = uploadData;\n this.progress = progress;\n }\n\n public getProgress(w: Watcher, callerId: string) {\n this.incCounter(w, callerId);\n\n if (this.failed) {\n this.logger.error(`Uploading terminally failed: ${this.progress.lastError}`);\n throw new Error(this.progress.lastError);\n }\n\n return cloneProgress(this.progress);\n }\n\n public shouldScheduleUpload(): boolean {\n return isMyUpload(this.progress);\n }\n\n /** Uploads a blob if it's not BlobIndex. */\n public async uploadBlobTask() {\n try {\n await uploadBlob(\n this.logger,\n this.clientBlob,\n this.res,\n this.uploadData!,\n this.isComputableDone.bind(this),\n {\n nPartsWithThisUploadSpeed: this.nPartsWithThisUploadSpeed,\n nPartsToIncreaseUpload: this.nPartsToIncreaseUpload,\n currentSpeed: this.nMaxUploads,\n maxSpeed: this.maxNConcurrentPartsUpload,\n },\n );\n this.change.markChanged(`blob upload for ${resourceIdToString(this.res.id)} finished`);\n } catch (e: any) {\n if (isTerminalUploadError(e)) {\n this.logger.warn(`terminal error while uploading a blob: ${e}`);\n this.change.markChanged(\n `blob upload for ${resourceIdToString(this.res.id)} aborted: ${e.code}`,\n );\n this.setDone(true);\n\n return;\n }\n\n this.logger.error(`error while uploading a blob: ${e}`);\n this.change.markChanged(`blob upload for ${resourceIdToString(this.res.id)} failed`);\n\n if (nonRecoverableError(e)) {\n this.setTerminalError(e);\n return;\n }\n\n this.setRetriableError(e);\n\n if (isHeadersTimeoutError(e)) {\n // we probably have a slow internet, we need to slow things a bit.\n this.nMaxUploads = decreaseConcurrency(this.logger, this.nMaxUploads, 1);\n }\n\n throw e;\n }\n }\n\n public async updateStatus() {\n try {\n // we do it with timeout in case we have slow internet.\n const status = await this.clientProgress.getStatus(this.res, { timeout: 10000 });\n\n const oldStatus = this.progress.status;\n const newStatus = doneProgressIfExisted(this.alreadyExisted, protoToStatus(status));\n this.progress.status = newStatus;\n this.setDone(status.done);\n\n if (status.done || this.progress.status.progress != oldStatus?.progress) {\n this.change.markChanged(`upload status for ${resourceIdToString(this.res.id)} changed`);\n }\n } catch (e: any) {\n this.setRetriableError(e);\n\n if (isTimeoutError(e)) {\n this.logger.warn(`deadline exceeded while getting a status of BlobImport: ${e.message}`);\n return;\n }\n\n if (isTerminalUploadError(e)) {\n this.logger.warn(\n `terminal error while updating BlobImport status: ${e}, ${stringifyWithResourceId(this.res)}`,\n );\n this.change.markChanged(\n `upload status for ${resourceIdToString(this.res.id)} aborted: ${e.code}`,\n );\n this.setDone(true);\n return;\n }\n\n this.logger.error(`retryable error while updating a status of BlobImport: ${e}`);\n // It was a terminal error, but when a connection drops,\n // this will stop the whole task, so we make it retryable.\n // It was like that:\n // this.change.markChanged();\n // this.setTerminalError(e);\n }\n }\n\n /** Set non-terminal error, that task can be retried. */\n private setRetriableError(e: unknown) {\n this.progress.lastError = String(e);\n }\n\n /** Set a terminal error, the task will throw a error instead of a progress. */\n private setTerminalError(e: unknown) {\n this.progress.lastError = String(e);\n this.progress.done = false;\n this.failed = true;\n }\n\n public setDoneIfOutputSet(res: ImportResourceSnapshot) {\n if (isImportResourceOutputSet(res)) {\n this.setDone(true);\n this.alreadyExisted = true;\n }\n }\n\n private setDone(done: boolean) {\n this.progress.done = done;\n if (done) this.progress.lastError = undefined;\n }\n\n public incCounter(w: Watcher, callerId: string) {\n this.change.attachWatcher(w);\n this.counter.inc(callerId);\n }\n\n public decCounter(callerId: string) {\n return this.counter.dec(callerId);\n }\n\n private isComputableDone() {\n return this.counter.isZero();\n }\n}\n\n/** Uploads a blob if it's not BlobIndex. */\nexport async function uploadBlob(\n logger: MiLogger,\n clientBlob: ClientUpload,\n res: ResourceInfo,\n uploadData: ImportFileHandleUploadData,\n isDoneFn: () => boolean,\n speed: {\n nPartsWithThisUploadSpeed: number;\n nPartsToIncreaseUpload: number;\n currentSpeed: number;\n maxSpeed: number;\n },\n) {\n assert(isUpload(res), \"the upload operation can be done only for BlobUploads\");\n const timeout = 10000; // 10 sec instead of standard 5 sec, things might be slow with a slow connection.\n\n if (isDoneFn()) return;\n const parts = await clientBlob.initUpload(res, { timeout });\n logger.info(\n `started to upload blob ${res.id},` +\n ` parts overall: ${parts.overall}, parts remained: ${parts.toUpload.length},` +\n ` number of concurrent uploads: ${speed.currentSpeed}`,\n );\n\n const partUploadFn = (part: bigint) => async (controller: AsyncPoolController) => {\n if (isDoneFn()) return;\n await clientBlob.partUpload(\n res,\n uploadData.localPath,\n BigInt(uploadData.modificationTime),\n part,\n parts.checksumAlgorithm,\n parts.checksumHeader,\n { timeout },\n );\n logger.info(`uploaded chunk ${part}/${parts.overall} of resource: ${res.id}`);\n\n // if we had a network freeze, it will be increased slowly.\n speed.nPartsWithThisUploadSpeed++;\n if (speed.nPartsWithThisUploadSpeed >= speed.nPartsToIncreaseUpload) {\n speed.nPartsWithThisUploadSpeed = 0;\n speed.currentSpeed = increaseConcurrency(logger, speed.currentSpeed, speed.maxSpeed);\n controller.setConcurrency(speed.currentSpeed);\n }\n };\n\n await asyncPool(speed.currentSpeed, parts.toUpload.map(partUploadFn));\n\n if (isDoneFn()) return;\n await clientBlob.finalize(res, { timeout });\n\n logger.info(`uploading of resource ${res.id} finished.`);\n}\n\nfunction newProgress(res: ImportResourceSnapshot, signer: Signer) {\n let isUploadSignMatch: boolean | undefined;\n let uploadData: ImportFileHandleUploadData | undefined;\n if (isUpload(res)) {\n uploadData = ImportFileHandleUploadData.parse(res.data);\n isUploadSignMatch = isSignMatch(signer, uploadData.localPath, uploadData.pathSignature);\n }\n\n return {\n uploadData: uploadData,\n progress: {\n done: false,\n status: undefined,\n isUpload: isUpload(res),\n isUploadSignMatch: isUploadSignMatch,\n lastError: undefined,\n } satisfies sdk.ImportProgress,\n };\n}\n\n/** Returns true if we need to upload the blob that got from this progress. */\nexport function isMyUpload(p: sdk.ImportProgress): boolean {\n return p.isUpload && (p.isUploadSignMatch ?? false);\n}\n\n/** Creates a deep copy of progress,\n * since we do not want to pass a mutable object\n * to API, it led to bugs before.\n * We do not use '...' cloning syntax\n * for the compiler to fail here if we change API. */\nfunction cloneProgress(progress: sdk.ImportProgress): sdk.ImportProgress {\n const cloned: sdk.ImportProgress = {\n done: progress.done,\n isUpload: progress.isUpload,\n isUploadSignMatch: progress.isUploadSignMatch,\n lastError: progress.lastError,\n };\n\n if (progress.status)\n cloned.status = {\n progress: progress.status.progress,\n bytesProcessed: progress.status.bytesProcessed,\n bytesTotal: progress.status.bytesTotal,\n };\n\n return progress;\n}\n\nfunction isImportResourceOutputSet(res: ImportResourceSnapshot) {\n return \"blob\" in res.fields\n ? res.fields.blob !== undefined\n : res.fields.incarnation !== undefined;\n}\n\nexport function isUpload(res: ResourceInfo) {\n return res.type.name.startsWith(\"BlobUpload\");\n}\n\nexport function isSignMatch(signer: Signer, path: string, signature: string): boolean {\n try {\n signer.verify(path, signature);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction protoToStatus(proto: ProgressStatus): sdk.ImportStatus {\n return {\n progress: proto.progress ?? 0,\n bytesProcessed: Number(proto.bytesProcessed),\n bytesTotal: Number(proto.bytesTotal),\n };\n}\n\n/** Special hack: if we didn't even start to upload the blob\n * to backend because the result was already there,\n * the backend does show us a status that nothing were uploaded,\n * but we need to show the client that everything is OK.\n * Thus, here we set progress to be 1.0 and all bytes are become processed. */\nfunction doneProgressIfExisted(alreadyExisted: boolean, status: sdk.ImportStatus) {\n if (alreadyExisted && status.bytesTotal != 0 && status.bytesProcessed == 0) {\n return {\n progress: 1.0,\n bytesProcessed: Number(status.bytesTotal),\n bytesTotal: Number(status.bytesTotal),\n };\n }\n\n return status;\n}\n\nexport function isTerminalUploadError(e: any) {\n if (e?.name !== \"RpcError\") return false;\n switch (e.code) {\n case \"NOT_FOUND\":\n case \"ABORTED\":\n case \"ALREADY_EXISTS\":\n return true;\n default:\n return false;\n }\n}\n\nexport function nonRecoverableError(e: any) {\n return (\n e instanceof MTimeError ||\n e instanceof UnexpectedEOF ||\n e instanceof NoFileForUploading ||\n e instanceof BadRequestError\n );\n}\n\nfunction isHeadersTimeoutError(e: any) {\n return (e as Error)?.message.includes(`UND_ERR_HEADERS_TIMEOUT`);\n}\n\n/** It's called for every upload success so if everyone is succeeded, we'll double the concurrency. */\nfunction increaseConcurrency(logger: MiLogger, current: number, max: number): number {\n const newConcurrency = Math.min(current + 2, max);\n if (newConcurrency != current)\n logger.info(`uploadTask.increaseConcurrency: increased from ${current} to ${newConcurrency}`);\n\n return newConcurrency;\n}\n\n/** When a error happens, this will half the concurrency level, so the next time\n * we'll try to upload blobs slower. */\nfunction decreaseConcurrency(logger: MiLogger, current: number, min: number): number {\n const newConcurrency = Math.max(Math.round(current / 2), min);\n if (newConcurrency != current)\n logger.info(`uploadTask.decreaseConcurrency: decreased from ${current} to ${newConcurrency}`);\n\n return newConcurrency;\n}\n"],"mappings":";;;;;;;;;;;AAqBA,IAAa,aAAb,MAAwB;CACtB,AAAiB,SAAuB,IAAI,cAAc;CAC1D,AAAiB,UAA0B,IAAI,gBAAgB;CAC/D,AAAQ;CACR,AAAQ,4BAA4B;CACpC,AAAQ,yBAAyB;;CAGjC,AAAQ;CACR,AAAO;;CAGP,AAAO;;;CAIP,AAAQ,iBAAiB;CAEzB,YACE,AAAiB,QACjB,AAAiB,YACjB,AAAiB,gBACjB,AAAiB,2BACjB,QACA,AAAgB,KAChB;EANiB;EACA;EACA;EACA;EAED;AAEhB,OAAK,cAAc,KAAK;EACxB,MAAM,EAAE,YAAY,aAAa,YAAY,KAAK,OAAO;AACzD,OAAK,aAAa;AAClB,OAAK,WAAW;;CAGlB,AAAO,YAAY,GAAY,UAAkB;AAC/C,OAAK,WAAW,GAAG,SAAS;AAE5B,MAAI,KAAK,QAAQ;AACf,QAAK,OAAO,MAAM,gCAAgC,KAAK,SAAS,YAAY;AAC5E,SAAM,IAAI,MAAM,KAAK,SAAS,UAAU;;AAG1C,SAAO,cAAc,KAAK,SAAS;;CAGrC,AAAO,uBAAgC;AACrC,SAAO,WAAW,KAAK,SAAS;;;CAIlC,MAAa,iBAAiB;AAC5B,MAAI;AACF,SAAM,WACJ,KAAK,QACL,KAAK,YACL,KAAK,KACL,KAAK,YACL,KAAK,iBAAiB,KAAK,KAAK,EAChC;IACE,2BAA2B,KAAK;IAChC,wBAAwB,KAAK;IAC7B,cAAc,KAAK;IACnB,UAAU,KAAK;IAChB,CACF;AACD,QAAK,OAAO,YAAY,mBAAmB,mBAAmB,KAAK,IAAI,GAAG,CAAC,WAAW;WAC/E,GAAQ;AACf,OAAI,sBAAsB,EAAE,EAAE;AAC5B,SAAK,OAAO,KAAK,0CAA0C,IAAI;AAC/D,SAAK,OAAO,YACV,mBAAmB,mBAAmB,KAAK,IAAI,GAAG,CAAC,YAAY,EAAE,OAClE;AACD,SAAK,QAAQ,KAAK;AAElB;;AAGF,QAAK,OAAO,MAAM,iCAAiC,IAAI;AACvD,QAAK,OAAO,YAAY,mBAAmB,mBAAmB,KAAK,IAAI,GAAG,CAAC,SAAS;AAEpF,OAAI,oBAAoB,EAAE,EAAE;AAC1B,SAAK,iBAAiB,EAAE;AACxB;;AAGF,QAAK,kBAAkB,EAAE;AAEzB,OAAI,sBAAsB,EAAE,CAE1B,MAAK,cAAc,oBAAoB,KAAK,QAAQ,KAAK,aAAa,EAAE;AAG1E,SAAM;;;CAIV,MAAa,eAAe;AAC1B,MAAI;GAEF,MAAM,SAAS,MAAM,KAAK,eAAe,UAAU,KAAK,KAAK,EAAE,SAAS,KAAO,CAAC;GAEhF,MAAM,YAAY,KAAK,SAAS;GAChC,MAAM,YAAY,sBAAsB,KAAK,gBAAgB,cAAc,OAAO,CAAC;AACnF,QAAK,SAAS,SAAS;AACvB,QAAK,QAAQ,OAAO,KAAK;AAEzB,OAAI,OAAO,QAAQ,KAAK,SAAS,OAAO,YAAY,WAAW,SAC7D,MAAK,OAAO,YAAY,qBAAqB,mBAAmB,KAAK,IAAI,GAAG,CAAC,UAAU;WAElF,GAAQ;AACf,QAAK,kBAAkB,EAAE;AAEzB,OAAI,eAAe,EAAE,EAAE;AACrB,SAAK,OAAO,KAAK,2DAA2D,EAAE,UAAU;AACxF;;AAGF,OAAI,sBAAsB,EAAE,EAAE;AAC5B,SAAK,OAAO,KACV,oDAAoD,EAAE,IAAI,wBAAwB,KAAK,IAAI,GAC5F;AACD,SAAK,OAAO,YACV,qBAAqB,mBAAmB,KAAK,IAAI,GAAG,CAAC,YAAY,EAAE,OACpE;AACD,SAAK,QAAQ,KAAK;AAClB;;AAGF,QAAK,OAAO,MAAM,0DAA0D,IAAI;;;;CAUpF,AAAQ,kBAAkB,GAAY;AACpC,OAAK,SAAS,YAAY,OAAO,EAAE;;;CAIrC,AAAQ,iBAAiB,GAAY;AACnC,OAAK,SAAS,YAAY,OAAO,EAAE;AACnC,OAAK,SAAS,OAAO;AACrB,OAAK,SAAS;;CAGhB,AAAO,mBAAmB,KAA6B;AACrD,MAAI,0BAA0B,IAAI,EAAE;AAClC,QAAK,QAAQ,KAAK;AAClB,QAAK,iBAAiB;;;CAI1B,AAAQ,QAAQ,MAAe;AAC7B,OAAK,SAAS,OAAO;AACrB,MAAI,KAAM,MAAK,SAAS,YAAY;;CAGtC,AAAO,WAAW,GAAY,UAAkB;AAC9C,OAAK,OAAO,cAAc,EAAE;AAC5B,OAAK,QAAQ,IAAI,SAAS;;CAG5B,AAAO,WAAW,UAAkB;AAClC,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAGnC,AAAQ,mBAAmB;AACzB,SAAO,KAAK,QAAQ,QAAQ;;;;AAKhC,eAAsB,WACpB,QACA,YACA,KACA,YACA,UACA,OAMA;AACA,QAAO,SAAS,IAAI,EAAE,wDAAwD;CAC9E,MAAM,UAAU;AAEhB,KAAI,UAAU,CAAE;CAChB,MAAM,QAAQ,MAAM,WAAW,WAAW,KAAK,EAAE,SAAS,CAAC;AAC3D,QAAO,KACL,0BAA0B,IAAI,GAAG,mBACZ,MAAM,QAAQ,oBAAoB,MAAM,SAAS,OAAO,kCACzC,MAAM,eAC3C;CAED,MAAM,gBAAgB,SAAiB,OAAO,eAAoC;AAChF,MAAI,UAAU,CAAE;AAChB,QAAM,WAAW,WACf,KACA,WAAW,WACX,OAAO,WAAW,iBAAiB,EACnC,MACA,MAAM,mBACN,MAAM,gBACN,EAAE,SAAS,CACZ;AACD,SAAO,KAAK,kBAAkB,KAAK,GAAG,MAAM,QAAQ,gBAAgB,IAAI,KAAK;AAG7E,QAAM;AACN,MAAI,MAAM,6BAA6B,MAAM,wBAAwB;AACnE,SAAM,4BAA4B;AAClC,SAAM,eAAe,oBAAoB,QAAQ,MAAM,cAAc,MAAM,SAAS;AACpF,cAAW,eAAe,MAAM,aAAa;;;AAIjD,OAAM,UAAU,MAAM,cAAc,MAAM,SAAS,IAAI,aAAa,CAAC;AAErE,KAAI,UAAU,CAAE;AAChB,OAAM,WAAW,SAAS,KAAK,EAAE,SAAS,CAAC;AAE3C,QAAO,KAAK,yBAAyB,IAAI,GAAG,YAAY;;AAG1D,SAAS,YAAY,KAA6B,QAAgB;CAChE,IAAI;CACJ,IAAI;AACJ,KAAI,SAAS,IAAI,EAAE;AACjB,eAAa,2BAA2B,MAAM,IAAI,KAAK;AACvD,sBAAoB,YAAY,QAAQ,WAAW,WAAW,WAAW,cAAc;;AAGzF,QAAO;EACO;EACZ,UAAU;GACR,MAAM;GACN,QAAQ;GACR,UAAU,SAAS,IAAI;GACJ;GACnB,WAAW;GACZ;EACF;;;AAIH,SAAgB,WAAW,GAAgC;AACzD,QAAO,EAAE,aAAa,EAAE,qBAAqB;;;;;;;AAQ/C,SAAS,cAAc,UAAkD;CACvE,MAAM,SAA6B;EACjC,MAAM,SAAS;EACf,UAAU,SAAS;EACnB,mBAAmB,SAAS;EAC5B,WAAW,SAAS;EACrB;AAED,KAAI,SAAS,OACX,QAAO,SAAS;EACd,UAAU,SAAS,OAAO;EAC1B,gBAAgB,SAAS,OAAO;EAChC,YAAY,SAAS,OAAO;EAC7B;AAEH,QAAO;;AAGT,SAAS,0BAA0B,KAA6B;AAC9D,QAAO,UAAU,IAAI,SACjB,IAAI,OAAO,SAAS,SACpB,IAAI,OAAO,gBAAgB;;AAGjC,SAAgB,SAAS,KAAmB;AAC1C,QAAO,IAAI,KAAK,KAAK,WAAW,aAAa;;AAG/C,SAAgB,YAAY,QAAgB,MAAc,WAA4B;AACpF,KAAI;AACF,SAAO,OAAO,MAAM,UAAU;AAC9B,SAAO;SACD;AACN,SAAO;;;AAIX,SAAS,cAAc,OAAyC;AAC9D,QAAO;EACL,UAAU,MAAM,YAAY;EAC5B,gBAAgB,OAAO,MAAM,eAAe;EAC5C,YAAY,OAAO,MAAM,WAAW;EACrC;;;;;;;AAQH,SAAS,sBAAsB,gBAAyB,QAA0B;AAChF,KAAI,kBAAkB,OAAO,cAAc,KAAK,OAAO,kBAAkB,EACvE,QAAO;EACL,UAAU;EACV,gBAAgB,OAAO,OAAO,WAAW;EACzC,YAAY,OAAO,OAAO,WAAW;EACtC;AAGH,QAAO;;AAGT,SAAgB,sBAAsB,GAAQ;AAC5C,KAAI,GAAG,SAAS,WAAY,QAAO;AACnC,SAAQ,EAAE,MAAV;EACE,KAAK;EACL,KAAK;EACL,KAAK,iBACH,QAAO;EACT,QACE,QAAO;;;AAIb,SAAgB,oBAAoB,GAAQ;AAC1C,QACE,aAAa,cACb,aAAa,iBACb,aAAa,sBACb,aAAa;;AAIjB,SAAS,sBAAsB,GAAQ;AACrC,QAAQ,GAAa,QAAQ,SAAS,0BAA0B;;;AAIlE,SAAS,oBAAoB,QAAkB,SAAiB,KAAqB;CACnF,MAAM,iBAAiB,KAAK,IAAI,UAAU,GAAG,IAAI;AACjD,KAAI,kBAAkB,QACpB,QAAO,KAAK,kDAAkD,QAAQ,MAAM,iBAAiB;AAE/F,QAAO;;;;AAKT,SAAS,oBAAoB,QAAkB,SAAiB,KAAqB;CACnF,MAAM,iBAAiB,KAAK,IAAI,KAAK,MAAM,UAAU,EAAE,EAAE,IAAI;AAC7D,KAAI,kBAAkB,QACpB,QAAO,KAAK,kDAAkD,QAAQ,MAAM,iBAAiB;AAE/F,QAAO"}
1
+ {"version":3,"file":"upload_task.js","names":[],"sources":["../../src/drivers/upload_task.ts"],"sourcesContent":["import type { Watcher } from \"@milaboratories/computable\";\nimport { ChangeSource } from \"@milaboratories/computable\";\nimport {\n isTimeoutError,\n resourceIdToString,\n stringifyWithResourceId,\n} from \"@milaboratories/pl-client\";\nimport type * as sdk from \"@milaboratories/pl-model-common\";\nimport type { AsyncPoolController, MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { asyncPool, CallersCounter } from \"@milaboratories/ts-helpers\";\nimport type { ClientProgress, ProgressStatus } from \"../clients/progress\";\nimport type { ClientUpload } from \"../clients/upload\";\nimport { BadRequestError, MTimeError, NoFileForUploading, UnexpectedEOF } from \"../clients/upload\";\nimport type { ImportResourceSnapshot } from \"./types\";\nimport { ImportFileHandleUploadData } from \"./types\";\nimport assert from \"node:assert\";\nimport { ResourceInfo } from \"@milaboratories/pl-tree\";\n\n/** Holds all info needed to upload a file and a status of uploading\n * and indexing. Also, has a method to update a status of the progress.\n * And holds a change source. */\nexport class UploadTask {\n private readonly change: ChangeSource = new ChangeSource();\n private readonly counter: CallersCounter = new CallersCounter();\n private nMaxUploads: number;\n private nPartsWithThisUploadSpeed = 0;\n private nPartsToIncreaseUpload = 10; // how many parts we have to wait to increase concurrency, 50 mb, 10 parts by 5 mb each.\n\n /** If this is upload progress this field will be defined */\n private uploadData?: ImportFileHandleUploadData;\n public progress: sdk.ImportProgress;\n\n /** If failed, then getting a progress is terminally failed. */\n public failed?: boolean;\n\n /** True if the blob was existed.\n * At this case, the task will show progress == 1.0. */\n private alreadyExisted = false;\n\n constructor(\n private readonly logger: MiLogger,\n private readonly clientBlob: ClientUpload,\n private readonly clientProgress: ClientProgress,\n private readonly maxNConcurrentPartsUpload: number,\n signer: Signer,\n public readonly res: ImportResourceSnapshot,\n ) {\n this.nMaxUploads = this.maxNConcurrentPartsUpload;\n const { uploadData, progress } = newProgress(res, signer);\n this.uploadData = uploadData;\n this.progress = progress;\n }\n\n public getProgress(w: Watcher, callerId: string) {\n this.incCounter(w, callerId);\n\n if (this.failed) {\n this.logger.error(`Uploading terminally failed: ${this.progress.lastError}`);\n throw new Error(this.progress.lastError);\n }\n\n return cloneProgress(this.progress);\n }\n\n public shouldScheduleUpload(): boolean {\n return isMyUpload(this.progress);\n }\n\n /** Uploads a blob if it's not BlobIndex. */\n public async uploadBlobTask() {\n try {\n await uploadBlob(\n this.logger,\n this.clientBlob,\n this.res,\n this.uploadData!,\n this.isComputableDone.bind(this),\n {\n nPartsWithThisUploadSpeed: this.nPartsWithThisUploadSpeed,\n nPartsToIncreaseUpload: this.nPartsToIncreaseUpload,\n currentSpeed: this.nMaxUploads,\n maxSpeed: this.maxNConcurrentPartsUpload,\n },\n );\n this.change.markChanged(`blob upload for ${resourceIdToString(this.res.id)} finished`);\n } catch (e: any) {\n if (isTerminalUploadError(e)) {\n this.logger.warn(`terminal error while uploading a blob: ${e}`);\n this.change.markChanged(\n `blob upload for ${resourceIdToString(this.res.id)} aborted: ${e.code}`,\n );\n this.setDone(true);\n\n return;\n }\n\n this.logger.error(`error while uploading a blob: ${e}`);\n this.change.markChanged(`blob upload for ${resourceIdToString(this.res.id)} failed`);\n\n if (nonRecoverableError(e)) {\n this.setTerminalError(e);\n return;\n }\n\n this.setRetriableError(e);\n\n if (isHeadersTimeoutError(e)) {\n // we probably have a slow internet, we need to slow things a bit.\n this.nMaxUploads = decreaseConcurrency(this.logger, this.nMaxUploads, 1);\n }\n\n throw e;\n }\n }\n\n public async updateStatus() {\n try {\n // we do it with timeout in case we have slow internet.\n const status = await this.clientProgress.getStatus(this.res, { timeout: 10000 });\n\n const oldStatus = this.progress.status;\n const newStatus = doneProgressIfExisted(this.alreadyExisted, protoToStatus(status));\n this.progress.status = newStatus;\n this.setDone(status.done);\n\n if (status.done || this.progress.status.progress != oldStatus?.progress) {\n this.change.markChanged(`upload status for ${resourceIdToString(this.res.id)} changed`);\n }\n } catch (e: any) {\n this.setRetriableError(e);\n\n if (isTimeoutError(e)) {\n this.logger.warn(`deadline exceeded while getting a status of BlobImport: ${e.message}`);\n return;\n }\n\n if (isTerminalUploadError(e)) {\n this.logger.warn(\n `terminal error while updating BlobImport status: ${e}, ${stringifyWithResourceId(this.res)}`,\n );\n this.change.markChanged(\n `upload status for ${resourceIdToString(this.res.id)} aborted: ${e.code}`,\n );\n this.setDone(true);\n return;\n }\n\n this.logger.error(`retryable error while updating a status of BlobImport: ${e}`);\n // It was a terminal error, but when a connection drops,\n // this will stop the whole task, so we make it retryable.\n // It was like that:\n // this.change.markChanged();\n // this.setTerminalError(e);\n }\n }\n\n /** Set non-terminal error, that task can be retried. */\n private setRetriableError(e: unknown) {\n this.progress.lastError = String(e);\n }\n\n /** Set a terminal error, the task will throw a error instead of a progress. */\n private setTerminalError(e: unknown) {\n this.progress.lastError = String(e);\n this.progress.done = false;\n this.failed = true;\n }\n\n public setDoneIfOutputSet(res: ImportResourceSnapshot) {\n if (isImportResourceOutputSet(res)) {\n this.setDone(true);\n this.alreadyExisted = true;\n }\n }\n\n private setDone(done: boolean) {\n this.progress.done = done;\n if (done) this.progress.lastError = undefined;\n }\n\n public incCounter(w: Watcher, callerId: string) {\n this.change.attachWatcher(w);\n this.counter.inc(callerId);\n }\n\n public decCounter(callerId: string) {\n return this.counter.dec(callerId);\n }\n\n private isComputableDone() {\n return this.counter.isZero();\n }\n}\n\n/** Uploads a blob if it's not BlobIndex. */\nexport async function uploadBlob(\n logger: MiLogger,\n clientBlob: ClientUpload,\n res: ResourceInfo,\n uploadData: ImportFileHandleUploadData,\n isDoneFn: () => boolean,\n speed: {\n nPartsWithThisUploadSpeed: number;\n nPartsToIncreaseUpload: number;\n currentSpeed: number;\n maxSpeed: number;\n },\n) {\n assert(isUpload(res), \"the upload operation can be done only for BlobUploads\");\n const timeout = 10000; // 10 sec instead of standard 5 sec, things might be slow with a slow connection.\n\n if (isDoneFn()) return;\n const parts = await clientBlob.initUpload(res, { timeout });\n logger.info(\n `started to upload blob ${res.id},` +\n ` parts overall: ${parts.overall}, parts remained: ${parts.toUpload.length},` +\n ` number of concurrent uploads: ${speed.currentSpeed}`,\n );\n\n const partUploadFn = (part: bigint) => async (controller: AsyncPoolController) => {\n if (isDoneFn()) return;\n await clientBlob.partUpload(\n res,\n uploadData.localPath,\n BigInt(uploadData.modificationTime),\n part,\n parts.checksumAlgorithm,\n parts.checksumHeader,\n { timeout },\n );\n logger.info(`uploaded chunk ${part}/${parts.overall} of resource: ${res.id}`);\n\n // if we had a network freeze, it will be increased slowly.\n speed.nPartsWithThisUploadSpeed++;\n if (speed.nPartsWithThisUploadSpeed >= speed.nPartsToIncreaseUpload) {\n speed.nPartsWithThisUploadSpeed = 0;\n speed.currentSpeed = increaseConcurrency(logger, speed.currentSpeed, speed.maxSpeed);\n controller.setConcurrency(speed.currentSpeed);\n }\n };\n\n await asyncPool(speed.currentSpeed, parts.toUpload.map(partUploadFn));\n\n if (isDoneFn()) return;\n await clientBlob.finalize(res, { timeout });\n\n logger.info(`uploading of resource ${res.id} finished.`);\n}\n\nfunction newProgress(res: ImportResourceSnapshot, signer: Signer) {\n let isUploadSignMatch: boolean | undefined;\n let uploadData: ImportFileHandleUploadData | undefined;\n if (isUpload(res)) {\n uploadData = ImportFileHandleUploadData.parse(res.data);\n isUploadSignMatch = isSignMatch(signer, uploadData.localPath, uploadData.pathSignature);\n }\n\n return {\n uploadData: uploadData,\n progress: {\n done: false,\n status: undefined,\n isUpload: isUpload(res),\n isUploadSignMatch: isUploadSignMatch,\n lastError: undefined,\n } satisfies sdk.ImportProgress,\n };\n}\n\n/** Returns true if we need to upload the blob that got from this progress. */\nexport function isMyUpload(p: sdk.ImportProgress): boolean {\n return p.isUpload && (p.isUploadSignMatch ?? false);\n}\n\n/** Creates a deep copy of progress,\n * since we do not want to pass a mutable object\n * to API, it led to bugs before.\n * We do not use '...' cloning syntax\n * for the compiler to fail here if we change API. */\nfunction cloneProgress(progress: sdk.ImportProgress): sdk.ImportProgress {\n const cloned: sdk.ImportProgress = {\n done: progress.done,\n isUpload: progress.isUpload,\n isUploadSignMatch: progress.isUploadSignMatch,\n lastError: progress.lastError,\n };\n\n if (progress.status)\n cloned.status = {\n progress: progress.status.progress,\n bytesProcessed: progress.status.bytesProcessed,\n bytesTotal: progress.status.bytesTotal,\n };\n\n return progress;\n}\n\nfunction isImportResourceOutputSet(res: ImportResourceSnapshot) {\n return \"blob\" in res.fields\n ? res.fields.blob !== undefined\n : res.fields.incarnation !== undefined;\n}\n\nexport function isUpload(res: ResourceInfo) {\n return res.type.name.startsWith(\"BlobUpload\");\n}\n\nexport function isSignMatch(signer: Signer, path: string, signature: string): boolean {\n try {\n signer.verify(path, signature);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction protoToStatus(proto: ProgressStatus): sdk.ImportStatus {\n return {\n progress: proto.progress ?? 0,\n bytesProcessed: Number(proto.bytesProcessed),\n bytesTotal: Number(proto.bytesTotal),\n };\n}\n\n/** Special hack: if we didn't even start to upload the blob\n * to backend because the result was already there,\n * the backend does show us a status that nothing were uploaded,\n * but we need to show the client that everything is OK.\n * Thus, here we set progress to be 1.0 and all bytes are become processed. */\nfunction doneProgressIfExisted(alreadyExisted: boolean, status: sdk.ImportStatus) {\n if (alreadyExisted && status.bytesTotal != 0 && status.bytesProcessed == 0) {\n return {\n progress: 1.0,\n bytesProcessed: Number(status.bytesTotal),\n bytesTotal: Number(status.bytesTotal),\n };\n }\n\n return status;\n}\n\nexport function isTerminalUploadError(e: any) {\n if (e?.name !== \"RpcError\") return false;\n switch (e.code) {\n case \"NOT_FOUND\":\n case \"ABORTED\":\n case \"ALREADY_EXISTS\":\n return true;\n default:\n return false;\n }\n}\n\nexport function nonRecoverableError(e: any) {\n return (\n e instanceof MTimeError ||\n e instanceof UnexpectedEOF ||\n e instanceof NoFileForUploading ||\n e instanceof BadRequestError\n );\n}\n\nfunction isHeadersTimeoutError(e: any) {\n return (e as Error)?.message.includes(`UND_ERR_HEADERS_TIMEOUT`);\n}\n\n/** It's called for every upload success so if everyone is succeeded, we'll double the concurrency. */\nfunction increaseConcurrency(logger: MiLogger, current: number, max: number): number {\n const newConcurrency = Math.min(current + 2, max);\n if (newConcurrency != current)\n logger.info(`uploadTask.increaseConcurrency: increased from ${current} to ${newConcurrency}`);\n\n return newConcurrency;\n}\n\n/** When a error happens, this will half the concurrency level, so the next time\n * we'll try to upload blobs slower. */\nfunction decreaseConcurrency(logger: MiLogger, current: number, min: number): number {\n const newConcurrency = Math.max(Math.round(current / 2), min);\n if (newConcurrency != current)\n logger.info(`uploadTask.decreaseConcurrency: decreased from ${current} to ${newConcurrency}`);\n\n return newConcurrency;\n}\n"],"mappings":"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"}
@@ -1,7 +1,6 @@
1
- const require_runtime = require('../../_virtual/_rolldown/runtime.cjs');
1
+ const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
2
2
  let path = require("path");
3
3
  path = require_runtime.__toESM(path);
4
-
5
4
  //#region src/drivers/urls/url.ts
6
5
  /** Creates a new plblob+folder URL. */
7
6
  function newFolderURL(signer, saveDir, fPath) {
@@ -45,10 +44,10 @@ function parseNestedPathNoEscape(baseDir, userInputPath) {
45
44
  if (!absolutePath.startsWith(normalizedBase)) throw new Error("Path validation failed.");
46
45
  return absolutePath;
47
46
  }
48
-
49
47
  //#endregion
50
48
  exports.getPathForBlockUIURL = getPathForBlockUIURL;
51
49
  exports.getPathForFolderURL = getPathForFolderURL;
52
50
  exports.newBlockUIURL = newBlockUIURL;
53
51
  exports.newFolderURL = newFolderURL;
52
+
54
53
  //# sourceMappingURL=url.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"url.cjs","names":[],"sources":["../../../src/drivers/urls/url.ts"],"sourcesContent":["import type { FolderURL, BlockUIURL } from \"@milaboratories/pl-model-common\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport path from \"path\";\n\n/** Creates a new plblob+folder URL. */\nexport function newFolderURL(signer: Signer, saveDir: string, fPath: string): FolderURL {\n const p = path.relative(saveDir, fPath);\n const sign = signer.sign(p);\n\n return `plblob+folder://${sign}.${p}.blob`;\n}\n\n/** Creates a new block-ui URL. */\nexport function newBlockUIURL(signer: Signer, saveDir: string, fPath: string): BlockUIURL {\n const p = path.relative(saveDir, fPath);\n const sign = signer.sign(p);\n\n return `block-ui://${sign}.${p}.uidir`;\n}\n\n/** Checks the signature and path injections.\n * @returns the path to the file inside the root directory. */\nexport function getPathForFolderURL(signer: Signer, url: FolderURL, rootDir: string): string {\n return getPath(signer, url, rootDir);\n}\n\n/** Checks the signature and path injections.\n * @returns the path to the file inside the root directory. */\nexport function getPathForBlockUIURL(signer: Signer, url: BlockUIURL, rootDir: string): string {\n return getPath(signer, url, rootDir);\n}\n\n/** Parses URL,\n * checks the signature,\n * gets the absolute path by the given root directory.\n * If the path is empty, it returns the index.html file.\n * @returns an absolute path for the user. */\nfunction getPath(signer: Signer, url: string, rootDir: string): string {\n const parsed = new URL(url);\n const [sign, subfolder, _] = parsed.host.split(\".\");\n\n signer.verify(\n subfolder,\n sign,\n `signature verification failed for url: ${url}, subfolder: ${subfolder}`,\n );\n\n // Decoding changes '%20' to ' ' for example.\n const pathname = decodeURIComponent(parsed.pathname.slice(1));\n\n let fPath = parseNestedPathNoEscape(path.join(rootDir, `${subfolder}`), pathname);\n\n if (parsed.pathname == \"\" || parsed.pathname == \"/\") {\n fPath = path.join(fPath, \"index.html\");\n }\n\n return path.resolve(fPath);\n}\n\n/** Checks that the userInputPath is in baseDir.\n * @returns an absolute path for the user. */\nfunction parseNestedPathNoEscape(baseDir: string, userInputPath: string): string {\n const absolutePath = path.resolve(baseDir, userInputPath);\n\n const normalizedBase = path.resolve(baseDir);\n\n if (!absolutePath.startsWith(normalizedBase)) {\n throw new Error(\"Path validation failed.\");\n }\n\n return absolutePath;\n}\n"],"mappings":";;;;;;AAKA,SAAgB,aAAa,QAAgB,SAAiB,OAA0B;CACtF,MAAM,IAAI,aAAK,SAAS,SAAS,MAAM;AAGvC,QAAO,mBAFM,OAAO,KAAK,EAAE,CAEI,GAAG,EAAE;;;AAItC,SAAgB,cAAc,QAAgB,SAAiB,OAA2B;CACxF,MAAM,IAAI,aAAK,SAAS,SAAS,MAAM;AAGvC,QAAO,cAFM,OAAO,KAAK,EAAE,CAED,GAAG,EAAE;;;;AAKjC,SAAgB,oBAAoB,QAAgB,KAAgB,SAAyB;AAC3F,QAAO,QAAQ,QAAQ,KAAK,QAAQ;;;;AAKtC,SAAgB,qBAAqB,QAAgB,KAAiB,SAAyB;AAC7F,QAAO,QAAQ,QAAQ,KAAK,QAAQ;;;;;;;AAQtC,SAAS,QAAQ,QAAgB,KAAa,SAAyB;CACrE,MAAM,SAAS,IAAI,IAAI,IAAI;CAC3B,MAAM,CAAC,MAAM,WAAW,KAAK,OAAO,KAAK,MAAM,IAAI;AAEnD,QAAO,OACL,WACA,MACA,0CAA0C,IAAI,eAAe,YAC9D;CAGD,MAAM,WAAW,mBAAmB,OAAO,SAAS,MAAM,EAAE,CAAC;CAE7D,IAAI,QAAQ,wBAAwB,aAAK,KAAK,SAAS,GAAG,YAAY,EAAE,SAAS;AAEjF,KAAI,OAAO,YAAY,MAAM,OAAO,YAAY,IAC9C,SAAQ,aAAK,KAAK,OAAO,aAAa;AAGxC,QAAO,aAAK,QAAQ,MAAM;;;;AAK5B,SAAS,wBAAwB,SAAiB,eAA+B;CAC/E,MAAM,eAAe,aAAK,QAAQ,SAAS,cAAc;CAEzD,MAAM,iBAAiB,aAAK,QAAQ,QAAQ;AAE5C,KAAI,CAAC,aAAa,WAAW,eAAe,CAC1C,OAAM,IAAI,MAAM,0BAA0B;AAG5C,QAAO"}
1
+ {"version":3,"file":"url.cjs","names":[],"sources":["../../../src/drivers/urls/url.ts"],"sourcesContent":["import type { FolderURL, BlockUIURL } from \"@milaboratories/pl-model-common\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport path from \"path\";\n\n/** Creates a new plblob+folder URL. */\nexport function newFolderURL(signer: Signer, saveDir: string, fPath: string): FolderURL {\n const p = path.relative(saveDir, fPath);\n const sign = signer.sign(p);\n\n return `plblob+folder://${sign}.${p}.blob`;\n}\n\n/** Creates a new block-ui URL. */\nexport function newBlockUIURL(signer: Signer, saveDir: string, fPath: string): BlockUIURL {\n const p = path.relative(saveDir, fPath);\n const sign = signer.sign(p);\n\n return `block-ui://${sign}.${p}.uidir`;\n}\n\n/** Checks the signature and path injections.\n * @returns the path to the file inside the root directory. */\nexport function getPathForFolderURL(signer: Signer, url: FolderURL, rootDir: string): string {\n return getPath(signer, url, rootDir);\n}\n\n/** Checks the signature and path injections.\n * @returns the path to the file inside the root directory. */\nexport function getPathForBlockUIURL(signer: Signer, url: BlockUIURL, rootDir: string): string {\n return getPath(signer, url, rootDir);\n}\n\n/** Parses URL,\n * checks the signature,\n * gets the absolute path by the given root directory.\n * If the path is empty, it returns the index.html file.\n * @returns an absolute path for the user. */\nfunction getPath(signer: Signer, url: string, rootDir: string): string {\n const parsed = new URL(url);\n const [sign, subfolder, _] = parsed.host.split(\".\");\n\n signer.verify(\n subfolder,\n sign,\n `signature verification failed for url: ${url}, subfolder: ${subfolder}`,\n );\n\n // Decoding changes '%20' to ' ' for example.\n const pathname = decodeURIComponent(parsed.pathname.slice(1));\n\n let fPath = parseNestedPathNoEscape(path.join(rootDir, `${subfolder}`), pathname);\n\n if (parsed.pathname == \"\" || parsed.pathname == \"/\") {\n fPath = path.join(fPath, \"index.html\");\n }\n\n return path.resolve(fPath);\n}\n\n/** Checks that the userInputPath is in baseDir.\n * @returns an absolute path for the user. */\nfunction parseNestedPathNoEscape(baseDir: string, userInputPath: string): string {\n const absolutePath = path.resolve(baseDir, userInputPath);\n\n const normalizedBase = path.resolve(baseDir);\n\n if (!absolutePath.startsWith(normalizedBase)) {\n throw new Error(\"Path validation failed.\");\n }\n\n return absolutePath;\n}\n"],"mappings":";;;;;AAKA,SAAgB,aAAa,QAAgB,SAAiB,OAA0B;CACtF,MAAM,IAAI,KAAA,QAAK,SAAS,SAAS,MAAM;AAGvC,QAAO,mBAFM,OAAO,KAAK,EAAE,CAEI,GAAG,EAAE;;;AAItC,SAAgB,cAAc,QAAgB,SAAiB,OAA2B;CACxF,MAAM,IAAI,KAAA,QAAK,SAAS,SAAS,MAAM;AAGvC,QAAO,cAFM,OAAO,KAAK,EAAE,CAED,GAAG,EAAE;;;;AAKjC,SAAgB,oBAAoB,QAAgB,KAAgB,SAAyB;AAC3F,QAAO,QAAQ,QAAQ,KAAK,QAAQ;;;;AAKtC,SAAgB,qBAAqB,QAAgB,KAAiB,SAAyB;AAC7F,QAAO,QAAQ,QAAQ,KAAK,QAAQ;;;;;;;AAQtC,SAAS,QAAQ,QAAgB,KAAa,SAAyB;CACrE,MAAM,SAAS,IAAI,IAAI,IAAI;CAC3B,MAAM,CAAC,MAAM,WAAW,KAAK,OAAO,KAAK,MAAM,IAAI;AAEnD,QAAO,OACL,WACA,MACA,0CAA0C,IAAI,eAAe,YAC9D;CAGD,MAAM,WAAW,mBAAmB,OAAO,SAAS,MAAM,EAAE,CAAC;CAE7D,IAAI,QAAQ,wBAAwB,KAAA,QAAK,KAAK,SAAS,GAAG,YAAY,EAAE,SAAS;AAEjF,KAAI,OAAO,YAAY,MAAM,OAAO,YAAY,IAC9C,SAAQ,KAAA,QAAK,KAAK,OAAO,aAAa;AAGxC,QAAO,KAAA,QAAK,QAAQ,MAAM;;;;AAK5B,SAAS,wBAAwB,SAAiB,eAA+B;CAC/E,MAAM,eAAe,KAAA,QAAK,QAAQ,SAAS,cAAc;CAEzD,MAAM,iBAAiB,KAAA,QAAK,QAAQ,QAAQ;AAE5C,KAAI,CAAC,aAAa,WAAW,eAAe,CAC1C,OAAM,IAAI,MAAM,0BAA0B;AAG5C,QAAO"}
@@ -1,5 +1,4 @@
1
1
  import path from "path";
2
-
3
2
  //#region src/drivers/urls/url.ts
4
3
  /** Creates a new plblob+folder URL. */
5
4
  function newFolderURL(signer, saveDir, fPath) {
@@ -43,7 +42,7 @@ function parseNestedPathNoEscape(baseDir, userInputPath) {
43
42
  if (!absolutePath.startsWith(normalizedBase)) throw new Error("Path validation failed.");
44
43
  return absolutePath;
45
44
  }
46
-
47
45
  //#endregion
48
46
  export { getPathForBlockUIURL, getPathForFolderURL, newBlockUIURL, newFolderURL };
47
+
49
48
  //# sourceMappingURL=url.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"url.js","names":[],"sources":["../../../src/drivers/urls/url.ts"],"sourcesContent":["import type { FolderURL, BlockUIURL } from \"@milaboratories/pl-model-common\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport path from \"path\";\n\n/** Creates a new plblob+folder URL. */\nexport function newFolderURL(signer: Signer, saveDir: string, fPath: string): FolderURL {\n const p = path.relative(saveDir, fPath);\n const sign = signer.sign(p);\n\n return `plblob+folder://${sign}.${p}.blob`;\n}\n\n/** Creates a new block-ui URL. */\nexport function newBlockUIURL(signer: Signer, saveDir: string, fPath: string): BlockUIURL {\n const p = path.relative(saveDir, fPath);\n const sign = signer.sign(p);\n\n return `block-ui://${sign}.${p}.uidir`;\n}\n\n/** Checks the signature and path injections.\n * @returns the path to the file inside the root directory. */\nexport function getPathForFolderURL(signer: Signer, url: FolderURL, rootDir: string): string {\n return getPath(signer, url, rootDir);\n}\n\n/** Checks the signature and path injections.\n * @returns the path to the file inside the root directory. */\nexport function getPathForBlockUIURL(signer: Signer, url: BlockUIURL, rootDir: string): string {\n return getPath(signer, url, rootDir);\n}\n\n/** Parses URL,\n * checks the signature,\n * gets the absolute path by the given root directory.\n * If the path is empty, it returns the index.html file.\n * @returns an absolute path for the user. */\nfunction getPath(signer: Signer, url: string, rootDir: string): string {\n const parsed = new URL(url);\n const [sign, subfolder, _] = parsed.host.split(\".\");\n\n signer.verify(\n subfolder,\n sign,\n `signature verification failed for url: ${url}, subfolder: ${subfolder}`,\n );\n\n // Decoding changes '%20' to ' ' for example.\n const pathname = decodeURIComponent(parsed.pathname.slice(1));\n\n let fPath = parseNestedPathNoEscape(path.join(rootDir, `${subfolder}`), pathname);\n\n if (parsed.pathname == \"\" || parsed.pathname == \"/\") {\n fPath = path.join(fPath, \"index.html\");\n }\n\n return path.resolve(fPath);\n}\n\n/** Checks that the userInputPath is in baseDir.\n * @returns an absolute path for the user. */\nfunction parseNestedPathNoEscape(baseDir: string, userInputPath: string): string {\n const absolutePath = path.resolve(baseDir, userInputPath);\n\n const normalizedBase = path.resolve(baseDir);\n\n if (!absolutePath.startsWith(normalizedBase)) {\n throw new Error(\"Path validation failed.\");\n }\n\n return absolutePath;\n}\n"],"mappings":";;;;AAKA,SAAgB,aAAa,QAAgB,SAAiB,OAA0B;CACtF,MAAM,IAAI,KAAK,SAAS,SAAS,MAAM;AAGvC,QAAO,mBAFM,OAAO,KAAK,EAAE,CAEI,GAAG,EAAE;;;AAItC,SAAgB,cAAc,QAAgB,SAAiB,OAA2B;CACxF,MAAM,IAAI,KAAK,SAAS,SAAS,MAAM;AAGvC,QAAO,cAFM,OAAO,KAAK,EAAE,CAED,GAAG,EAAE;;;;AAKjC,SAAgB,oBAAoB,QAAgB,KAAgB,SAAyB;AAC3F,QAAO,QAAQ,QAAQ,KAAK,QAAQ;;;;AAKtC,SAAgB,qBAAqB,QAAgB,KAAiB,SAAyB;AAC7F,QAAO,QAAQ,QAAQ,KAAK,QAAQ;;;;;;;AAQtC,SAAS,QAAQ,QAAgB,KAAa,SAAyB;CACrE,MAAM,SAAS,IAAI,IAAI,IAAI;CAC3B,MAAM,CAAC,MAAM,WAAW,KAAK,OAAO,KAAK,MAAM,IAAI;AAEnD,QAAO,OACL,WACA,MACA,0CAA0C,IAAI,eAAe,YAC9D;CAGD,MAAM,WAAW,mBAAmB,OAAO,SAAS,MAAM,EAAE,CAAC;CAE7D,IAAI,QAAQ,wBAAwB,KAAK,KAAK,SAAS,GAAG,YAAY,EAAE,SAAS;AAEjF,KAAI,OAAO,YAAY,MAAM,OAAO,YAAY,IAC9C,SAAQ,KAAK,KAAK,OAAO,aAAa;AAGxC,QAAO,KAAK,QAAQ,MAAM;;;;AAK5B,SAAS,wBAAwB,SAAiB,eAA+B;CAC/E,MAAM,eAAe,KAAK,QAAQ,SAAS,cAAc;CAEzD,MAAM,iBAAiB,KAAK,QAAQ,QAAQ;AAE5C,KAAI,CAAC,aAAa,WAAW,eAAe,CAC1C,OAAM,IAAI,MAAM,0BAA0B;AAG5C,QAAO"}
1
+ {"version":3,"file":"url.js","names":[],"sources":["../../../src/drivers/urls/url.ts"],"sourcesContent":["import type { FolderURL, BlockUIURL } from \"@milaboratories/pl-model-common\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport path from \"path\";\n\n/** Creates a new plblob+folder URL. */\nexport function newFolderURL(signer: Signer, saveDir: string, fPath: string): FolderURL {\n const p = path.relative(saveDir, fPath);\n const sign = signer.sign(p);\n\n return `plblob+folder://${sign}.${p}.blob`;\n}\n\n/** Creates a new block-ui URL. */\nexport function newBlockUIURL(signer: Signer, saveDir: string, fPath: string): BlockUIURL {\n const p = path.relative(saveDir, fPath);\n const sign = signer.sign(p);\n\n return `block-ui://${sign}.${p}.uidir`;\n}\n\n/** Checks the signature and path injections.\n * @returns the path to the file inside the root directory. */\nexport function getPathForFolderURL(signer: Signer, url: FolderURL, rootDir: string): string {\n return getPath(signer, url, rootDir);\n}\n\n/** Checks the signature and path injections.\n * @returns the path to the file inside the root directory. */\nexport function getPathForBlockUIURL(signer: Signer, url: BlockUIURL, rootDir: string): string {\n return getPath(signer, url, rootDir);\n}\n\n/** Parses URL,\n * checks the signature,\n * gets the absolute path by the given root directory.\n * If the path is empty, it returns the index.html file.\n * @returns an absolute path for the user. */\nfunction getPath(signer: Signer, url: string, rootDir: string): string {\n const parsed = new URL(url);\n const [sign, subfolder, _] = parsed.host.split(\".\");\n\n signer.verify(\n subfolder,\n sign,\n `signature verification failed for url: ${url}, subfolder: ${subfolder}`,\n );\n\n // Decoding changes '%20' to ' ' for example.\n const pathname = decodeURIComponent(parsed.pathname.slice(1));\n\n let fPath = parseNestedPathNoEscape(path.join(rootDir, `${subfolder}`), pathname);\n\n if (parsed.pathname == \"\" || parsed.pathname == \"/\") {\n fPath = path.join(fPath, \"index.html\");\n }\n\n return path.resolve(fPath);\n}\n\n/** Checks that the userInputPath is in baseDir.\n * @returns an absolute path for the user. */\nfunction parseNestedPathNoEscape(baseDir: string, userInputPath: string): string {\n const absolutePath = path.resolve(baseDir, userInputPath);\n\n const normalizedBase = path.resolve(baseDir);\n\n if (!absolutePath.startsWith(normalizedBase)) {\n throw new Error(\"Path validation failed.\");\n }\n\n return absolutePath;\n}\n"],"mappings":";;;AAKA,SAAgB,aAAa,QAAgB,SAAiB,OAA0B;CACtF,MAAM,IAAI,KAAK,SAAS,SAAS,MAAM;AAGvC,QAAO,mBAFM,OAAO,KAAK,EAAE,CAEI,GAAG,EAAE;;;AAItC,SAAgB,cAAc,QAAgB,SAAiB,OAA2B;CACxF,MAAM,IAAI,KAAK,SAAS,SAAS,MAAM;AAGvC,QAAO,cAFM,OAAO,KAAK,EAAE,CAED,GAAG,EAAE;;;;AAKjC,SAAgB,oBAAoB,QAAgB,KAAgB,SAAyB;AAC3F,QAAO,QAAQ,QAAQ,KAAK,QAAQ;;;;AAKtC,SAAgB,qBAAqB,QAAgB,KAAiB,SAAyB;AAC7F,QAAO,QAAQ,QAAQ,KAAK,QAAQ;;;;;;;AAQtC,SAAS,QAAQ,QAAgB,KAAa,SAAyB;CACrE,MAAM,SAAS,IAAI,IAAI,IAAI;CAC3B,MAAM,CAAC,MAAM,WAAW,KAAK,OAAO,KAAK,MAAM,IAAI;AAEnD,QAAO,OACL,WACA,MACA,0CAA0C,IAAI,eAAe,YAC9D;CAGD,MAAM,WAAW,mBAAmB,OAAO,SAAS,MAAM,EAAE,CAAC;CAE7D,IAAI,QAAQ,wBAAwB,KAAK,KAAK,SAAS,GAAG,YAAY,EAAE,SAAS;AAEjF,KAAI,OAAO,YAAY,MAAM,OAAO,YAAY,IAC9C,SAAQ,KAAK,KAAK,OAAO,aAAa;AAGxC,QAAO,KAAK,QAAQ,MAAM;;;;AAK5B,SAAS,wBAAwB,SAAiB,eAA+B;CAC/E,MAAM,eAAe,KAAK,QAAQ,SAAS,cAAc;CAEzD,MAAM,iBAAiB,KAAK,QAAQ,QAAQ;AAE5C,KAAI,CAAC,aAAa,WAAW,eAAe,CAC1C,OAAM,IAAI,MAAM,0BAA0B;AAG5C,QAAO"}
@@ -1,4 +1,4 @@
1
- const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
1
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
2
2
  let path = require("path");
3
3
  path = require_runtime.__toESM(path);
4
4
  let os = require("os");
@@ -6,7 +6,6 @@ os = require_runtime.__toESM(os);
6
6
  let util = require("util");
7
7
  util = require_runtime.__toESM(util);
8
8
  let child_process = require("child_process");
9
-
10
9
  //#region src/drivers/virtual_storages.ts
11
10
  async function DefaultVirtualLocalStorages() {
12
11
  const home = os.default.homedir();
@@ -35,7 +34,7 @@ async function DefaultVirtualLocalStorages() {
35
34
  }
36
35
  }
37
36
  }
38
-
39
37
  //#endregion
40
38
  exports.DefaultVirtualLocalStorages = DefaultVirtualLocalStorages;
39
+
41
40
  //# sourceMappingURL=virtual_storages.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtual_storages.cjs","names":["exec"],"sources":["../../src/drivers/virtual_storages.ts"],"sourcesContent":["import path from \"path\";\nimport os from \"os\";\nimport util from \"util\";\nimport { exec } from \"child_process\";\nimport type { VirtualLocalStorageSpec } from \"./types\";\n\nexport async function DefaultVirtualLocalStorages(): Promise<VirtualLocalStorageSpec[]> {\n const home = os.homedir();\n if (path.sep == \"/\")\n return [\n {\n name: \"local\",\n root: \"/\",\n initialPath: home,\n },\n ];\n else {\n // determine the drive on which user's home folder is stored\n const homeRoot = path.parse(home).root; // e.g. C:\\\n const homeDrive = homeRoot.replaceAll(\":\\\\\", \"\"); // e.g. C drive.\n\n // code below inspired by\n // https://stackoverflow.com/a/52411712/769192\n\n try {\n const wmic = await util.promisify(exec)(\"wmic logicaldisk get name\");\n // parsing wmic output\n const drives = wmic.stdout\n .split(\"\\r\\n\")\n .filter((line) => line.includes(\":\"))\n .map((line) => line.trim().replaceAll(\":\", \"\"));\n\n return drives.map((drive) => {\n const isHomeDrive = drive == homeDrive;\n return {\n name: `local_disk_${drive}`,\n root: `${drive}:\\\\`,\n initialPath: isHomeDrive ? home : `${drive}:\\\\`,\n };\n });\n } catch {\n return [\n {\n name: `local_disk_${homeDrive}`,\n root: `${homeDrive}:\\\\`,\n initialPath: home,\n },\n ];\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAMA,eAAsB,8BAAkE;CACtF,MAAM,OAAO,WAAG,SAAS;AACzB,KAAI,aAAK,OAAO,IACd,QAAO,CACL;EACE,MAAM;EACN,MAAM;EACN,aAAa;EACd,CACF;MACE;EAGH,MAAM,YADW,aAAK,MAAM,KAAK,CAAC,KACP,WAAW,OAAO,GAAG;AAKhD,MAAI;AAQF,WAPa,MAAM,aAAK,UAAUA,mBAAK,CAAC,4BAA4B,EAEhD,OACjB,MAAM,OAAO,CACb,QAAQ,SAAS,KAAK,SAAS,IAAI,CAAC,CACpC,KAAK,SAAS,KAAK,MAAM,CAAC,WAAW,KAAK,GAAG,CAAC,CAEnC,KAAK,UAAU;IAC3B,MAAM,cAAc,SAAS;AAC7B,WAAO;KACL,MAAM,cAAc;KACpB,MAAM,GAAG,MAAM;KACf,aAAa,cAAc,OAAO,GAAG,MAAM;KAC5C;KACD;UACI;AACN,UAAO,CACL;IACE,MAAM,cAAc;IACpB,MAAM,GAAG,UAAU;IACnB,aAAa;IACd,CACF"}
1
+ {"version":3,"file":"virtual_storages.cjs","names":["exec"],"sources":["../../src/drivers/virtual_storages.ts"],"sourcesContent":["import path from \"path\";\nimport os from \"os\";\nimport util from \"util\";\nimport { exec } from \"child_process\";\nimport type { VirtualLocalStorageSpec } from \"./types\";\n\nexport async function DefaultVirtualLocalStorages(): Promise<VirtualLocalStorageSpec[]> {\n const home = os.homedir();\n if (path.sep == \"/\")\n return [\n {\n name: \"local\",\n root: \"/\",\n initialPath: home,\n },\n ];\n else {\n // determine the drive on which user's home folder is stored\n const homeRoot = path.parse(home).root; // e.g. C:\\\n const homeDrive = homeRoot.replaceAll(\":\\\\\", \"\"); // e.g. C drive.\n\n // code below inspired by\n // https://stackoverflow.com/a/52411712/769192\n\n try {\n const wmic = await util.promisify(exec)(\"wmic logicaldisk get name\");\n // parsing wmic output\n const drives = wmic.stdout\n .split(\"\\r\\n\")\n .filter((line) => line.includes(\":\"))\n .map((line) => line.trim().replaceAll(\":\", \"\"));\n\n return drives.map((drive) => {\n const isHomeDrive = drive == homeDrive;\n return {\n name: `local_disk_${drive}`,\n root: `${drive}:\\\\`,\n initialPath: isHomeDrive ? home : `${drive}:\\\\`,\n };\n });\n } catch {\n return [\n {\n name: `local_disk_${homeDrive}`,\n root: `${homeDrive}:\\\\`,\n initialPath: home,\n },\n ];\n }\n }\n}\n"],"mappings":";;;;;;;;;AAMA,eAAsB,8BAAkE;CACtF,MAAM,OAAO,GAAA,QAAG,SAAS;AACzB,KAAI,KAAA,QAAK,OAAO,IACd,QAAO,CACL;EACE,MAAM;EACN,MAAM;EACN,aAAa;EACd,CACF;MACE;EAGH,MAAM,YADW,KAAA,QAAK,MAAM,KAAK,CAAC,KACP,WAAW,OAAO,GAAG;AAKhD,MAAI;AAQF,WAPa,MAAM,KAAA,QAAK,UAAUA,cAAAA,KAAK,CAAC,4BAA4B,EAEhD,OACjB,MAAM,OAAO,CACb,QAAQ,SAAS,KAAK,SAAS,IAAI,CAAC,CACpC,KAAK,SAAS,KAAK,MAAM,CAAC,WAAW,KAAK,GAAG,CAAC,CAEnC,KAAK,UAAU;IAC3B,MAAM,cAAc,SAAS;AAC7B,WAAO;KACL,MAAM,cAAc;KACpB,MAAM,GAAG,MAAM;KACf,aAAa,cAAc,OAAO,GAAG,MAAM;KAC5C;KACD;UACI;AACN,UAAO,CACL;IACE,MAAM,cAAc;IACpB,MAAM,GAAG,UAAU;IACnB,aAAa;IACd,CACF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual_storages.d.ts","names":[],"sources":["../../src/drivers/virtual_storages.ts"],"mappings":";;;iBAMsB,2BAAA,CAAA,GAA+B,OAAA,CAAQ,uBAAA"}
@@ -2,7 +2,6 @@ import path from "path";
2
2
  import os from "os";
3
3
  import util from "util";
4
4
  import { exec } from "child_process";
5
-
6
5
  //#region src/drivers/virtual_storages.ts
7
6
  async function DefaultVirtualLocalStorages() {
8
7
  const home = os.homedir();
@@ -31,7 +30,7 @@ async function DefaultVirtualLocalStorages() {
31
30
  }
32
31
  }
33
32
  }
34
-
35
33
  //#endregion
36
34
  export { DefaultVirtualLocalStorages };
35
+
37
36
  //# sourceMappingURL=virtual_storages.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtual_storages.js","names":[],"sources":["../../src/drivers/virtual_storages.ts"],"sourcesContent":["import path from \"path\";\nimport os from \"os\";\nimport util from \"util\";\nimport { exec } from \"child_process\";\nimport type { VirtualLocalStorageSpec } from \"./types\";\n\nexport async function DefaultVirtualLocalStorages(): Promise<VirtualLocalStorageSpec[]> {\n const home = os.homedir();\n if (path.sep == \"/\")\n return [\n {\n name: \"local\",\n root: \"/\",\n initialPath: home,\n },\n ];\n else {\n // determine the drive on which user's home folder is stored\n const homeRoot = path.parse(home).root; // e.g. C:\\\n const homeDrive = homeRoot.replaceAll(\":\\\\\", \"\"); // e.g. C drive.\n\n // code below inspired by\n // https://stackoverflow.com/a/52411712/769192\n\n try {\n const wmic = await util.promisify(exec)(\"wmic logicaldisk get name\");\n // parsing wmic output\n const drives = wmic.stdout\n .split(\"\\r\\n\")\n .filter((line) => line.includes(\":\"))\n .map((line) => line.trim().replaceAll(\":\", \"\"));\n\n return drives.map((drive) => {\n const isHomeDrive = drive == homeDrive;\n return {\n name: `local_disk_${drive}`,\n root: `${drive}:\\\\`,\n initialPath: isHomeDrive ? home : `${drive}:\\\\`,\n };\n });\n } catch {\n return [\n {\n name: `local_disk_${homeDrive}`,\n root: `${homeDrive}:\\\\`,\n initialPath: home,\n },\n ];\n }\n }\n}\n"],"mappings":";;;;;;AAMA,eAAsB,8BAAkE;CACtF,MAAM,OAAO,GAAG,SAAS;AACzB,KAAI,KAAK,OAAO,IACd,QAAO,CACL;EACE,MAAM;EACN,MAAM;EACN,aAAa;EACd,CACF;MACE;EAGH,MAAM,YADW,KAAK,MAAM,KAAK,CAAC,KACP,WAAW,OAAO,GAAG;AAKhD,MAAI;AAQF,WAPa,MAAM,KAAK,UAAU,KAAK,CAAC,4BAA4B,EAEhD,OACjB,MAAM,OAAO,CACb,QAAQ,SAAS,KAAK,SAAS,IAAI,CAAC,CACpC,KAAK,SAAS,KAAK,MAAM,CAAC,WAAW,KAAK,GAAG,CAAC,CAEnC,KAAK,UAAU;IAC3B,MAAM,cAAc,SAAS;AAC7B,WAAO;KACL,MAAM,cAAc;KACpB,MAAM,GAAG,MAAM;KACf,aAAa,cAAc,OAAO,GAAG,MAAM;KAC5C;KACD;UACI;AACN,UAAO,CACL;IACE,MAAM,cAAc;IACpB,MAAM,GAAG,UAAU;IACnB,aAAa;IACd,CACF"}
1
+ {"version":3,"file":"virtual_storages.js","names":[],"sources":["../../src/drivers/virtual_storages.ts"],"sourcesContent":["import path from \"path\";\nimport os from \"os\";\nimport util from \"util\";\nimport { exec } from \"child_process\";\nimport type { VirtualLocalStorageSpec } from \"./types\";\n\nexport async function DefaultVirtualLocalStorages(): Promise<VirtualLocalStorageSpec[]> {\n const home = os.homedir();\n if (path.sep == \"/\")\n return [\n {\n name: \"local\",\n root: \"/\",\n initialPath: home,\n },\n ];\n else {\n // determine the drive on which user's home folder is stored\n const homeRoot = path.parse(home).root; // e.g. C:\\\n const homeDrive = homeRoot.replaceAll(\":\\\\\", \"\"); // e.g. C drive.\n\n // code below inspired by\n // https://stackoverflow.com/a/52411712/769192\n\n try {\n const wmic = await util.promisify(exec)(\"wmic logicaldisk get name\");\n // parsing wmic output\n const drives = wmic.stdout\n .split(\"\\r\\n\")\n .filter((line) => line.includes(\":\"))\n .map((line) => line.trim().replaceAll(\":\", \"\"));\n\n return drives.map((drive) => {\n const isHomeDrive = drive == homeDrive;\n return {\n name: `local_disk_${drive}`,\n root: `${drive}:\\\\`,\n initialPath: isHomeDrive ? home : `${drive}:\\\\`,\n };\n });\n } catch {\n return [\n {\n name: `local_disk_${homeDrive}`,\n root: `${homeDrive}:\\\\`,\n initialPath: home,\n },\n ];\n }\n }\n}\n"],"mappings":";;;;;AAMA,eAAsB,8BAAkE;CACtF,MAAM,OAAO,GAAG,SAAS;AACzB,KAAI,KAAK,OAAO,IACd,QAAO,CACL;EACE,MAAM;EACN,MAAM;EACN,aAAa;EACd,CACF;MACE;EAGH,MAAM,YADW,KAAK,MAAM,KAAK,CAAC,KACP,WAAW,OAAO,GAAG;AAKhD,MAAI;AAQF,WAPa,MAAM,KAAK,UAAU,KAAK,CAAC,4BAA4B,EAEhD,OACjB,MAAM,OAAO,CACb,QAAQ,SAAS,KAAK,SAAS,IAAI,CAAC,CACpC,KAAK,SAAS,KAAK,MAAM,CAAC,WAAW,KAAK,GAAG,CAAC,CAEnC,KAAK,UAAU;IAC3B,MAAM,cAAc,SAAS;AAC7B,WAAO;KACL,MAAM,cAAc;KACpB,MAAM,GAAG,MAAM;KACf,aAAa,cAAc,OAAO,GAAG,MAAM;KAC5C;KACD;UACI;AACN,UAAO,CACL;IACE,MAAM,cAAc;IACpB,MAAM,GAAG,UAAU;IACnB,aAAa;IACd,CACF"}
@@ -1,10 +1,9 @@
1
- const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
2
- const require_download_errors = require('./download_errors.cjs');
1
+ require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_download_errors = require("./download_errors.cjs");
3
3
  let undici = require("undici");
4
4
  let node_stream = require("node:stream");
5
5
  let node_stream_web = require("node:stream/web");
6
6
  let node_stream_consumers = require("node:stream/consumers");
7
-
8
7
  //#region src/helpers/download.ts
9
8
  var RemoteFileDownloader = class {
10
9
  offByOneServers = [];
@@ -71,7 +70,7 @@ async function checkStatusCodeOk(statusCode, webBody, url) {
71
70
  throw new require_download_errors.DownloadNetworkError(statusCode, url, beginning);
72
71
  }
73
72
  }
74
-
75
73
  //#endregion
76
74
  exports.RemoteFileDownloader = RemoteFileDownloader;
75
+
77
76
  //# sourceMappingURL=download.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"download.cjs","names":["Readable","TransformStream","OffByOneError","DownloadNetworkError400","DownloadNetworkError"],"sources":["../../src/helpers/download.ts"],"sourcesContent":["// @TODO Gleb Zakharov\n/* eslint-disable n/no-unsupported-features/node-builtins */\nimport type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream } from \"node:stream/web\";\nimport { TransformStream } from \"node:stream/web\";\nimport { text } from \"node:stream/consumers\";\nimport type { GetContentOptions } from \"@milaboratories/pl-model-common\";\nimport { OffByOneError, DownloadNetworkError400, DownloadNetworkError } from \"./download_errors\";\n\nexport type ContentHandler<T> = (content: ReadableStream, size: number) => Promise<T>;\n\nexport class RemoteFileDownloader {\n private readonly offByOneServers: string[] = [];\n\n constructor(public readonly httpClient: Dispatcher) {}\n\n async withContent<T>(\n url: string,\n reqHeaders: Record<string, string>,\n ops: GetContentOptions,\n handler: ContentHandler<T>,\n ): Promise<T> {\n const headers = { ...reqHeaders };\n const urlOrigin = new URL(url).origin;\n\n // Add range header if specified\n if (ops.range) {\n const offByOne = this.offByOneServers.includes(urlOrigin);\n headers[\"Range\"] = `bytes=${ops.range.from}-${ops.range.to - (offByOne ? 0 : 1)}`;\n }\n\n const {\n statusCode,\n body,\n headers: responseHeaders,\n } = await request(url, {\n dispatcher: this.httpClient,\n // Undici automatically sets certain headers, so we need to lowercase user-provided headers\n // to prevent automatic headers from being set and avoid \"duplicated headers\" error.\n headers: Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]),\n ),\n signal: ops.signal,\n highWaterMark: 1 * 1024 * 1024, // 1MB chunks instead of 64KB, tested to be optimal for Human Aging dataset\n });\n ops.signal?.throwIfAborted();\n\n const webBody = Readable.toWeb(body);\n let handlerSuccess = false;\n\n try {\n await checkStatusCodeOk(statusCode, webBody, url);\n ops.signal?.throwIfAborted();\n\n let result: T | undefined = undefined;\n\n const contentLength = Number(responseHeaders[\"content-length\"]);\n if (Number.isNaN(contentLength) || contentLength === 0) {\n // Some backend versions have a bug that they are not returning content-length header.\n // In this case `content-length` header is returned as 0.\n // We should not clip the result stream to 0 bytes in such case.\n result = await handler(webBody, 0);\n } else {\n // Some backend versions have a bug where they return more data than requested in range.\n // So we have to manually normalize the stream to the expected size.\n const size = ops.range ? ops.range.to - ops.range.from : contentLength;\n const normalizedStream = webBody.pipeThrough(\n new (class extends TransformStream {\n constructor(sizeBytes: number, recordOffByOne: () => void) {\n super({\n transform(chunk: Uint8Array, controller) {\n const truncatedChunk = chunk.slice(0, sizeBytes);\n controller.enqueue(truncatedChunk);\n sizeBytes -= truncatedChunk.length;\n if (!sizeBytes) controller.terminate();\n },\n flush(controller) {\n // Some backend versions have a bug where they return 1 less byte than requested in range.\n // We cannot always request one more byte because if this end byte is the last byte of the file,\n // the backend will return 416 (Range Not Satisfiable). So error is thrown to force client to retry the request.\n if (sizeBytes === 1) {\n recordOffByOne();\n controller.error(new OffByOneError());\n }\n },\n });\n }\n })(size, () => this.offByOneServers.push(urlOrigin)),\n );\n result = await handler(normalizedStream, size);\n }\n\n handlerSuccess = true;\n return result;\n } catch (error) {\n // Cleanup on error (including handler errors)\n if (!handlerSuccess && !webBody.locked) {\n try {\n await webBody.cancel();\n } catch {\n // Ignore cleanup errors\n }\n }\n throw error;\n }\n }\n}\n\nasync function checkStatusCodeOk(statusCode: number, webBody: ReadableStream, url: string) {\n if (statusCode != 200 && statusCode != 206 /* partial content from range request */) {\n const beginning = (await text(webBody)).substring(0, 1000);\n if (400 <= statusCode && statusCode < 500)\n throw new DownloadNetworkError400(statusCode, url, beginning);\n throw new DownloadNetworkError(statusCode, url, beginning);\n }\n}\n"],"mappings":";;;;;;;;AAaA,IAAa,uBAAb,MAAkC;CAChC,AAAiB,kBAA4B,EAAE;CAE/C,YAAY,AAAgB,YAAwB;EAAxB;;CAE5B,MAAM,YACJ,KACA,YACA,KACA,SACY;EACZ,MAAM,UAAU,EAAE,GAAG,YAAY;EACjC,MAAM,YAAY,IAAI,IAAI,IAAI,CAAC;AAG/B,MAAI,IAAI,OAAO;GACb,MAAM,WAAW,KAAK,gBAAgB,SAAS,UAAU;AACzD,WAAQ,WAAW,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,MAAM,WAAW,IAAI;;EAG/E,MAAM,EACJ,YACA,MACA,SAAS,oBACP,0BAAc,KAAK;GACrB,YAAY,KAAK;GAGjB,SAAS,OAAO,YACd,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,IAAI,aAAa,EAAE,MAAM,CAAC,CAC1E;GACD,QAAQ,IAAI;GACZ,eAAe,IAAI,OAAO;GAC3B,CAAC;AACF,MAAI,QAAQ,gBAAgB;EAE5B,MAAM,UAAUA,qBAAS,MAAM,KAAK;EACpC,IAAI,iBAAiB;AAErB,MAAI;AACF,SAAM,kBAAkB,YAAY,SAAS,IAAI;AACjD,OAAI,QAAQ,gBAAgB;GAE5B,IAAI,SAAwB;GAE5B,MAAM,gBAAgB,OAAO,gBAAgB,kBAAkB;AAC/D,OAAI,OAAO,MAAM,cAAc,IAAI,kBAAkB,EAInD,UAAS,MAAM,QAAQ,SAAS,EAAE;QAC7B;IAGL,MAAM,OAAO,IAAI,QAAQ,IAAI,MAAM,KAAK,IAAI,MAAM,OAAO;AAwBzD,aAAS,MAAM,QAvBU,QAAQ,YAC/B,IAAK,cAAcC,gCAAgB;KACjC,YAAY,WAAmB,gBAA4B;AACzD,YAAM;OACJ,UAAU,OAAmB,YAAY;QACvC,MAAM,iBAAiB,MAAM,MAAM,GAAG,UAAU;AAChD,mBAAW,QAAQ,eAAe;AAClC,qBAAa,eAAe;AAC5B,YAAI,CAAC,UAAW,YAAW,WAAW;;OAExC,MAAM,YAAY;AAIhB,YAAI,cAAc,GAAG;AACnB,yBAAgB;AAChB,oBAAW,MAAM,IAAIC,uCAAe,CAAC;;;OAG1C,CAAC;;MAEH,YAAY,KAAK,gBAAgB,KAAK,UAAU,CAAC,CACrD,EACwC,KAAK;;AAGhD,oBAAiB;AACjB,UAAO;WACA,OAAO;AAEd,OAAI,CAAC,kBAAkB,CAAC,QAAQ,OAC9B,KAAI;AACF,UAAM,QAAQ,QAAQ;WAChB;AAIV,SAAM;;;;AAKZ,eAAe,kBAAkB,YAAoB,SAAyB,KAAa;AACzF,KAAI,cAAc,OAAO,cAAc,KAA8C;EACnF,MAAM,aAAa,sCAAW,QAAQ,EAAE,UAAU,GAAG,IAAK;AAC1D,MAAI,OAAO,cAAc,aAAa,IACpC,OAAM,IAAIC,gDAAwB,YAAY,KAAK,UAAU;AAC/D,QAAM,IAAIC,6CAAqB,YAAY,KAAK,UAAU"}
1
+ {"version":3,"file":"download.cjs","names":["Readable","TransformStream","OffByOneError","DownloadNetworkError400","DownloadNetworkError"],"sources":["../../src/helpers/download.ts"],"sourcesContent":["// @TODO Gleb Zakharov\n/* eslint-disable n/no-unsupported-features/node-builtins */\nimport type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream } from \"node:stream/web\";\nimport { TransformStream } from \"node:stream/web\";\nimport { text } from \"node:stream/consumers\";\nimport type { GetContentOptions } from \"@milaboratories/pl-model-common\";\nimport { OffByOneError, DownloadNetworkError400, DownloadNetworkError } from \"./download_errors\";\n\nexport type ContentHandler<T> = (content: ReadableStream, size: number) => Promise<T>;\n\nexport class RemoteFileDownloader {\n private readonly offByOneServers: string[] = [];\n\n constructor(public readonly httpClient: Dispatcher) {}\n\n async withContent<T>(\n url: string,\n reqHeaders: Record<string, string>,\n ops: GetContentOptions,\n handler: ContentHandler<T>,\n ): Promise<T> {\n const headers = { ...reqHeaders };\n const urlOrigin = new URL(url).origin;\n\n // Add range header if specified\n if (ops.range) {\n const offByOne = this.offByOneServers.includes(urlOrigin);\n headers[\"Range\"] = `bytes=${ops.range.from}-${ops.range.to - (offByOne ? 0 : 1)}`;\n }\n\n const {\n statusCode,\n body,\n headers: responseHeaders,\n } = await request(url, {\n dispatcher: this.httpClient,\n // Undici automatically sets certain headers, so we need to lowercase user-provided headers\n // to prevent automatic headers from being set and avoid \"duplicated headers\" error.\n headers: Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]),\n ),\n signal: ops.signal,\n highWaterMark: 1 * 1024 * 1024, // 1MB chunks instead of 64KB, tested to be optimal for Human Aging dataset\n });\n ops.signal?.throwIfAborted();\n\n const webBody = Readable.toWeb(body);\n let handlerSuccess = false;\n\n try {\n await checkStatusCodeOk(statusCode, webBody, url);\n ops.signal?.throwIfAborted();\n\n let result: T | undefined = undefined;\n\n const contentLength = Number(responseHeaders[\"content-length\"]);\n if (Number.isNaN(contentLength) || contentLength === 0) {\n // Some backend versions have a bug that they are not returning content-length header.\n // In this case `content-length` header is returned as 0.\n // We should not clip the result stream to 0 bytes in such case.\n result = await handler(webBody, 0);\n } else {\n // Some backend versions have a bug where they return more data than requested in range.\n // So we have to manually normalize the stream to the expected size.\n const size = ops.range ? ops.range.to - ops.range.from : contentLength;\n const normalizedStream = webBody.pipeThrough(\n new (class extends TransformStream {\n constructor(sizeBytes: number, recordOffByOne: () => void) {\n super({\n transform(chunk: Uint8Array, controller) {\n const truncatedChunk = chunk.slice(0, sizeBytes);\n controller.enqueue(truncatedChunk);\n sizeBytes -= truncatedChunk.length;\n if (!sizeBytes) controller.terminate();\n },\n flush(controller) {\n // Some backend versions have a bug where they return 1 less byte than requested in range.\n // We cannot always request one more byte because if this end byte is the last byte of the file,\n // the backend will return 416 (Range Not Satisfiable). So error is thrown to force client to retry the request.\n if (sizeBytes === 1) {\n recordOffByOne();\n controller.error(new OffByOneError());\n }\n },\n });\n }\n })(size, () => this.offByOneServers.push(urlOrigin)),\n );\n result = await handler(normalizedStream, size);\n }\n\n handlerSuccess = true;\n return result;\n } catch (error) {\n // Cleanup on error (including handler errors)\n if (!handlerSuccess && !webBody.locked) {\n try {\n await webBody.cancel();\n } catch {\n // Ignore cleanup errors\n }\n }\n throw error;\n }\n }\n}\n\nasync function checkStatusCodeOk(statusCode: number, webBody: ReadableStream, url: string) {\n if (statusCode != 200 && statusCode != 206 /* partial content from range request */) {\n const beginning = (await text(webBody)).substring(0, 1000);\n if (400 <= statusCode && statusCode < 500)\n throw new DownloadNetworkError400(statusCode, url, beginning);\n throw new DownloadNetworkError(statusCode, url, beginning);\n }\n}\n"],"mappings":";;;;;;;AAaA,IAAa,uBAAb,MAAkC;CAChC,kBAA6C,EAAE;CAE/C,YAAY,YAAwC;AAAxB,OAAA,aAAA;;CAE5B,MAAM,YACJ,KACA,YACA,KACA,SACY;EACZ,MAAM,UAAU,EAAE,GAAG,YAAY;EACjC,MAAM,YAAY,IAAI,IAAI,IAAI,CAAC;AAG/B,MAAI,IAAI,OAAO;GACb,MAAM,WAAW,KAAK,gBAAgB,SAAS,UAAU;AACzD,WAAQ,WAAW,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,MAAM,WAAW,IAAI;;EAG/E,MAAM,EACJ,YACA,MACA,SAAS,oBACP,OAAA,GAAA,OAAA,SAAc,KAAK;GACrB,YAAY,KAAK;GAGjB,SAAS,OAAO,YACd,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,IAAI,aAAa,EAAE,MAAM,CAAC,CAC1E;GACD,QAAQ,IAAI;GACZ,eAAe,IAAI,OAAO;GAC3B,CAAC;AACF,MAAI,QAAQ,gBAAgB;EAE5B,MAAM,UAAUA,YAAAA,SAAS,MAAM,KAAK;EACpC,IAAI,iBAAiB;AAErB,MAAI;AACF,SAAM,kBAAkB,YAAY,SAAS,IAAI;AACjD,OAAI,QAAQ,gBAAgB;GAE5B,IAAI,SAAwB,KAAA;GAE5B,MAAM,gBAAgB,OAAO,gBAAgB,kBAAkB;AAC/D,OAAI,OAAO,MAAM,cAAc,IAAI,kBAAkB,EAInD,UAAS,MAAM,QAAQ,SAAS,EAAE;QAC7B;IAGL,MAAM,OAAO,IAAI,QAAQ,IAAI,MAAM,KAAK,IAAI,MAAM,OAAO;AAwBzD,aAAS,MAAM,QAvBU,QAAQ,YAC/B,IAAK,cAAcC,gBAAAA,gBAAgB;KACjC,YAAY,WAAmB,gBAA4B;AACzD,YAAM;OACJ,UAAU,OAAmB,YAAY;QACvC,MAAM,iBAAiB,MAAM,MAAM,GAAG,UAAU;AAChD,mBAAW,QAAQ,eAAe;AAClC,qBAAa,eAAe;AAC5B,YAAI,CAAC,UAAW,YAAW,WAAW;;OAExC,MAAM,YAAY;AAIhB,YAAI,cAAc,GAAG;AACnB,yBAAgB;AAChB,oBAAW,MAAM,IAAIC,wBAAAA,eAAe,CAAC;;;OAG1C,CAAC;;MAEH,YAAY,KAAK,gBAAgB,KAAK,UAAU,CAAC,CACrD,EACwC,KAAK;;AAGhD,oBAAiB;AACjB,UAAO;WACA,OAAO;AAEd,OAAI,CAAC,kBAAkB,CAAC,QAAQ,OAC9B,KAAI;AACF,UAAM,QAAQ,QAAQ;WAChB;AAIV,SAAM;;;;AAKZ,eAAe,kBAAkB,YAAoB,SAAyB,KAAa;AACzF,KAAI,cAAc,OAAO,cAAc,KAA8C;EACnF,MAAM,aAAa,OAAA,GAAA,sBAAA,MAAW,QAAQ,EAAE,UAAU,GAAG,IAAK;AAC1D,MAAI,OAAO,cAAc,aAAa,IACpC,OAAM,IAAIC,wBAAAA,wBAAwB,YAAY,KAAK,UAAU;AAC/D,QAAM,IAAIC,wBAAAA,qBAAqB,YAAY,KAAK,UAAU"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download.d.ts","names":[],"sources":["../../src/helpers/download.ts"],"mappings":";;;;;KAWY,gBAAA,OAAqB,OAAA,EAAS,cAAA,EAAgB,IAAA,aAAiB,OAAA,CAAQ,CAAA;AAAA,cAEtE,oBAAA;EAAA,SAGiB,UAAA,EAAY,UAAA;EAAA,iBAFvB,eAAA;cAEW,UAAA,EAAY,UAAA;EAElC,WAAA,GAAA,CACJ,GAAA,UACA,UAAA,EAAY,MAAA,kBACZ,GAAA,EAAK,iBAAA,EACL,OAAA,EAAS,gBAAA,CAAe,CAAA,IACvB,OAAA,CAAQ,CAAA;AAAA"}
@@ -3,7 +3,6 @@ import { request } from "undici";
3
3
  import { Readable } from "node:stream";
4
4
  import { TransformStream } from "node:stream/web";
5
5
  import { text } from "node:stream/consumers";
6
-
7
6
  //#region src/helpers/download.ts
8
7
  var RemoteFileDownloader = class {
9
8
  offByOneServers = [];
@@ -70,7 +69,7 @@ async function checkStatusCodeOk(statusCode, webBody, url) {
70
69
  throw new DownloadNetworkError(statusCode, url, beginning);
71
70
  }
72
71
  }
73
-
74
72
  //#endregion
75
73
  export { RemoteFileDownloader };
74
+
76
75
  //# sourceMappingURL=download.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"download.js","names":[],"sources":["../../src/helpers/download.ts"],"sourcesContent":["// @TODO Gleb Zakharov\n/* eslint-disable n/no-unsupported-features/node-builtins */\nimport type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream } from \"node:stream/web\";\nimport { TransformStream } from \"node:stream/web\";\nimport { text } from \"node:stream/consumers\";\nimport type { GetContentOptions } from \"@milaboratories/pl-model-common\";\nimport { OffByOneError, DownloadNetworkError400, DownloadNetworkError } from \"./download_errors\";\n\nexport type ContentHandler<T> = (content: ReadableStream, size: number) => Promise<T>;\n\nexport class RemoteFileDownloader {\n private readonly offByOneServers: string[] = [];\n\n constructor(public readonly httpClient: Dispatcher) {}\n\n async withContent<T>(\n url: string,\n reqHeaders: Record<string, string>,\n ops: GetContentOptions,\n handler: ContentHandler<T>,\n ): Promise<T> {\n const headers = { ...reqHeaders };\n const urlOrigin = new URL(url).origin;\n\n // Add range header if specified\n if (ops.range) {\n const offByOne = this.offByOneServers.includes(urlOrigin);\n headers[\"Range\"] = `bytes=${ops.range.from}-${ops.range.to - (offByOne ? 0 : 1)}`;\n }\n\n const {\n statusCode,\n body,\n headers: responseHeaders,\n } = await request(url, {\n dispatcher: this.httpClient,\n // Undici automatically sets certain headers, so we need to lowercase user-provided headers\n // to prevent automatic headers from being set and avoid \"duplicated headers\" error.\n headers: Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]),\n ),\n signal: ops.signal,\n highWaterMark: 1 * 1024 * 1024, // 1MB chunks instead of 64KB, tested to be optimal for Human Aging dataset\n });\n ops.signal?.throwIfAborted();\n\n const webBody = Readable.toWeb(body);\n let handlerSuccess = false;\n\n try {\n await checkStatusCodeOk(statusCode, webBody, url);\n ops.signal?.throwIfAborted();\n\n let result: T | undefined = undefined;\n\n const contentLength = Number(responseHeaders[\"content-length\"]);\n if (Number.isNaN(contentLength) || contentLength === 0) {\n // Some backend versions have a bug that they are not returning content-length header.\n // In this case `content-length` header is returned as 0.\n // We should not clip the result stream to 0 bytes in such case.\n result = await handler(webBody, 0);\n } else {\n // Some backend versions have a bug where they return more data than requested in range.\n // So we have to manually normalize the stream to the expected size.\n const size = ops.range ? ops.range.to - ops.range.from : contentLength;\n const normalizedStream = webBody.pipeThrough(\n new (class extends TransformStream {\n constructor(sizeBytes: number, recordOffByOne: () => void) {\n super({\n transform(chunk: Uint8Array, controller) {\n const truncatedChunk = chunk.slice(0, sizeBytes);\n controller.enqueue(truncatedChunk);\n sizeBytes -= truncatedChunk.length;\n if (!sizeBytes) controller.terminate();\n },\n flush(controller) {\n // Some backend versions have a bug where they return 1 less byte than requested in range.\n // We cannot always request one more byte because if this end byte is the last byte of the file,\n // the backend will return 416 (Range Not Satisfiable). So error is thrown to force client to retry the request.\n if (sizeBytes === 1) {\n recordOffByOne();\n controller.error(new OffByOneError());\n }\n },\n });\n }\n })(size, () => this.offByOneServers.push(urlOrigin)),\n );\n result = await handler(normalizedStream, size);\n }\n\n handlerSuccess = true;\n return result;\n } catch (error) {\n // Cleanup on error (including handler errors)\n if (!handlerSuccess && !webBody.locked) {\n try {\n await webBody.cancel();\n } catch {\n // Ignore cleanup errors\n }\n }\n throw error;\n }\n }\n}\n\nasync function checkStatusCodeOk(statusCode: number, webBody: ReadableStream, url: string) {\n if (statusCode != 200 && statusCode != 206 /* partial content from range request */) {\n const beginning = (await text(webBody)).substring(0, 1000);\n if (400 <= statusCode && statusCode < 500)\n throw new DownloadNetworkError400(statusCode, url, beginning);\n throw new DownloadNetworkError(statusCode, url, beginning);\n }\n}\n"],"mappings":";;;;;;;AAaA,IAAa,uBAAb,MAAkC;CAChC,AAAiB,kBAA4B,EAAE;CAE/C,YAAY,AAAgB,YAAwB;EAAxB;;CAE5B,MAAM,YACJ,KACA,YACA,KACA,SACY;EACZ,MAAM,UAAU,EAAE,GAAG,YAAY;EACjC,MAAM,YAAY,IAAI,IAAI,IAAI,CAAC;AAG/B,MAAI,IAAI,OAAO;GACb,MAAM,WAAW,KAAK,gBAAgB,SAAS,UAAU;AACzD,WAAQ,WAAW,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,MAAM,WAAW,IAAI;;EAG/E,MAAM,EACJ,YACA,MACA,SAAS,oBACP,MAAM,QAAQ,KAAK;GACrB,YAAY,KAAK;GAGjB,SAAS,OAAO,YACd,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,IAAI,aAAa,EAAE,MAAM,CAAC,CAC1E;GACD,QAAQ,IAAI;GACZ,eAAe,IAAI,OAAO;GAC3B,CAAC;AACF,MAAI,QAAQ,gBAAgB;EAE5B,MAAM,UAAU,SAAS,MAAM,KAAK;EACpC,IAAI,iBAAiB;AAErB,MAAI;AACF,SAAM,kBAAkB,YAAY,SAAS,IAAI;AACjD,OAAI,QAAQ,gBAAgB;GAE5B,IAAI,SAAwB;GAE5B,MAAM,gBAAgB,OAAO,gBAAgB,kBAAkB;AAC/D,OAAI,OAAO,MAAM,cAAc,IAAI,kBAAkB,EAInD,UAAS,MAAM,QAAQ,SAAS,EAAE;QAC7B;IAGL,MAAM,OAAO,IAAI,QAAQ,IAAI,MAAM,KAAK,IAAI,MAAM,OAAO;AAwBzD,aAAS,MAAM,QAvBU,QAAQ,YAC/B,IAAK,cAAc,gBAAgB;KACjC,YAAY,WAAmB,gBAA4B;AACzD,YAAM;OACJ,UAAU,OAAmB,YAAY;QACvC,MAAM,iBAAiB,MAAM,MAAM,GAAG,UAAU;AAChD,mBAAW,QAAQ,eAAe;AAClC,qBAAa,eAAe;AAC5B,YAAI,CAAC,UAAW,YAAW,WAAW;;OAExC,MAAM,YAAY;AAIhB,YAAI,cAAc,GAAG;AACnB,yBAAgB;AAChB,oBAAW,MAAM,IAAI,eAAe,CAAC;;;OAG1C,CAAC;;MAEH,YAAY,KAAK,gBAAgB,KAAK,UAAU,CAAC,CACrD,EACwC,KAAK;;AAGhD,oBAAiB;AACjB,UAAO;WACA,OAAO;AAEd,OAAI,CAAC,kBAAkB,CAAC,QAAQ,OAC9B,KAAI;AACF,UAAM,QAAQ,QAAQ;WAChB;AAIV,SAAM;;;;AAKZ,eAAe,kBAAkB,YAAoB,SAAyB,KAAa;AACzF,KAAI,cAAc,OAAO,cAAc,KAA8C;EACnF,MAAM,aAAa,MAAM,KAAK,QAAQ,EAAE,UAAU,GAAG,IAAK;AAC1D,MAAI,OAAO,cAAc,aAAa,IACpC,OAAM,IAAI,wBAAwB,YAAY,KAAK,UAAU;AAC/D,QAAM,IAAI,qBAAqB,YAAY,KAAK,UAAU"}
1
+ {"version":3,"file":"download.js","names":[],"sources":["../../src/helpers/download.ts"],"sourcesContent":["// @TODO Gleb Zakharov\n/* eslint-disable n/no-unsupported-features/node-builtins */\nimport type { Dispatcher } from \"undici\";\nimport { request } from \"undici\";\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream } from \"node:stream/web\";\nimport { TransformStream } from \"node:stream/web\";\nimport { text } from \"node:stream/consumers\";\nimport type { GetContentOptions } from \"@milaboratories/pl-model-common\";\nimport { OffByOneError, DownloadNetworkError400, DownloadNetworkError } from \"./download_errors\";\n\nexport type ContentHandler<T> = (content: ReadableStream, size: number) => Promise<T>;\n\nexport class RemoteFileDownloader {\n private readonly offByOneServers: string[] = [];\n\n constructor(public readonly httpClient: Dispatcher) {}\n\n async withContent<T>(\n url: string,\n reqHeaders: Record<string, string>,\n ops: GetContentOptions,\n handler: ContentHandler<T>,\n ): Promise<T> {\n const headers = { ...reqHeaders };\n const urlOrigin = new URL(url).origin;\n\n // Add range header if specified\n if (ops.range) {\n const offByOne = this.offByOneServers.includes(urlOrigin);\n headers[\"Range\"] = `bytes=${ops.range.from}-${ops.range.to - (offByOne ? 0 : 1)}`;\n }\n\n const {\n statusCode,\n body,\n headers: responseHeaders,\n } = await request(url, {\n dispatcher: this.httpClient,\n // Undici automatically sets certain headers, so we need to lowercase user-provided headers\n // to prevent automatic headers from being set and avoid \"duplicated headers\" error.\n headers: Object.fromEntries(\n Object.entries(headers).map(([key, value]) => [key.toLowerCase(), value]),\n ),\n signal: ops.signal,\n highWaterMark: 1 * 1024 * 1024, // 1MB chunks instead of 64KB, tested to be optimal for Human Aging dataset\n });\n ops.signal?.throwIfAborted();\n\n const webBody = Readable.toWeb(body);\n let handlerSuccess = false;\n\n try {\n await checkStatusCodeOk(statusCode, webBody, url);\n ops.signal?.throwIfAborted();\n\n let result: T | undefined = undefined;\n\n const contentLength = Number(responseHeaders[\"content-length\"]);\n if (Number.isNaN(contentLength) || contentLength === 0) {\n // Some backend versions have a bug that they are not returning content-length header.\n // In this case `content-length` header is returned as 0.\n // We should not clip the result stream to 0 bytes in such case.\n result = await handler(webBody, 0);\n } else {\n // Some backend versions have a bug where they return more data than requested in range.\n // So we have to manually normalize the stream to the expected size.\n const size = ops.range ? ops.range.to - ops.range.from : contentLength;\n const normalizedStream = webBody.pipeThrough(\n new (class extends TransformStream {\n constructor(sizeBytes: number, recordOffByOne: () => void) {\n super({\n transform(chunk: Uint8Array, controller) {\n const truncatedChunk = chunk.slice(0, sizeBytes);\n controller.enqueue(truncatedChunk);\n sizeBytes -= truncatedChunk.length;\n if (!sizeBytes) controller.terminate();\n },\n flush(controller) {\n // Some backend versions have a bug where they return 1 less byte than requested in range.\n // We cannot always request one more byte because if this end byte is the last byte of the file,\n // the backend will return 416 (Range Not Satisfiable). So error is thrown to force client to retry the request.\n if (sizeBytes === 1) {\n recordOffByOne();\n controller.error(new OffByOneError());\n }\n },\n });\n }\n })(size, () => this.offByOneServers.push(urlOrigin)),\n );\n result = await handler(normalizedStream, size);\n }\n\n handlerSuccess = true;\n return result;\n } catch (error) {\n // Cleanup on error (including handler errors)\n if (!handlerSuccess && !webBody.locked) {\n try {\n await webBody.cancel();\n } catch {\n // Ignore cleanup errors\n }\n }\n throw error;\n }\n }\n}\n\nasync function checkStatusCodeOk(statusCode: number, webBody: ReadableStream, url: string) {\n if (statusCode != 200 && statusCode != 206 /* partial content from range request */) {\n const beginning = (await text(webBody)).substring(0, 1000);\n if (400 <= statusCode && statusCode < 500)\n throw new DownloadNetworkError400(statusCode, url, beginning);\n throw new DownloadNetworkError(statusCode, url, beginning);\n }\n}\n"],"mappings":";;;;;;AAaA,IAAa,uBAAb,MAAkC;CAChC,kBAA6C,EAAE;CAE/C,YAAY,YAAwC;AAAxB,OAAA,aAAA;;CAE5B,MAAM,YACJ,KACA,YACA,KACA,SACY;EACZ,MAAM,UAAU,EAAE,GAAG,YAAY;EACjC,MAAM,YAAY,IAAI,IAAI,IAAI,CAAC;AAG/B,MAAI,IAAI,OAAO;GACb,MAAM,WAAW,KAAK,gBAAgB,SAAS,UAAU;AACzD,WAAQ,WAAW,SAAS,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,MAAM,WAAW,IAAI;;EAG/E,MAAM,EACJ,YACA,MACA,SAAS,oBACP,MAAM,QAAQ,KAAK;GACrB,YAAY,KAAK;GAGjB,SAAS,OAAO,YACd,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,IAAI,aAAa,EAAE,MAAM,CAAC,CAC1E;GACD,QAAQ,IAAI;GACZ,eAAe,IAAI,OAAO;GAC3B,CAAC;AACF,MAAI,QAAQ,gBAAgB;EAE5B,MAAM,UAAU,SAAS,MAAM,KAAK;EACpC,IAAI,iBAAiB;AAErB,MAAI;AACF,SAAM,kBAAkB,YAAY,SAAS,IAAI;AACjD,OAAI,QAAQ,gBAAgB;GAE5B,IAAI,SAAwB,KAAA;GAE5B,MAAM,gBAAgB,OAAO,gBAAgB,kBAAkB;AAC/D,OAAI,OAAO,MAAM,cAAc,IAAI,kBAAkB,EAInD,UAAS,MAAM,QAAQ,SAAS,EAAE;QAC7B;IAGL,MAAM,OAAO,IAAI,QAAQ,IAAI,MAAM,KAAK,IAAI,MAAM,OAAO;AAwBzD,aAAS,MAAM,QAvBU,QAAQ,YAC/B,IAAK,cAAc,gBAAgB;KACjC,YAAY,WAAmB,gBAA4B;AACzD,YAAM;OACJ,UAAU,OAAmB,YAAY;QACvC,MAAM,iBAAiB,MAAM,MAAM,GAAG,UAAU;AAChD,mBAAW,QAAQ,eAAe;AAClC,qBAAa,eAAe;AAC5B,YAAI,CAAC,UAAW,YAAW,WAAW;;OAExC,MAAM,YAAY;AAIhB,YAAI,cAAc,GAAG;AACnB,yBAAgB;AAChB,oBAAW,MAAM,IAAI,eAAe,CAAC;;;OAG1C,CAAC;;MAEH,YAAY,KAAK,gBAAgB,KAAK,UAAU,CAAC,CACrD,EACwC,KAAK;;AAGhD,oBAAiB;AACjB,UAAO;WACA,OAAO;AAEd,OAAI,CAAC,kBAAkB,CAAC,QAAQ,OAC9B,KAAI;AACF,UAAM,QAAQ,QAAQ;WAChB;AAIV,SAAM;;;;AAKZ,eAAe,kBAAkB,YAAoB,SAAyB,KAAa;AACzF,KAAI,cAAc,OAAO,cAAc,KAA8C;EACnF,MAAM,aAAa,MAAM,KAAK,QAAQ,EAAE,UAAU,GAAG,IAAK;AAC1D,MAAI,OAAO,cAAc,aAAa,IACpC,OAAM,IAAI,wBAAwB,YAAY,KAAK,UAAU;AAC/D,QAAM,IAAI,qBAAqB,YAAY,KAAK,UAAU"}
@@ -1,4 +1,3 @@
1
-
2
1
  //#region src/helpers/download_errors.ts
3
2
  var DownloadNetworkError = class extends Error {
4
3
  name = "DownloadNetworkError";
@@ -33,7 +32,6 @@ var OffByOneError = class extends Error {
33
32
  function isOffByOneError(error) {
34
33
  return error instanceof Error && error.name === "OffByOneError";
35
34
  }
36
-
37
35
  //#endregion
38
36
  exports.DownloadNetworkError = DownloadNetworkError;
39
37
  exports.DownloadNetworkError400 = DownloadNetworkError400;
@@ -41,4 +39,5 @@ exports.OffByOneError = OffByOneError;
41
39
  exports.isDownloadNetworkError = isDownloadNetworkError;
42
40
  exports.isDownloadNetworkError400 = isDownloadNetworkError400;
43
41
  exports.isOffByOneError = isOffByOneError;
42
+
44
43
  //# sourceMappingURL=download_errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"download_errors.cjs","names":[],"sources":["../../src/helpers/download_errors.ts"],"sourcesContent":["export class DownloadNetworkError extends Error {\n name = \"DownloadNetworkError\";\n\n statusCode: number;\n url: string;\n beginning: string;\n\n constructor(statusCode: number, url: string, beginning: string) {\n super(\n `Http download error: statusCode: ${statusCode} url: ${url.toString()}, beginning of body: ${beginning}`,\n );\n this.statusCode = statusCode;\n this.url = url;\n this.beginning = beginning;\n }\n}\n\nexport function isDownloadNetworkError(error: unknown): error is DownloadNetworkError {\n return error instanceof Error && error.name.startsWith(\"DownloadNetworkError\");\n}\n\n/** Throws when a status code of the downloading URL was in range [400, 500). */\nexport class DownloadNetworkError400 extends DownloadNetworkError {\n name = \"DownloadNetworkError400\";\n}\n\nexport function isDownloadNetworkError400(error: unknown): error is DownloadNetworkError400 {\n return error instanceof Error && error.name === \"DownloadNetworkError400\";\n}\n\n/**\n * There are backend versions that return 1 less byte than requested in range.\n * For such cases, this error will be thrown, so client can retry the request.\n * Dowloader will retry the request with one more byte in range.\n */\nexport class OffByOneError extends Error {\n name = \"OffByOneError\";\n}\n\nexport function isOffByOneError(error: unknown): error is OffByOneError {\n return error instanceof Error && error.name === \"OffByOneError\";\n}\n"],"mappings":";;AAAA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,OAAO;CAEP;CACA;CACA;CAEA,YAAY,YAAoB,KAAa,WAAmB;AAC9D,QACE,oCAAoC,WAAW,QAAQ,IAAI,UAAU,CAAC,uBAAuB,YAC9F;AACD,OAAK,aAAa;AAClB,OAAK,MAAM;AACX,OAAK,YAAY;;;AAIrB,SAAgB,uBAAuB,OAA+C;AACpF,QAAO,iBAAiB,SAAS,MAAM,KAAK,WAAW,uBAAuB;;;AAIhF,IAAa,0BAAb,cAA6C,qBAAqB;CAChE,OAAO;;AAGT,SAAgB,0BAA0B,OAAkD;AAC1F,QAAO,iBAAiB,SAAS,MAAM,SAAS;;;;;;;AAQlD,IAAa,gBAAb,cAAmC,MAAM;CACvC,OAAO;;AAGT,SAAgB,gBAAgB,OAAwC;AACtE,QAAO,iBAAiB,SAAS,MAAM,SAAS"}
1
+ {"version":3,"file":"download_errors.cjs","names":[],"sources":["../../src/helpers/download_errors.ts"],"sourcesContent":["export class DownloadNetworkError extends Error {\n name = \"DownloadNetworkError\";\n\n statusCode: number;\n url: string;\n beginning: string;\n\n constructor(statusCode: number, url: string, beginning: string) {\n super(\n `Http download error: statusCode: ${statusCode} url: ${url.toString()}, beginning of body: ${beginning}`,\n );\n this.statusCode = statusCode;\n this.url = url;\n this.beginning = beginning;\n }\n}\n\nexport function isDownloadNetworkError(error: unknown): error is DownloadNetworkError {\n return error instanceof Error && error.name.startsWith(\"DownloadNetworkError\");\n}\n\n/** Throws when a status code of the downloading URL was in range [400, 500). */\nexport class DownloadNetworkError400 extends DownloadNetworkError {\n name = \"DownloadNetworkError400\";\n}\n\nexport function isDownloadNetworkError400(error: unknown): error is DownloadNetworkError400 {\n return error instanceof Error && error.name === \"DownloadNetworkError400\";\n}\n\n/**\n * There are backend versions that return 1 less byte than requested in range.\n * For such cases, this error will be thrown, so client can retry the request.\n * Dowloader will retry the request with one more byte in range.\n */\nexport class OffByOneError extends Error {\n name = \"OffByOneError\";\n}\n\nexport function isOffByOneError(error: unknown): error is OffByOneError {\n return error instanceof Error && error.name === \"OffByOneError\";\n}\n"],"mappings":";AAAA,IAAa,uBAAb,cAA0C,MAAM;CAC9C,OAAO;CAEP;CACA;CACA;CAEA,YAAY,YAAoB,KAAa,WAAmB;AAC9D,QACE,oCAAoC,WAAW,QAAQ,IAAI,UAAU,CAAC,uBAAuB,YAC9F;AACD,OAAK,aAAa;AAClB,OAAK,MAAM;AACX,OAAK,YAAY;;;AAIrB,SAAgB,uBAAuB,OAA+C;AACpF,QAAO,iBAAiB,SAAS,MAAM,KAAK,WAAW,uBAAuB;;;AAIhF,IAAa,0BAAb,cAA6C,qBAAqB;CAChE,OAAO;;AAGT,SAAgB,0BAA0B,OAAkD;AAC1F,QAAO,iBAAiB,SAAS,MAAM,SAAS;;;;;;;AAQlD,IAAa,gBAAb,cAAmC,MAAM;CACvC,OAAO;;AAGT,SAAgB,gBAAgB,OAAwC;AACtE,QAAO,iBAAiB,SAAS,MAAM,SAAS"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"download_errors.d.ts","names":[],"sources":["../../src/helpers/download_errors.ts"],"mappings":";cAAa,oBAAA,SAA6B,KAAA;EACxC,IAAA;EAEA,UAAA;EACA,GAAA;EACA,SAAA;cAEY,UAAA,UAAoB,GAAA,UAAa,SAAA;AAAA;AAAA,iBAU/B,sBAAA,CAAuB,KAAA,YAAiB,KAAA,IAAS,oBAAA;;cAKpD,uBAAA,SAAgC,oBAAA;EAC3C,IAAA;AAAA;AAAA,iBAGc,yBAAA,CAA0B,KAAA,YAAiB,KAAA,IAAS,uBAAA;;;;AATpE;;cAkBa,aAAA,SAAsB,KAAA;EACjC,IAAA;AAAA;AAAA,iBAGc,eAAA,CAAgB,KAAA,YAAiB,KAAA,IAAS,aAAA"}