@milaboratories/pl-middle-layer 1.74.11 → 1.74.12

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 (320) hide show
  1. package/dist/_virtual/{_@oxc-project_runtime@0.137.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/usingCtx.cjs +1 -2
  2. package/dist/_virtual/{_@oxc-project_runtime@0.137.0 → _@oxc-project_runtime@0.155.0}/helpers/esm/usingCtx.js +1 -2
  3. package/dist/_virtual/_rolldown/runtime.cjs +2 -3
  4. package/dist/block_registry/location_provider.cjs +1 -2
  5. package/dist/block_registry/location_provider.cjs.map +1 -1
  6. package/dist/block_registry/location_provider.js +1 -2
  7. package/dist/block_registry/registry-v2-provider.cjs +1 -2
  8. package/dist/block_registry/registry-v2-provider.cjs.map +1 -1
  9. package/dist/block_registry/registry-v2-provider.d.ts +0 -2
  10. package/dist/block_registry/registry-v2-provider.d.ts.map +1 -1
  11. package/dist/block_registry/registry-v2-provider.js +1 -2
  12. package/dist/block_registry/registry.cjs +3 -3
  13. package/dist/block_registry/registry.cjs.map +1 -1
  14. package/dist/block_registry/registry.d.ts +0 -2
  15. package/dist/block_registry/registry.d.ts.map +1 -1
  16. package/dist/block_registry/registry.js +3 -3
  17. package/dist/block_registry/registry.js.map +1 -1
  18. package/dist/block_registry/template_provider.cjs +1 -2
  19. package/dist/block_registry/template_provider.cjs.map +1 -1
  20. package/dist/block_registry/template_provider.d.ts +0 -2
  21. package/dist/block_registry/template_provider.d.ts.map +1 -1
  22. package/dist/block_registry/template_provider.js +1 -2
  23. package/dist/block_registry/watcher.cjs +15 -14
  24. package/dist/block_registry/watcher.cjs.map +1 -1
  25. package/dist/block_registry/watcher.d.ts +0 -2
  26. package/dist/block_registry/watcher.d.ts.map +1 -1
  27. package/dist/block_registry/watcher.js +15 -14
  28. package/dist/block_registry/watcher.js.map +1 -1
  29. package/dist/block_registry/well_known_registries.cjs +1 -2
  30. package/dist/block_registry/well_known_registries.d.ts +0 -2
  31. package/dist/block_registry/well_known_registries.d.ts.map +1 -1
  32. package/dist/block_registry/well_known_registries.js +1 -2
  33. package/dist/cfg_render/executor.cjs +1 -2
  34. package/dist/cfg_render/executor.cjs.map +1 -1
  35. package/dist/cfg_render/executor.js +1 -2
  36. package/dist/cfg_render/renderer.cjs +1 -2
  37. package/dist/cfg_render/renderer.cjs.map +1 -1
  38. package/dist/cfg_render/renderer.js +1 -2
  39. package/dist/cfg_render/traverse.cjs +1 -2
  40. package/dist/cfg_render/traverse.js +1 -2
  41. package/dist/cfg_render/util.cjs +1 -2
  42. package/dist/cfg_render/util.js +1 -2
  43. package/dist/debug/index.cjs +1 -2
  44. package/dist/debug/index.js +1 -2
  45. package/dist/dev_env/index.cjs +1 -2
  46. package/dist/dev_env/index.cjs.map +1 -1
  47. package/dist/dev_env/index.js +1 -2
  48. package/dist/dev_env/util.cjs +1 -2
  49. package/dist/dev_env/util.js +1 -2
  50. package/dist/js_render/column_registry.cjs +1 -2
  51. package/dist/js_render/column_registry.cjs.map +1 -1
  52. package/dist/js_render/column_registry.js +1 -2
  53. package/dist/js_render/computable_context.cjs +4 -4
  54. package/dist/js_render/computable_context.cjs.map +1 -1
  55. package/dist/js_render/computable_context.js +4 -4
  56. package/dist/js_render/computable_context.js.map +1 -1
  57. package/dist/js_render/context.cjs +1 -2
  58. package/dist/js_render/context.cjs.map +1 -1
  59. package/dist/js_render/context.js +1 -2
  60. package/dist/js_render/index.cjs +12 -9
  61. package/dist/js_render/index.cjs.map +1 -1
  62. package/dist/js_render/index.js +12 -9
  63. package/dist/js_render/index.js.map +1 -1
  64. package/dist/js_render/service_injectors.cjs +2 -3
  65. package/dist/js_render/service_injectors.js +2 -3
  66. package/dist/js_render/spec_override_collapse.cjs +1 -2
  67. package/dist/js_render/spec_override_collapse.cjs.map +1 -1
  68. package/dist/js_render/spec_override_collapse.js +1 -2
  69. package/dist/middle_layer/active_cfg.cjs +1 -2
  70. package/dist/middle_layer/active_cfg.cjs.map +1 -1
  71. package/dist/middle_layer/active_cfg.js +1 -2
  72. package/dist/middle_layer/block.cjs +1 -2
  73. package/dist/middle_layer/block.cjs.map +1 -1
  74. package/dist/middle_layer/block.js +1 -2
  75. package/dist/middle_layer/block_ctx.cjs +3 -3
  76. package/dist/middle_layer/block_ctx.cjs.map +1 -1
  77. package/dist/middle_layer/block_ctx.js +3 -3
  78. package/dist/middle_layer/block_ctx.js.map +1 -1
  79. package/dist/middle_layer/block_ctx_unsafe.cjs +1 -2
  80. package/dist/middle_layer/block_ctx_unsafe.js +1 -2
  81. package/dist/middle_layer/build_stamp.cjs +2 -3
  82. package/dist/middle_layer/build_stamp.js +2 -3
  83. package/dist/middle_layer/driver_kit.cjs +1 -2
  84. package/dist/middle_layer/driver_kit.cjs.map +1 -1
  85. package/dist/middle_layer/driver_kit.d.ts +0 -2
  86. package/dist/middle_layer/driver_kit.d.ts.map +1 -1
  87. package/dist/middle_layer/driver_kit.js +1 -2
  88. package/dist/middle_layer/finality_stop_rules.cjs +1 -2
  89. package/dist/middle_layer/finality_stop_rules.js +1 -2
  90. package/dist/middle_layer/folders.cjs +12 -8
  91. package/dist/middle_layer/folders.cjs.map +1 -1
  92. package/dist/middle_layer/folders.d.ts +0 -2
  93. package/dist/middle_layer/folders.d.ts.map +1 -1
  94. package/dist/middle_layer/folders.js +12 -8
  95. package/dist/middle_layer/folders.js.map +1 -1
  96. package/dist/middle_layer/frontend_path.cjs +1 -2
  97. package/dist/middle_layer/frontend_path.cjs.map +1 -1
  98. package/dist/middle_layer/frontend_path.js +1 -2
  99. package/dist/middle_layer/middle_layer.cjs +7 -6
  100. package/dist/middle_layer/middle_layer.cjs.map +1 -1
  101. package/dist/middle_layer/middle_layer.d.ts +0 -2
  102. package/dist/middle_layer/middle_layer.d.ts.map +1 -1
  103. package/dist/middle_layer/middle_layer.js +7 -6
  104. package/dist/middle_layer/middle_layer.js.map +1 -1
  105. package/dist/middle_layer/navigation_states.cjs +1 -2
  106. package/dist/middle_layer/navigation_states.js +1 -2
  107. package/dist/middle_layer/ops.cjs +10 -11
  108. package/dist/middle_layer/ops.cjs.map +1 -1
  109. package/dist/middle_layer/ops.d.ts +0 -2
  110. package/dist/middle_layer/ops.d.ts.map +1 -1
  111. package/dist/middle_layer/ops.js +10 -11
  112. package/dist/middle_layer/ops.js.map +1 -1
  113. package/dist/middle_layer/project.cjs +3 -3
  114. package/dist/middle_layer/project.cjs.map +1 -1
  115. package/dist/middle_layer/project.d.ts +3 -5
  116. package/dist/middle_layer/project.d.ts.map +1 -1
  117. package/dist/middle_layer/project.js +3 -3
  118. package/dist/middle_layer/project.js.map +1 -1
  119. package/dist/middle_layer/project_list.cjs +1 -2
  120. package/dist/middle_layer/project_list.cjs.map +1 -1
  121. package/dist/middle_layer/project_list.d.ts +0 -2
  122. package/dist/middle_layer/project_list.d.ts.map +1 -1
  123. package/dist/middle_layer/project_list.js +1 -2
  124. package/dist/middle_layer/project_overview.cjs +8 -5
  125. package/dist/middle_layer/project_overview.cjs.map +1 -1
  126. package/dist/middle_layer/project_overview.js +8 -5
  127. package/dist/middle_layer/project_overview.js.map +1 -1
  128. package/dist/middle_layer/project_overview_light.cjs +4 -3
  129. package/dist/middle_layer/project_overview_light.cjs.map +1 -1
  130. package/dist/middle_layer/project_overview_light.js +4 -3
  131. package/dist/middle_layer/project_overview_light.js.map +1 -1
  132. package/dist/middle_layer/render.cjs +1 -2
  133. package/dist/middle_layer/render.cjs.map +1 -1
  134. package/dist/middle_layer/render.js +1 -2
  135. package/dist/middle_layer/sharing_list.cjs +1 -2
  136. package/dist/middle_layer/sharing_list.cjs.map +1 -1
  137. package/dist/middle_layer/sharing_list.d.ts +0 -2
  138. package/dist/middle_layer/sharing_list.d.ts.map +1 -1
  139. package/dist/middle_layer/sharing_list.js +1 -2
  140. package/dist/middle_layer/sharing_list.js.map +1 -1
  141. package/dist/middle_layer/template_list.cjs +1 -2
  142. package/dist/middle_layer/template_list.cjs.map +1 -1
  143. package/dist/middle_layer/template_list.d.ts +0 -2
  144. package/dist/middle_layer/template_list.d.ts.map +1 -1
  145. package/dist/middle_layer/template_list.js +1 -2
  146. package/dist/middle_layer/tree_snapshot_store.cjs +3 -3
  147. package/dist/middle_layer/tree_snapshot_store.cjs.map +1 -1
  148. package/dist/middle_layer/tree_snapshot_store.d.ts +0 -2
  149. package/dist/middle_layer/tree_snapshot_store.d.ts.map +1 -1
  150. package/dist/middle_layer/tree_snapshot_store.js +3 -3
  151. package/dist/middle_layer/tree_snapshot_store.js.map +1 -1
  152. package/dist/middle_layer/types.d.ts +0 -2
  153. package/dist/middle_layer/types.d.ts.map +1 -1
  154. package/dist/middle_layer/util.cjs +1 -2
  155. package/dist/middle_layer/util.cjs.map +1 -1
  156. package/dist/middle_layer/util.js +1 -2
  157. package/dist/model/args.cjs +1 -2
  158. package/dist/model/args.cjs.map +1 -1
  159. package/dist/model/args.js +1 -2
  160. package/dist/model/block_pack_spec.cjs +1 -2
  161. package/dist/model/block_pack_spec.d.ts +0 -2
  162. package/dist/model/block_pack_spec.d.ts.map +1 -1
  163. package/dist/model/block_pack_spec.js +1 -2
  164. package/dist/model/frontend.d.ts +0 -2
  165. package/dist/model/frontend.d.ts.map +1 -1
  166. package/dist/model/project_helper.cjs +1 -2
  167. package/dist/model/project_helper.cjs.map +1 -1
  168. package/dist/model/project_helper.d.ts +0 -2
  169. package/dist/model/project_helper.d.ts.map +1 -1
  170. package/dist/model/project_helper.js +1 -2
  171. package/dist/model/project_model.cjs +1 -2
  172. package/dist/model/project_model.d.ts +0 -2
  173. package/dist/model/project_model.d.ts.map +1 -1
  174. package/dist/model/project_model.js +1 -2
  175. package/dist/model/project_model_util.cjs +1 -2
  176. package/dist/model/project_model_util.js +1 -2
  177. package/dist/model/project_model_v1.cjs +1 -2
  178. package/dist/model/project_model_v1.js +1 -2
  179. package/dist/model/sharing_model.cjs +1 -2
  180. package/dist/model/sharing_model.cjs.map +1 -1
  181. package/dist/model/sharing_model.d.ts +0 -2
  182. package/dist/model/sharing_model.d.ts.map +1 -1
  183. package/dist/model/sharing_model.js +1 -2
  184. package/dist/model/template_apply.cjs +1 -2
  185. package/dist/model/template_apply.cjs.map +1 -1
  186. package/dist/model/template_apply.d.ts +0 -2
  187. package/dist/model/template_apply.d.ts.map +1 -1
  188. package/dist/model/template_apply.js +1 -2
  189. package/dist/model/template_export.cjs +1 -2
  190. package/dist/model/template_export.d.ts +0 -2
  191. package/dist/model/template_export.d.ts.map +1 -1
  192. package/dist/model/template_export.js +1 -2
  193. package/dist/model/template_parser.cjs +1 -2
  194. package/dist/model/template_parser.cjs.map +1 -1
  195. package/dist/model/template_parser.d.ts +0 -2
  196. package/dist/model/template_parser.d.ts.map +1 -1
  197. package/dist/model/template_parser.js +1 -2
  198. package/dist/model/template_resolve.cjs +1 -2
  199. package/dist/model/template_resolve.cjs.map +1 -1
  200. package/dist/model/template_resolve.d.ts +0 -2
  201. package/dist/model/template_resolve.d.ts.map +1 -1
  202. package/dist/model/template_resolve.js +1 -2
  203. package/dist/model/template_serializer.cjs +1 -2
  204. package/dist/model/template_serializer.cjs.map +1 -1
  205. package/dist/model/template_serializer.d.ts +0 -2
  206. package/dist/model/template_serializer.d.ts.map +1 -1
  207. package/dist/model/template_serializer.js +1 -2
  208. package/dist/model/template_spec.d.ts +0 -2
  209. package/dist/model/template_spec.d.ts.map +1 -1
  210. package/dist/mutator/block-pack/block_pack.cjs +7 -6
  211. package/dist/mutator/block-pack/block_pack.cjs.map +1 -1
  212. package/dist/mutator/block-pack/block_pack.d.ts +0 -2
  213. package/dist/mutator/block-pack/block_pack.d.ts.map +1 -1
  214. package/dist/mutator/block-pack/block_pack.js +7 -6
  215. package/dist/mutator/block-pack/block_pack.js.map +1 -1
  216. package/dist/mutator/block-pack/frontend.cjs +1 -2
  217. package/dist/mutator/block-pack/frontend.cjs.map +1 -1
  218. package/dist/mutator/block-pack/frontend.js +1 -2
  219. package/dist/mutator/block-pack/required_capabilities.cjs +1 -2
  220. package/dist/mutator/block-pack/required_capabilities.cjs.map +1 -1
  221. package/dist/mutator/block-pack/required_capabilities.js +1 -2
  222. package/dist/mutator/context_export.cjs +1 -2
  223. package/dist/mutator/context_export.cjs.map +1 -1
  224. package/dist/mutator/context_export.js +1 -2
  225. package/dist/mutator/list.cjs +1 -2
  226. package/dist/mutator/list.cjs.map +1 -1
  227. package/dist/mutator/list.js +1 -2
  228. package/dist/mutator/migration.cjs +1 -2
  229. package/dist/mutator/migration.cjs.map +1 -1
  230. package/dist/mutator/migration.js +1 -2
  231. package/dist/mutator/project.cjs +30 -22
  232. package/dist/mutator/project.cjs.map +1 -1
  233. package/dist/mutator/project.d.ts +0 -2
  234. package/dist/mutator/project.d.ts.map +1 -1
  235. package/dist/mutator/project.js +30 -22
  236. package/dist/mutator/project.js.map +1 -1
  237. package/dist/mutator/sharing.cjs +1 -2
  238. package/dist/mutator/sharing.cjs.map +1 -1
  239. package/dist/mutator/sharing.js +1 -2
  240. package/dist/mutator/template/direct_template_loader.cjs +1 -2
  241. package/dist/mutator/template/direct_template_loader.cjs.map +1 -1
  242. package/dist/mutator/template/direct_template_loader.js +1 -2
  243. package/dist/mutator/template/direct_template_loader_v3.cjs +1 -2
  244. package/dist/mutator/template/direct_template_loader_v3.cjs.map +1 -1
  245. package/dist/mutator/template/direct_template_loader_v3.js +1 -2
  246. package/dist/mutator/template/render_block.cjs +1 -2
  247. package/dist/mutator/template/render_block.cjs.map +1 -1
  248. package/dist/mutator/template/render_block.js +1 -2
  249. package/dist/mutator/template/render_template.cjs +1 -2
  250. package/dist/mutator/template/render_template.cjs.map +1 -1
  251. package/dist/mutator/template/render_template.d.ts +0 -2
  252. package/dist/mutator/template/render_template.d.ts.map +1 -1
  253. package/dist/mutator/template/render_template.js +1 -2
  254. package/dist/mutator/template/template_cache.cjs +6 -5
  255. package/dist/mutator/template/template_cache.cjs.map +1 -1
  256. package/dist/mutator/template/template_cache.d.ts +1 -2
  257. package/dist/mutator/template/template_cache.d.ts.map +1 -1
  258. package/dist/mutator/template/template_cache.js +6 -5
  259. package/dist/mutator/template/template_cache.js.map +1 -1
  260. package/dist/mutator/template/template_loading.cjs +1 -2
  261. package/dist/mutator/template/template_loading.cjs.map +1 -1
  262. package/dist/mutator/template/template_loading.d.ts +0 -2
  263. package/dist/mutator/template/template_loading.d.ts.map +1 -1
  264. package/dist/mutator/template/template_loading.js +1 -2
  265. package/dist/mutator/template.cjs +1 -2
  266. package/dist/mutator/template.cjs.map +1 -1
  267. package/dist/mutator/template.js +1 -2
  268. package/dist/mutator/template_construct.cjs +1 -2
  269. package/dist/mutator/template_construct.cjs.map +1 -1
  270. package/dist/mutator/template_construct.js +1 -2
  271. package/dist/network_check/network_check.cjs +2 -3
  272. package/dist/network_check/network_check.cjs.map +1 -1
  273. package/dist/network_check/network_check.d.ts +0 -2
  274. package/dist/network_check/network_check.d.ts.map +1 -1
  275. package/dist/network_check/network_check.js +2 -3
  276. package/dist/network_check/network_check.js.map +1 -1
  277. package/dist/network_check/pings.cjs +1 -2
  278. package/dist/network_check/pings.cjs.map +1 -1
  279. package/dist/network_check/pings.js +1 -2
  280. package/dist/network_check/template.cjs +5 -4
  281. package/dist/network_check/template.cjs.map +1 -1
  282. package/dist/network_check/template.js +5 -4
  283. package/dist/network_check/template.js.map +1 -1
  284. package/dist/pool/data.cjs +1 -2
  285. package/dist/pool/data.cjs.map +1 -1
  286. package/dist/pool/data.d.ts +0 -2
  287. package/dist/pool/data.d.ts.map +1 -1
  288. package/dist/pool/data.js +1 -2
  289. package/dist/pool/driver.cjs +5 -4
  290. package/dist/pool/driver.cjs.map +1 -1
  291. package/dist/pool/driver.d.ts +0 -2
  292. package/dist/pool/driver.d.ts.map +1 -1
  293. package/dist/pool/driver.js +5 -4
  294. package/dist/pool/driver.js.map +1 -1
  295. package/dist/pool/p_object_collection.cjs +9 -8
  296. package/dist/pool/p_object_collection.cjs.map +1 -1
  297. package/dist/pool/p_object_collection.d.ts +0 -2
  298. package/dist/pool/p_object_collection.d.ts.map +1 -1
  299. package/dist/pool/p_object_collection.js +9 -8
  300. package/dist/pool/p_object_collection.js.map +1 -1
  301. package/dist/pool/result_pool.cjs +8 -7
  302. package/dist/pool/result_pool.cjs.map +1 -1
  303. package/dist/pool/result_pool.js +8 -7
  304. package/dist/pool/result_pool.js.map +1 -1
  305. package/dist/pool/upstream_block_ctx.cjs +1 -2
  306. package/dist/pool/upstream_block_ctx.cjs.map +1 -1
  307. package/dist/pool/upstream_block_ctx.js +1 -2
  308. package/dist/service_factories.cjs +1 -2
  309. package/dist/service_factories.js +1 -2
  310. package/dist/worker/WorkerManager.cjs +1 -2
  311. package/dist/worker/WorkerManager.js +1 -2
  312. package/dist/worker/WorkerManager.js.map +1 -1
  313. package/dist/worker/worker.cjs +1 -2
  314. package/dist/worker/worker.js +1 -2
  315. package/dist/worker/workerApi.cjs +1 -2
  316. package/dist/worker/workerApi.cjs.map +1 -1
  317. package/dist/worker/workerApi.js +1 -2
  318. package/package.json +33 -33
  319. package/src/middle_layer/middle_layer.ts +4 -6
  320. package/src/middle_layer/tree_snapshot_store.test.ts +4 -1
@@ -1,7 +1,7 @@
1
1
  const require_template_apply = require("../model/template_apply.cjs");
2
2
  let _platforma_sdk_model = require("@platforma-sdk/model");
3
3
  let node_crypto = require("node:crypto");
4
- //#region src/mutator/template_construct.ts
4
+ // src/mutator/template_construct.ts
5
5
  /**
6
6
  * Apply a `template-v1` document: create each entry's block, in file order.
7
7
  *
@@ -71,7 +71,6 @@ function applyTemplateEntries(deps) {
71
71
  }
72
72
  return added;
73
73
  }
74
- //#endregion
75
74
  exports.applyTemplateEntries = applyTemplateEntries;
76
75
 
77
76
  //# sourceMappingURL=template_construct.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"template_construct.cjs","names":["randomUUID","TemplateEntryRejected","BLOCK_STORAGE_FACADE_VERSION"],"sources":["../../src/mutator/template_construct.ts"],"sourcesContent":["import { BLOCK_STORAGE_FACADE_VERSION, extractConfig } from \"@platforma-sdk/model\";\nimport type { ProjectTemplateV1 } from \"@milaboratories/pl-model-common\";\nimport type { BlockPackSpecPrepared } from \"../model\";\nimport type { ProjectHelper } from \"../model/project_helper\";\nimport type { AppliedEntry } from \"../model/template_apply\";\nimport { TemplateEntryRejected } from \"../model/template_apply\";\nimport { randomUUID } from \"node:crypto\";\nimport type { Block } from \"../model/project_model\";\nimport type { NewBlockSpec } from \"./project\";\n\n/** What construction needs to know about one entry, once its block has been located. */\nexport type PreparedTemplateEntry = {\n /** The block pack, already prepared and template-cached. */\n readonly blockPack: BlockPackSpecPrepared;\n /**\n * The label to place the block under: the block package's published title.\n *\n * A template names no block instances, so this cannot come from the file — and must\n * not be derived from it, since an exported template names its entries by the source\n * project's block ids. It reaches here from resolution, the only stage that talks to\n * a registry.\n *\n * A block whose model derives a title shows that instead (`project_overview.ts`\n * prefers the derived one), so for most blocks this is invisible. For the ones that\n * derive none it is the name the user reads.\n */\n readonly label: string;\n};\n\n/** The one method construction needs from a project mutator. */\nexport type BlockPlacer = {\n addBlock: (block: Block, spec: NewBlockSpec, before?: string) => void;\n};\n\n/**\n * Apply a `template-v1` document: create each entry's block, in file order.\n *\n * Everything slow is already done by the time this runs: kinds resolved, block packs\n * prepared and template-cached, the project created and a mutator open. What is left per\n * entry is in-memory work — assign an id, redirect the entry's references against the ids\n * already handed out, ask the block's own model for the storage those params imply, and\n * place the block. All of it inside the caller's single transaction.\n *\n * File order is instantiation order, and a reference to a later entry is rejected before\n * this point, so one forward pass is enough: by the time an entry is created, every entry it\n * references already exists and already has an id. Blocks are appended in the order they are\n * added, so nothing here passes a `before`.\n *\n * **All or nothing.** An entry this project cannot honour throws\n * {@link TemplateEntryRejected}, which leaves the caller's transaction uncommitted, so the\n * project keeps none of the blocks this call placed. A half-applied project is not a useful\n * result: its tail is missing, so whatever the missing entries were supposed to feed is wired\n * to nothing, and the user cannot tell which of the blocks present were configured by the\n * file and which they would have to fix. Failing whole also means the reader gets one\n * statement about their file rather than a project to reconcile.\n *\n * @param entries Prepared entries by template-local id. Resolution covers every entry\n * or the apply does not start, so a missing one is a caller error rather than a\n * property of the file\n * @param newBlockId Source of project-local block ids. Defaults to random UUIDs, the same\n * ids `Project.addBlock` would have generated on its own; injectable so a test can name\n * the ids it expects\n * @throws {TemplateEntryRejected} for the first entry the file describes unusably\n */\nexport function applyTemplateEntries(deps: {\n readonly document: ProjectTemplateV1;\n readonly placer: BlockPlacer;\n readonly projectHelper: ProjectHelper;\n readonly entries: ReadonlyMap<string, PreparedTemplateEntry>;\n readonly newBlockId?: () => string;\n}): AppliedEntry[] {\n const { document, placer, projectHelper, entries } = deps;\n const newBlockId = deps.newBlockId ?? randomUUID;\n const added: AppliedEntry[] = [];\n\n /**\n * Template-local entry id → the block id it was given.\n *\n * An entry lands in here the moment it is given an id, before its block is placed. That is\n * safe precisely because the apply is all-or-nothing: there is no surviving project in\n * which a later entry could point at a block that never got created. It does mean an entry\n * whose params reference itself is wired to itself rather than left dangling — a document\n * the reference check rejects before an apply begins.\n */\n const blockIds = new Map<string, string>();\n\n for (const entry of document.blocks) {\n const prepared = entries.get(entry.id);\n if (prepared === undefined) {\n throw new TemplateEntryRejected(entry.id, `No block was prepared for entry '${entry.id}'.`);\n }\n\n const blockConfig = extractConfig(prepared.blockPack.config);\n\n // Only a block on the current storage facade can be told what to initialize itself\n // with, and it is refused rather than created with its own defaults even when the\n // entry carries no params: every entry names a kind, and a block predating the\n // facade implements none, so creating one would honour the entry's pinned version\n // while contradicting the kind it claims to be. With params it is worse still — a\n // block that looks applied but ignored everything the template said about it.\n //\n // Kind resolution never picks such a block, since declaring a kind requires a\n // recent SDK. An entry pinning an exact version can name anything ever published,\n // which is the way this is reached.\n if (blockConfig.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {\n throw new TemplateEntryRejected(\n entry.id,\n \"This version of the block is too old to be created from a template. Use a \" +\n \"newer version of the block, or remove the pinned block version from this \" +\n \"entry so a supported one is chosen automatically.\",\n );\n }\n\n const blockId = newBlockId();\n blockIds.set(entry.id, blockId);\n\n // Params travelled from the file untouched, so pointing their references at this project\n // is the block's own job, in its own bundle — nothing here knows which of the values in\n // there carry block ids. It happens inside this same call, along with the kind's check and\n // the block's own init: the block decides what its params mean, and this is the one place\n // that asks it. The map holds the entries created so far, including this one, so an entry\n // referencing itself is wired to itself and one referencing an entry below is left\n // pointing at nothing.\n //\n // Params are a mapping by the time a document exists — the parser reads an omitted key\n // as `{}` — so every entry goes through this path and is checked against its kind. Run\n // before anything is placed, so params the block declines cost nothing but the report.\n const storage = projectHelper.getInitialStorageFromParamsInVM(\n blockConfig,\n entry.params,\n blockIds,\n );\n if (storage.error !== undefined) {\n throw new TemplateEntryRejected(entry.id, storage.error.message);\n }\n\n placer.addBlock(\n { id: blockId, label: prepared.label, renderingMode: blockConfig.renderingMode },\n {\n storageMode: \"fromModel\",\n blockPack: prepared.blockPack,\n initialStorage: storage.value,\n },\n );\n\n added.push({ templateLocalId: entry.id, blockId });\n }\n\n return added;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,SAAgB,qBAAqB,MAMlB;CACjB,MAAM,EAAE,UAAU,QAAQ,eAAe,YAAY;CACrD,MAAM,aAAa,KAAK,cAAcA,YAAAA;CACtC,MAAM,QAAwB,CAAC;;;;;;;;;;CAW/B,MAAM,2BAAW,IAAI,IAAoB;CAEzC,KAAK,MAAM,SAAS,SAAS,QAAQ;EACnC,MAAM,WAAW,QAAQ,IAAI,MAAM,EAAE;EACrC,IAAI,aAAa,KAAA,GACf,MAAM,IAAIC,uBAAAA,sBAAsB,MAAM,IAAI,oCAAoC,MAAM,GAAG,GAAG;EAG5F,MAAM,eAAA,GAAA,qBAAA,cAAA,CAA4B,SAAS,UAAU,MAAM;EAY3D,IAAI,YAAY,oBAAoBC,qBAAAA,8BAClC,MAAM,IAAID,uBAAAA,sBACR,MAAM,IACN,sMAGF;EAGF,MAAM,UAAU,WAAW;EAC3B,SAAS,IAAI,MAAM,IAAI,OAAO;EAa9B,MAAM,UAAU,cAAc,gCAC5B,aACA,MAAM,QACN,QACF;EACA,IAAI,QAAQ,UAAU,KAAA,GACpB,MAAM,IAAIA,uBAAAA,sBAAsB,MAAM,IAAI,QAAQ,MAAM,OAAO;EAGjE,OAAO,SACL;GAAE,IAAI;GAAS,OAAO,SAAS;GAAO,eAAe,YAAY;EAAc,GAC/E;GACE,aAAa;GACb,WAAW,SAAS;GACpB,gBAAgB,QAAQ;EAC1B,CACF;EAEA,MAAM,KAAK;GAAE,iBAAiB,MAAM;GAAI;EAAQ,CAAC;CACnD;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"template_construct.cjs","names":["randomUUID","TemplateEntryRejected","extractConfig","BLOCK_STORAGE_FACADE_VERSION"],"sources":["../../src/mutator/template_construct.ts"],"sourcesContent":["import { BLOCK_STORAGE_FACADE_VERSION, extractConfig } from \"@platforma-sdk/model\";\nimport type { ProjectTemplateV1 } from \"@milaboratories/pl-model-common\";\nimport type { BlockPackSpecPrepared } from \"../model\";\nimport type { ProjectHelper } from \"../model/project_helper\";\nimport type { AppliedEntry } from \"../model/template_apply\";\nimport { TemplateEntryRejected } from \"../model/template_apply\";\nimport { randomUUID } from \"node:crypto\";\nimport type { Block } from \"../model/project_model\";\nimport type { NewBlockSpec } from \"./project\";\n\n/** What construction needs to know about one entry, once its block has been located. */\nexport type PreparedTemplateEntry = {\n /** The block pack, already prepared and template-cached. */\n readonly blockPack: BlockPackSpecPrepared;\n /**\n * The label to place the block under: the block package's published title.\n *\n * A template names no block instances, so this cannot come from the file — and must\n * not be derived from it, since an exported template names its entries by the source\n * project's block ids. It reaches here from resolution, the only stage that talks to\n * a registry.\n *\n * A block whose model derives a title shows that instead (`project_overview.ts`\n * prefers the derived one), so for most blocks this is invisible. For the ones that\n * derive none it is the name the user reads.\n */\n readonly label: string;\n};\n\n/** The one method construction needs from a project mutator. */\nexport type BlockPlacer = {\n addBlock: (block: Block, spec: NewBlockSpec, before?: string) => void;\n};\n\n/**\n * Apply a `template-v1` document: create each entry's block, in file order.\n *\n * Everything slow is already done by the time this runs: kinds resolved, block packs\n * prepared and template-cached, the project created and a mutator open. What is left per\n * entry is in-memory work — assign an id, redirect the entry's references against the ids\n * already handed out, ask the block's own model for the storage those params imply, and\n * place the block. All of it inside the caller's single transaction.\n *\n * File order is instantiation order, and a reference to a later entry is rejected before\n * this point, so one forward pass is enough: by the time an entry is created, every entry it\n * references already exists and already has an id. Blocks are appended in the order they are\n * added, so nothing here passes a `before`.\n *\n * **All or nothing.** An entry this project cannot honour throws\n * {@link TemplateEntryRejected}, which leaves the caller's transaction uncommitted, so the\n * project keeps none of the blocks this call placed. A half-applied project is not a useful\n * result: its tail is missing, so whatever the missing entries were supposed to feed is wired\n * to nothing, and the user cannot tell which of the blocks present were configured by the\n * file and which they would have to fix. Failing whole also means the reader gets one\n * statement about their file rather than a project to reconcile.\n *\n * @param entries Prepared entries by template-local id. Resolution covers every entry\n * or the apply does not start, so a missing one is a caller error rather than a\n * property of the file\n * @param newBlockId Source of project-local block ids. Defaults to random UUIDs, the same\n * ids `Project.addBlock` would have generated on its own; injectable so a test can name\n * the ids it expects\n * @throws {TemplateEntryRejected} for the first entry the file describes unusably\n */\nexport function applyTemplateEntries(deps: {\n readonly document: ProjectTemplateV1;\n readonly placer: BlockPlacer;\n readonly projectHelper: ProjectHelper;\n readonly entries: ReadonlyMap<string, PreparedTemplateEntry>;\n readonly newBlockId?: () => string;\n}): AppliedEntry[] {\n const { document, placer, projectHelper, entries } = deps;\n const newBlockId = deps.newBlockId ?? randomUUID;\n const added: AppliedEntry[] = [];\n\n /**\n * Template-local entry id → the block id it was given.\n *\n * An entry lands in here the moment it is given an id, before its block is placed. That is\n * safe precisely because the apply is all-or-nothing: there is no surviving project in\n * which a later entry could point at a block that never got created. It does mean an entry\n * whose params reference itself is wired to itself rather than left dangling — a document\n * the reference check rejects before an apply begins.\n */\n const blockIds = new Map<string, string>();\n\n for (const entry of document.blocks) {\n const prepared = entries.get(entry.id);\n if (prepared === undefined) {\n throw new TemplateEntryRejected(entry.id, `No block was prepared for entry '${entry.id}'.`);\n }\n\n const blockConfig = extractConfig(prepared.blockPack.config);\n\n // Only a block on the current storage facade can be told what to initialize itself\n // with, and it is refused rather than created with its own defaults even when the\n // entry carries no params: every entry names a kind, and a block predating the\n // facade implements none, so creating one would honour the entry's pinned version\n // while contradicting the kind it claims to be. With params it is worse still — a\n // block that looks applied but ignored everything the template said about it.\n //\n // Kind resolution never picks such a block, since declaring a kind requires a\n // recent SDK. An entry pinning an exact version can name anything ever published,\n // which is the way this is reached.\n if (blockConfig.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {\n throw new TemplateEntryRejected(\n entry.id,\n \"This version of the block is too old to be created from a template. Use a \" +\n \"newer version of the block, or remove the pinned block version from this \" +\n \"entry so a supported one is chosen automatically.\",\n );\n }\n\n const blockId = newBlockId();\n blockIds.set(entry.id, blockId);\n\n // Params travelled from the file untouched, so pointing their references at this project\n // is the block's own job, in its own bundle — nothing here knows which of the values in\n // there carry block ids. It happens inside this same call, along with the kind's check and\n // the block's own init: the block decides what its params mean, and this is the one place\n // that asks it. The map holds the entries created so far, including this one, so an entry\n // referencing itself is wired to itself and one referencing an entry below is left\n // pointing at nothing.\n //\n // Params are a mapping by the time a document exists — the parser reads an omitted key\n // as `{}` — so every entry goes through this path and is checked against its kind. Run\n // before anything is placed, so params the block declines cost nothing but the report.\n const storage = projectHelper.getInitialStorageFromParamsInVM(\n blockConfig,\n entry.params,\n blockIds,\n );\n if (storage.error !== undefined) {\n throw new TemplateEntryRejected(entry.id, storage.error.message);\n }\n\n placer.addBlock(\n { id: blockId, label: prepared.label, renderingMode: blockConfig.renderingMode },\n {\n storageMode: \"fromModel\",\n blockPack: prepared.blockPack,\n initialStorage: storage.value,\n },\n );\n\n added.push({ templateLocalId: entry.id, blockId });\n }\n\n return added;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,SAAgB,qBAAqB,MAMlB;CACjB,MAAM,EAAE,UAAU,QAAQ,eAAe,YAAY;CACrD,MAAM,aAAa,KAAK,cAAcA,YAAAA;CACtC,MAAM,QAAwB,CAAC;;;;;;;;;;CAW/B,MAAM,2BAAW,IAAI,IAAoB;CAEzC,KAAK,MAAM,SAAS,SAAS,QAAQ;EACnC,MAAM,WAAW,QAAQ,IAAI,MAAM,EAAE;EACrC,IAAI,aAAa,KAAA,GACf,MAAM,IAAIC,uBAAAA,sBAAsB,MAAM,IAAI,oCAAoC,MAAM,GAAG,GAAG;EAG5F,MAAM,eAAA,GAAcC,qBAAAA,cAAAA,CAAc,SAAS,UAAU,MAAM;EAY3D,IAAI,YAAY,oBAAoBC,qBAAAA,8BAClC,MAAM,IAAIF,uBAAAA,sBACR,MAAM,IACN,sMAGF;EAGF,MAAM,UAAU,WAAW;EAC3B,SAAS,IAAI,MAAM,IAAI,OAAO;EAa9B,MAAM,UAAU,cAAc,gCAC5B,aACA,MAAM,QACN,QACF;EACA,IAAI,QAAQ,UAAU,KAAA,GACpB,MAAM,IAAIA,uBAAAA,sBAAsB,MAAM,IAAI,QAAQ,MAAM,OAAO;EAGjE,OAAO,SACL;GAAE,IAAI;GAAS,OAAO,SAAS;GAAO,eAAe,YAAY;EAAc,GAC/E;GACE,aAAa;GACb,WAAW,SAAS;GACpB,gBAAgB,QAAQ;EAC1B,CACF;EAEA,MAAM,KAAK;GAAE,iBAAiB,MAAM;GAAI;EAAQ,CAAC;CACnD;CAEA,OAAO;AACT"}
@@ -1,7 +1,7 @@
1
1
  import { TemplateEntryRejected } from "../model/template_apply.js";
2
2
  import { BLOCK_STORAGE_FACADE_VERSION, extractConfig } from "@platforma-sdk/model";
3
3
  import { randomUUID } from "node:crypto";
4
- //#region src/mutator/template_construct.ts
4
+ // src/mutator/template_construct.ts
5
5
  /**
6
6
  * Apply a `template-v1` document: create each entry's block, in file order.
7
7
  *
@@ -71,7 +71,6 @@ function applyTemplateEntries(deps) {
71
71
  }
72
72
  return added;
73
73
  }
74
- //#endregion
75
74
  export { applyTemplateEntries };
76
75
 
77
76
  //# sourceMappingURL=template_construct.js.map
@@ -5,7 +5,7 @@ let _milaboratories_pl_client = require("@milaboratories/pl-client");
5
5
  let node_crypto = require("node:crypto");
6
6
  let _milaboratories_pl_drivers = require("@milaboratories/pl-drivers");
7
7
  let node_diagnostics_channel = require("node:diagnostics_channel");
8
- //#region src/network_check/network_check.ts
8
+ // src/network_check/network_check.ts
9
9
  /** Checks connectivity to Platforma Backend, to block registry
10
10
  * and to auto-update CDN,
11
11
  * and generates a string report. */
@@ -77,7 +77,7 @@ async function initNetworkCheck(plCredentials, plUser, plPassword, optsOverrides
77
77
  bodyLimit: 300,
78
78
  everyStorageBytesLimit: 1024,
79
79
  everyStorageMinFileSize: 1024,
80
- everyStorageMaxFileSize: 200 * 1024 * 1024,
80
+ everyStorageMaxFileSize: 209715200,
81
81
  everyStorageNFilesToCheck: 300,
82
82
  everyStorageMinLsRequests: 50,
83
83
  ...optsOverrides
@@ -214,7 +214,6 @@ const undiciEvents = [
214
214
  "undici:dispatcher:disconnect",
215
215
  "undici:dispatcher:retry"
216
216
  ];
217
- //#endregion
218
217
  exports.checkNetwork = checkNetwork;
219
218
  exports.initNetworkCheck = initNetworkCheck;
220
219
 
@@ -1 +1 @@
1
- {"version":3,"file":"network_check.cjs","names":["createTempFile","createBigTempFile","backendPings","blockRegistryOverviewPings","blockGARegistryOverviewPings","blockRegistryUiPings","blockGARegistryUiPings","autoUpdateCdnPings","uploadTemplate","uploadFile","downloadFile","softwareCheck","pythonSoftware","downloadFromEveryStorage","UnauthenticatedPlClient","PlClient","ConsoleLoggerAdapter","HmacSha256Signer","LsDriver","reportToString"],"sources":["../../src/network_check/network_check.ts"],"sourcesContent":["/** A utility to check network problems and gather statistics.\n * It's useful when we cannot connect to the server of a company\n * because of security reasons,\n * but they can send us and their DevOps team this report.\n *\n * What we check:\n * - pings to backend\n * - block registry for block overview and ui.\n * - autoupdate CDN.\n * - upload workflow to backend (workflow part via our API).\n * - the desktop could do multipart upload.\n * - the desktop could download files from S3.\n * - backend could download software and run it.\n * - backend could run python software.\n * - try to get something from every storage to work storage.\n *\n * We don't check backend access to S3 storage, it is checked on the start of backend.\n */\n\nimport type { AuthInformation, PlClientConfig } from \"@milaboratories/pl-client\";\nimport { PlClient, UnauthenticatedPlClient, plAddressToConfig } from \"@milaboratories/pl-client\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { ConsoleLoggerAdapter, HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport { channel } from \"node:diagnostics_channel\";\nimport type { ClientDownload, ClientUpload } from \"@milaboratories/pl-drivers\";\nimport { LsDriver, createDownloadClient, createUploadBlobClient } from \"@milaboratories/pl-drivers\";\nimport type { HttpNetworkReport, NetworkReport } from \"./pings\";\nimport {\n autoUpdateCdnPings,\n backendPings,\n blockGARegistryOverviewPings,\n blockGARegistryUiPings,\n blockRegistryOverviewPings,\n blockRegistryUiPings,\n reportToString,\n} from \"./pings\";\nimport type { Dispatcher } from \"undici\";\nimport type { TemplateReport } from \"./template\";\nimport {\n uploadTemplate,\n uploadFile,\n downloadFile,\n createTempFile,\n pythonSoftware,\n softwareCheck,\n createBigTempFile,\n downloadFromEveryStorage,\n} from \"./template\";\nimport { randomUUID } from \"node:crypto\";\n\n/** All reports we need to collect. */\ninterface NetworkReports {\n plPings: NetworkReport<string>[];\n\n blockRegistryOverviewChecks: HttpNetworkReport[];\n blockGARegistryOverviewChecks: HttpNetworkReport[];\n blockRegistryUiChecks: HttpNetworkReport[];\n blockGARegistryUiChecks: HttpNetworkReport[];\n\n autoUpdateCdnChecks: HttpNetworkReport[];\n\n uploadTemplateCheck: TemplateReport;\n uploadFileCheck: TemplateReport;\n downloadFileCheck: TemplateReport;\n softwareCheck: TemplateReport;\n pythonSoftwareCheck: TemplateReport;\n storageToDownloadReport: Record<string, TemplateReport>;\n}\n\nexport interface CheckNetworkOpts {\n /** Signal to abort all network checks. */\n signal?: AbortSignal;\n\n /** Platforma Backend pings options. */\n pingCheckDurationMs: number;\n pingTimeoutMs: number;\n maxPingsPerSecond: number;\n\n /** An options for CDN and block registry. */\n httpTimeoutMs: number;\n\n /** Block registry pings options. */\n blockRegistryDurationMs: number;\n maxRegistryChecksPerSecond: number;\n blockRegistryUrl: string;\n blockGARegistryUrl: string;\n blockOverviewPath: string;\n blockUiPath: string;\n\n /** CDN for auto-update pings options. */\n autoUpdateCdnDurationMs: number;\n maxAutoUpdateCdnChecksPerSecond: number;\n autoUpdateCdnUrl: string;\n\n /** Body limit for requests. */\n bodyLimit: number;\n\n /** Limit for the size of files to download from every storage. */\n everyStorageBytesLimit: number;\n /** Minimal size of files to create a directory from for every storage. */\n everyStorageMinFileSize: number;\n /** Maximal size of files to create a directory from for every storage. */\n everyStorageMaxFileSize: number;\n /** How many files to check from every storage. */\n everyStorageNFilesToCheck: number;\n /** Minimal number of ls requests for every storage. */\n everyStorageMinLsRequests: number;\n}\n\n/** Checks connectivity to Platforma Backend, to block registry\n * and to auto-update CDN,\n * and generates a string report. */\nexport async function checkNetwork(\n plCredentials: string,\n plUser: string | undefined,\n plPassword: string | undefined,\n optsOverrides: Partial<CheckNetworkOpts> = {},\n): Promise<string> {\n const undiciLogs: any[] = [];\n // Subscribe to all Undici diagnostic events\n undiciEvents.forEach((event) => {\n const diagnosticChannel = channel(event);\n diagnosticChannel.subscribe((message: any) => {\n const timestamp = new Date().toISOString();\n const data = { ...message };\n if (data?.response?.headers) {\n data.response = { ...data.response };\n data.response.headers = data.response.headers.slice();\n data.response.headers = data.response.headers.map((h: any) => h.toString());\n }\n\n // we try to upload big files, don't include the buffer in the report.\n if (data?.request?.body) {\n data.request = { ...data.request };\n data.request.body = `too big`;\n }\n\n undiciLogs.push(\n JSON.stringify({\n timestamp,\n event,\n data,\n }),\n );\n });\n });\n\n try {\n const {\n logger,\n plConfig,\n client,\n signer,\n downloadClient,\n uploadBlobClient,\n lsDriver,\n httpClient,\n ops,\n } = await initNetworkCheck(plCredentials, plUser, plPassword, optsOverrides);\n\n const { filePath: filePathToDownload, fileContent: fileContentToDownload } =\n await createTempFile();\n const { filePath: filePathToUpload } = await createBigTempFile();\n\n const report: NetworkReports = {\n plPings: await backendPings(ops, plConfig),\n blockRegistryOverviewChecks: await blockRegistryOverviewPings(ops, httpClient),\n blockGARegistryOverviewChecks: await blockGARegistryOverviewPings(ops, httpClient),\n blockRegistryUiChecks: await blockRegistryUiPings(ops, httpClient),\n blockGARegistryUiChecks: await blockGARegistryUiPings(ops, httpClient),\n\n autoUpdateCdnChecks: await autoUpdateCdnPings(ops, httpClient),\n\n uploadTemplateCheck: await uploadTemplate(logger, client, \"Jack\"),\n uploadFileCheck: await uploadFile(\n logger,\n signer,\n lsDriver,\n uploadBlobClient,\n client,\n filePathToUpload,\n ),\n downloadFileCheck: await downloadFile(\n logger,\n client,\n lsDriver,\n uploadBlobClient,\n downloadClient,\n filePathToDownload,\n fileContentToDownload,\n ),\n softwareCheck: await softwareCheck(client),\n pythonSoftwareCheck: await pythonSoftware(client, \"Jack\"),\n storageToDownloadReport: await downloadFromEveryStorage(logger, client, lsDriver, {\n minLsRequests: ops.everyStorageMinLsRequests,\n bytesLimit: ops.everyStorageBytesLimit,\n minFileSize: ops.everyStorageMinFileSize,\n maxFileSize: ops.everyStorageMaxFileSize,\n nFilesToCheck: ops.everyStorageNFilesToCheck,\n }),\n };\n\n return reportsToString(report, plCredentials, ops, undiciLogs);\n } catch (e) {\n return `Unhandled error while checking the network: ${e}`;\n }\n}\n\nexport async function initNetworkCheck(\n plCredentials: string,\n plUser: string | undefined,\n plPassword: string | undefined,\n optsOverrides: Partial<CheckNetworkOpts> = {},\n): Promise<{\n logger: MiLogger;\n plConfig: PlClientConfig;\n signer: Signer;\n client: PlClient;\n downloadClient: ClientDownload;\n uploadBlobClient: ClientUpload;\n lsDriver: LsDriver;\n httpClient: Dispatcher;\n ops: CheckNetworkOpts;\n terminate: () => Promise<void>;\n}> {\n const ops: CheckNetworkOpts = {\n pingCheckDurationMs: 10000,\n pingTimeoutMs: 3000,\n maxPingsPerSecond: 50,\n\n httpTimeoutMs: 3000,\n\n blockRegistryDurationMs: 3000,\n maxRegistryChecksPerSecond: 1,\n\n blockRegistryUrl: \"https://blocks.pl-open.science\",\n blockGARegistryUrl: \"https://blocks-ga.pl-open.science\",\n blockOverviewPath: \"v2/overview.json\",\n blockUiPath: \"v2/milaboratories/samples-and-data/1.7.0/ui.tgz\",\n\n autoUpdateCdnDurationMs: 5000,\n maxAutoUpdateCdnChecksPerSecond: 1,\n autoUpdateCdnUrl:\n \"https://cdn.platforma.bio/software/platforma-desktop-v2/windows/amd64/latest.yml\",\n\n bodyLimit: 300,\n\n everyStorageBytesLimit: 1024,\n everyStorageMinFileSize: 1024,\n everyStorageMaxFileSize: 200 * 1024 * 1024, // 200 MB\n everyStorageNFilesToCheck: 300,\n everyStorageMinLsRequests: 50,\n ...optsOverrides,\n };\n\n const plConfig = plAddressToConfig(plCredentials, {\n defaultRequestTimeout: ops.pingTimeoutMs,\n });\n\n // exposing alternative root for fields not to interfere with\n // projects of the user.\n plConfig.alternativeRoot = `check_network_${randomUUID()}`;\n\n const uaClient = await UnauthenticatedPlClient.build(plConfig);\n\n let auth: AuthInformation = {};\n if (plUser && plPassword) {\n auth = await uaClient.login(plUser, plPassword);\n }\n\n const client = await PlClient.init(plCredentials, { authInformation: auth });\n\n const httpClient = uaClient.ll.httpDispatcher;\n const logger = new ConsoleLoggerAdapter();\n\n // FIXME: do we need to get an actual secret?\n const signer = new HmacSha256Signer(\"localSecret\");\n\n // We could initialize middle-layer here, but for now it seems like an overkill.\n // Here's the code to do it:\n //\n // const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'platforma-network-check-'));\n // const ml = await MiddleLayer.init(client, tmpDir, {\n // logger,\n // localSecret: '',\n // localProjections: [],\n // openFileDialogCallback: () => Promise.resolve([]),\n // preferredUpdateChannel: 'stable',\n // });\n\n const downloadClient = createDownloadClient(logger, client, []);\n const uploadBlobClient = createUploadBlobClient(client, logger);\n\n const lsDriver = await LsDriver.init(logger, client, signer, [], () => Promise.resolve([]), []);\n\n const terminate = async () => {\n downloadClient.close();\n uploadBlobClient.close();\n await httpClient.close();\n await client.close();\n };\n\n return {\n logger,\n plConfig,\n client,\n signer,\n downloadClient,\n uploadBlobClient,\n lsDriver,\n httpClient,\n ops,\n terminate,\n };\n}\n\nfunction reportsToString(\n report: NetworkReports,\n plEndpoint: string,\n opts: CheckNetworkOpts,\n undiciLogs: any[],\n): string {\n const successPings = report.plPings.filter((p) => p.response.ok);\n const failedPings = report.plPings.filter((p) => !p.response.ok);\n const successPingsBodies = [\n ...new Set(successPings.map((p) => JSON.stringify((p.response as any).value))),\n ];\n\n const summary = (ok: boolean) => (ok ? \"OK\" : \"FAILED\");\n const templateSummary = (report: TemplateReport) =>\n report.status === \"ok\" ? \"OK\" : report.status === \"warn\" ? \"WARN\" : \"FAILED\";\n\n const pings = reportToString(report.plPings);\n const blockRegistryOverview = reportToString(report.blockRegistryOverviewChecks);\n const blockGARegistryOverview = reportToString(report.blockGARegistryOverviewChecks);\n const blockRegistryUi = reportToString(report.blockRegistryUiChecks);\n const blockGARegistryUi = reportToString(report.blockGARegistryUiChecks);\n const autoUpdateCdn = reportToString(report.autoUpdateCdnChecks);\n\n const storagesSummary = Object.entries(report.storageToDownloadReport)\n .map(([storage, report]) => `${templateSummary(report)} ${storage} storage check`)\n .join(\"\\n\");\n\n return `\n${summary(pings.ok)} pings to Platforma Backend\n${summary(blockRegistryOverview.ok)} block registry overview\n${summary(blockGARegistryOverview.ok)} block ga registry overview\n${summary(blockRegistryUi.ok)} block registry ui\n${summary(blockGARegistryUi.ok)} block ga registry ui\n${summary(autoUpdateCdn.ok)} auto-update CDN\n${templateSummary(report.uploadTemplateCheck)} upload template\n${templateSummary(report.uploadFileCheck)} upload file\n${templateSummary(report.downloadFileCheck)} download file\n${templateSummary(report.softwareCheck)} software check\n${templateSummary(report.pythonSoftwareCheck)} python software check\n${storagesSummary}\n\ndetails:\npl endpoint: ${plEndpoint};\noptions: ${JSON.stringify(opts, null, 2)}.\n\nUpload template response: ${report.uploadTemplateCheck.message}\n\nUpload file response: ${report.uploadFileCheck.message}\n\nDownload file response: ${report.downloadFileCheck.message}\n\nSoftware check response: ${report.softwareCheck.message}\nPython software check response: ${report.pythonSoftwareCheck.message}\nStorage to download responses: ${JSON.stringify(report.storageToDownloadReport, null, 2)}\n\nPlatforma pings: ${pings.details}\n\nBlock registry overview responses: ${blockRegistryOverview.details}\n\nBlock ga registry overview responses: ${blockGARegistryOverview.details}\n\nBlock registry ui responses: ${blockRegistryUi.details}\n\nBlock ga registry ui responses: ${blockGARegistryUi.details}\n\nAuto-update CDN responses: ${autoUpdateCdn.details}\n\ndumps:\nBlock registry overview dumps:\n${JSON.stringify(report.blockRegistryOverviewChecks, null, 2)}\n\nBlock ga registry overview dumps:\n${JSON.stringify(report.blockGARegistryOverviewChecks, null, 2)}\n\nBlock registry ui dumps:\n${JSON.stringify(report.blockRegistryUiChecks, null, 2)}\n\nBlock ga registry ui dumps:\n${JSON.stringify(report.blockGARegistryUiChecks, null, 2)}\n\nAuto-update CDN dumps:\n${JSON.stringify(report.autoUpdateCdnChecks, null, 2)}\n\nPlatforma pings error dumps:\n${JSON.stringify(failedPings, null, 2)}\n\nPlatforma pings success dump examples:\n${JSON.stringify(successPingsBodies, null, 2)}\n\nUndici logs:\n${undiciLogs.join(\"\\n\")}\n`;\n}\n\n// List of Undici diagnostic channels\nconst undiciEvents: string[] = [\n \"undici:request:create\", // When a new request is created\n \"undici:request:bodySent\", // When the request body is sent\n \"undici:request:headers\", // When request headers are sent\n \"undici:request:error\", // When a request encounters an error\n \"undici:request:trailers\", // When a response completes.\n\n \"undici:client:sendHeaders\",\n \"undici:client:beforeConnect\",\n \"undici:client:connected\",\n \"undici:client:connectError\",\n\n \"undici:socket:close\", // When a socket is closed\n \"undici:socket:connect\", // When a socket connects\n \"undici:socket:error\", // When a socket encounters an error\n\n \"undici:pool:request\", // When a request is added to the pool\n \"undici:pool:connect\", // When a pool creates a new connection\n \"undici:pool:disconnect\", // When a pool connection is closed\n \"undici:pool:destroy\", // When a pool is destroyed\n \"undici:dispatcher:request\", // When a dispatcher processes a request\n \"undici:dispatcher:connect\", // When a dispatcher connects\n \"undici:dispatcher:disconnect\", // When a dispatcher disconnects\n \"undici:dispatcher:retry\", // When a dispatcher retries a request\n];\n"],"mappings":"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"}
1
+ {"version":3,"file":"network_check.cjs","names":["channel","createTempFile","createBigTempFile","backendPings","blockRegistryOverviewPings","blockGARegistryOverviewPings","blockRegistryUiPings","blockGARegistryUiPings","autoUpdateCdnPings","uploadTemplate","uploadFile","downloadFile","softwareCheck","pythonSoftware","downloadFromEveryStorage","plAddressToConfig","randomUUID","UnauthenticatedPlClient","PlClient","ConsoleLoggerAdapter","HmacSha256Signer","createDownloadClient","createUploadBlobClient","LsDriver","reportToString"],"sources":["../../src/network_check/network_check.ts"],"sourcesContent":["/** A utility to check network problems and gather statistics.\n * It's useful when we cannot connect to the server of a company\n * because of security reasons,\n * but they can send us and their DevOps team this report.\n *\n * What we check:\n * - pings to backend\n * - block registry for block overview and ui.\n * - autoupdate CDN.\n * - upload workflow to backend (workflow part via our API).\n * - the desktop could do multipart upload.\n * - the desktop could download files from S3.\n * - backend could download software and run it.\n * - backend could run python software.\n * - try to get something from every storage to work storage.\n *\n * We don't check backend access to S3 storage, it is checked on the start of backend.\n */\n\nimport type { AuthInformation, PlClientConfig } from \"@milaboratories/pl-client\";\nimport { PlClient, UnauthenticatedPlClient, plAddressToConfig } from \"@milaboratories/pl-client\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { ConsoleLoggerAdapter, HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport { channel } from \"node:diagnostics_channel\";\nimport type { ClientDownload, ClientUpload } from \"@milaboratories/pl-drivers\";\nimport { LsDriver, createDownloadClient, createUploadBlobClient } from \"@milaboratories/pl-drivers\";\nimport type { HttpNetworkReport, NetworkReport } from \"./pings\";\nimport {\n autoUpdateCdnPings,\n backendPings,\n blockGARegistryOverviewPings,\n blockGARegistryUiPings,\n blockRegistryOverviewPings,\n blockRegistryUiPings,\n reportToString,\n} from \"./pings\";\nimport type { Dispatcher } from \"undici\";\nimport type { TemplateReport } from \"./template\";\nimport {\n uploadTemplate,\n uploadFile,\n downloadFile,\n createTempFile,\n pythonSoftware,\n softwareCheck,\n createBigTempFile,\n downloadFromEveryStorage,\n} from \"./template\";\nimport { randomUUID } from \"node:crypto\";\n\n/** All reports we need to collect. */\ninterface NetworkReports {\n plPings: NetworkReport<string>[];\n\n blockRegistryOverviewChecks: HttpNetworkReport[];\n blockGARegistryOverviewChecks: HttpNetworkReport[];\n blockRegistryUiChecks: HttpNetworkReport[];\n blockGARegistryUiChecks: HttpNetworkReport[];\n\n autoUpdateCdnChecks: HttpNetworkReport[];\n\n uploadTemplateCheck: TemplateReport;\n uploadFileCheck: TemplateReport;\n downloadFileCheck: TemplateReport;\n softwareCheck: TemplateReport;\n pythonSoftwareCheck: TemplateReport;\n storageToDownloadReport: Record<string, TemplateReport>;\n}\n\nexport interface CheckNetworkOpts {\n /** Signal to abort all network checks. */\n signal?: AbortSignal;\n\n /** Platforma Backend pings options. */\n pingCheckDurationMs: number;\n pingTimeoutMs: number;\n maxPingsPerSecond: number;\n\n /** An options for CDN and block registry. */\n httpTimeoutMs: number;\n\n /** Block registry pings options. */\n blockRegistryDurationMs: number;\n maxRegistryChecksPerSecond: number;\n blockRegistryUrl: string;\n blockGARegistryUrl: string;\n blockOverviewPath: string;\n blockUiPath: string;\n\n /** CDN for auto-update pings options. */\n autoUpdateCdnDurationMs: number;\n maxAutoUpdateCdnChecksPerSecond: number;\n autoUpdateCdnUrl: string;\n\n /** Body limit for requests. */\n bodyLimit: number;\n\n /** Limit for the size of files to download from every storage. */\n everyStorageBytesLimit: number;\n /** Minimal size of files to create a directory from for every storage. */\n everyStorageMinFileSize: number;\n /** Maximal size of files to create a directory from for every storage. */\n everyStorageMaxFileSize: number;\n /** How many files to check from every storage. */\n everyStorageNFilesToCheck: number;\n /** Minimal number of ls requests for every storage. */\n everyStorageMinLsRequests: number;\n}\n\n/** Checks connectivity to Platforma Backend, to block registry\n * and to auto-update CDN,\n * and generates a string report. */\nexport async function checkNetwork(\n plCredentials: string,\n plUser: string | undefined,\n plPassword: string | undefined,\n optsOverrides: Partial<CheckNetworkOpts> = {},\n): Promise<string> {\n const undiciLogs: any[] = [];\n // Subscribe to all Undici diagnostic events\n undiciEvents.forEach((event) => {\n const diagnosticChannel = channel(event);\n diagnosticChannel.subscribe((message: any) => {\n const timestamp = new Date().toISOString();\n const data = { ...message };\n if (data?.response?.headers) {\n data.response = { ...data.response };\n data.response.headers = data.response.headers.slice();\n data.response.headers = data.response.headers.map((h: any) => h.toString());\n }\n\n // we try to upload big files, don't include the buffer in the report.\n if (data?.request?.body) {\n data.request = { ...data.request };\n data.request.body = `too big`;\n }\n\n undiciLogs.push(\n JSON.stringify({\n timestamp,\n event,\n data,\n }),\n );\n });\n });\n\n try {\n const {\n logger,\n plConfig,\n client,\n signer,\n downloadClient,\n uploadBlobClient,\n lsDriver,\n httpClient,\n ops,\n } = await initNetworkCheck(plCredentials, plUser, plPassword, optsOverrides);\n\n const { filePath: filePathToDownload, fileContent: fileContentToDownload } =\n await createTempFile();\n const { filePath: filePathToUpload } = await createBigTempFile();\n\n const report: NetworkReports = {\n plPings: await backendPings(ops, plConfig),\n blockRegistryOverviewChecks: await blockRegistryOverviewPings(ops, httpClient),\n blockGARegistryOverviewChecks: await blockGARegistryOverviewPings(ops, httpClient),\n blockRegistryUiChecks: await blockRegistryUiPings(ops, httpClient),\n blockGARegistryUiChecks: await blockGARegistryUiPings(ops, httpClient),\n\n autoUpdateCdnChecks: await autoUpdateCdnPings(ops, httpClient),\n\n uploadTemplateCheck: await uploadTemplate(logger, client, \"Jack\"),\n uploadFileCheck: await uploadFile(\n logger,\n signer,\n lsDriver,\n uploadBlobClient,\n client,\n filePathToUpload,\n ),\n downloadFileCheck: await downloadFile(\n logger,\n client,\n lsDriver,\n uploadBlobClient,\n downloadClient,\n filePathToDownload,\n fileContentToDownload,\n ),\n softwareCheck: await softwareCheck(client),\n pythonSoftwareCheck: await pythonSoftware(client, \"Jack\"),\n storageToDownloadReport: await downloadFromEveryStorage(logger, client, lsDriver, {\n minLsRequests: ops.everyStorageMinLsRequests,\n bytesLimit: ops.everyStorageBytesLimit,\n minFileSize: ops.everyStorageMinFileSize,\n maxFileSize: ops.everyStorageMaxFileSize,\n nFilesToCheck: ops.everyStorageNFilesToCheck,\n }),\n };\n\n return reportsToString(report, plCredentials, ops, undiciLogs);\n } catch (e) {\n return `Unhandled error while checking the network: ${e}`;\n }\n}\n\nexport async function initNetworkCheck(\n plCredentials: string,\n plUser: string | undefined,\n plPassword: string | undefined,\n optsOverrides: Partial<CheckNetworkOpts> = {},\n): Promise<{\n logger: MiLogger;\n plConfig: PlClientConfig;\n signer: Signer;\n client: PlClient;\n downloadClient: ClientDownload;\n uploadBlobClient: ClientUpload;\n lsDriver: LsDriver;\n httpClient: Dispatcher;\n ops: CheckNetworkOpts;\n terminate: () => Promise<void>;\n}> {\n const ops: CheckNetworkOpts = {\n pingCheckDurationMs: 10000,\n pingTimeoutMs: 3000,\n maxPingsPerSecond: 50,\n\n httpTimeoutMs: 3000,\n\n blockRegistryDurationMs: 3000,\n maxRegistryChecksPerSecond: 1,\n\n blockRegistryUrl: \"https://blocks.pl-open.science\",\n blockGARegistryUrl: \"https://blocks-ga.pl-open.science\",\n blockOverviewPath: \"v2/overview.json\",\n blockUiPath: \"v2/milaboratories/samples-and-data/1.7.0/ui.tgz\",\n\n autoUpdateCdnDurationMs: 5000,\n maxAutoUpdateCdnChecksPerSecond: 1,\n autoUpdateCdnUrl:\n \"https://cdn.platforma.bio/software/platforma-desktop-v2/windows/amd64/latest.yml\",\n\n bodyLimit: 300,\n\n everyStorageBytesLimit: 1024,\n everyStorageMinFileSize: 1024,\n everyStorageMaxFileSize: 200 * 1024 * 1024, // 200 MB\n everyStorageNFilesToCheck: 300,\n everyStorageMinLsRequests: 50,\n ...optsOverrides,\n };\n\n const plConfig = plAddressToConfig(plCredentials, {\n defaultRequestTimeout: ops.pingTimeoutMs,\n });\n\n // exposing alternative root for fields not to interfere with\n // projects of the user.\n plConfig.alternativeRoot = `check_network_${randomUUID()}`;\n\n const uaClient = await UnauthenticatedPlClient.build(plConfig);\n\n let auth: AuthInformation = {};\n if (plUser && plPassword) {\n auth = await uaClient.login(plUser, plPassword);\n }\n\n const client = await PlClient.init(plCredentials, { authInformation: auth });\n\n const httpClient = uaClient.ll.httpDispatcher;\n const logger = new ConsoleLoggerAdapter();\n\n // FIXME: do we need to get an actual secret?\n const signer = new HmacSha256Signer(\"localSecret\");\n\n // We could initialize middle-layer here, but for now it seems like an overkill.\n // Here's the code to do it:\n //\n // const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'platforma-network-check-'));\n // const ml = await MiddleLayer.init(client, tmpDir, {\n // logger,\n // localSecret: '',\n // localProjections: [],\n // openFileDialogCallback: () => Promise.resolve([]),\n // preferredUpdateChannel: 'stable',\n // });\n\n const downloadClient = createDownloadClient(logger, client, []);\n const uploadBlobClient = createUploadBlobClient(client, logger);\n\n const lsDriver = await LsDriver.init(logger, client, signer, [], () => Promise.resolve([]), []);\n\n const terminate = async () => {\n downloadClient.close();\n uploadBlobClient.close();\n await httpClient.close();\n await client.close();\n };\n\n return {\n logger,\n plConfig,\n client,\n signer,\n downloadClient,\n uploadBlobClient,\n lsDriver,\n httpClient,\n ops,\n terminate,\n };\n}\n\nfunction reportsToString(\n report: NetworkReports,\n plEndpoint: string,\n opts: CheckNetworkOpts,\n undiciLogs: any[],\n): string {\n const successPings = report.plPings.filter((p) => p.response.ok);\n const failedPings = report.plPings.filter((p) => !p.response.ok);\n const successPingsBodies = [\n ...new Set(successPings.map((p) => JSON.stringify((p.response as any).value))),\n ];\n\n const summary = (ok: boolean) => (ok ? \"OK\" : \"FAILED\");\n const templateSummary = (report: TemplateReport) =>\n report.status === \"ok\" ? \"OK\" : report.status === \"warn\" ? \"WARN\" : \"FAILED\";\n\n const pings = reportToString(report.plPings);\n const blockRegistryOverview = reportToString(report.blockRegistryOverviewChecks);\n const blockGARegistryOverview = reportToString(report.blockGARegistryOverviewChecks);\n const blockRegistryUi = reportToString(report.blockRegistryUiChecks);\n const blockGARegistryUi = reportToString(report.blockGARegistryUiChecks);\n const autoUpdateCdn = reportToString(report.autoUpdateCdnChecks);\n\n const storagesSummary = Object.entries(report.storageToDownloadReport)\n .map(([storage, report]) => `${templateSummary(report)} ${storage} storage check`)\n .join(\"\\n\");\n\n return `\n${summary(pings.ok)} pings to Platforma Backend\n${summary(blockRegistryOverview.ok)} block registry overview\n${summary(blockGARegistryOverview.ok)} block ga registry overview\n${summary(blockRegistryUi.ok)} block registry ui\n${summary(blockGARegistryUi.ok)} block ga registry ui\n${summary(autoUpdateCdn.ok)} auto-update CDN\n${templateSummary(report.uploadTemplateCheck)} upload template\n${templateSummary(report.uploadFileCheck)} upload file\n${templateSummary(report.downloadFileCheck)} download file\n${templateSummary(report.softwareCheck)} software check\n${templateSummary(report.pythonSoftwareCheck)} python software check\n${storagesSummary}\n\ndetails:\npl endpoint: ${plEndpoint};\noptions: ${JSON.stringify(opts, null, 2)}.\n\nUpload template response: ${report.uploadTemplateCheck.message}\n\nUpload file response: ${report.uploadFileCheck.message}\n\nDownload file response: ${report.downloadFileCheck.message}\n\nSoftware check response: ${report.softwareCheck.message}\nPython software check response: ${report.pythonSoftwareCheck.message}\nStorage to download responses: ${JSON.stringify(report.storageToDownloadReport, null, 2)}\n\nPlatforma pings: ${pings.details}\n\nBlock registry overview responses: ${blockRegistryOverview.details}\n\nBlock ga registry overview responses: ${blockGARegistryOverview.details}\n\nBlock registry ui responses: ${blockRegistryUi.details}\n\nBlock ga registry ui responses: ${blockGARegistryUi.details}\n\nAuto-update CDN responses: ${autoUpdateCdn.details}\n\ndumps:\nBlock registry overview dumps:\n${JSON.stringify(report.blockRegistryOverviewChecks, null, 2)}\n\nBlock ga registry overview dumps:\n${JSON.stringify(report.blockGARegistryOverviewChecks, null, 2)}\n\nBlock registry ui dumps:\n${JSON.stringify(report.blockRegistryUiChecks, null, 2)}\n\nBlock ga registry ui dumps:\n${JSON.stringify(report.blockGARegistryUiChecks, null, 2)}\n\nAuto-update CDN dumps:\n${JSON.stringify(report.autoUpdateCdnChecks, null, 2)}\n\nPlatforma pings error dumps:\n${JSON.stringify(failedPings, null, 2)}\n\nPlatforma pings success dump examples:\n${JSON.stringify(successPingsBodies, null, 2)}\n\nUndici logs:\n${undiciLogs.join(\"\\n\")}\n`;\n}\n\n// List of Undici diagnostic channels\nconst undiciEvents: string[] = [\n \"undici:request:create\", // When a new request is created\n \"undici:request:bodySent\", // When the request body is sent\n \"undici:request:headers\", // When request headers are sent\n \"undici:request:error\", // When a request encounters an error\n \"undici:request:trailers\", // When a response completes.\n\n \"undici:client:sendHeaders\",\n \"undici:client:beforeConnect\",\n \"undici:client:connected\",\n \"undici:client:connectError\",\n\n \"undici:socket:close\", // When a socket is closed\n \"undici:socket:connect\", // When a socket connects\n \"undici:socket:error\", // When a socket encounters an error\n\n \"undici:pool:request\", // When a request is added to the pool\n \"undici:pool:connect\", // When a pool creates a new connection\n \"undici:pool:disconnect\", // When a pool connection is closed\n \"undici:pool:destroy\", // When a pool is destroyed\n \"undici:dispatcher:request\", // When a dispatcher processes a request\n \"undici:dispatcher:connect\", // When a dispatcher connects\n \"undici:dispatcher:disconnect\", // When a dispatcher disconnects\n \"undici:dispatcher:retry\", // When a dispatcher retries a request\n];\n"],"mappings":"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"}
@@ -2,7 +2,6 @@ import { Dispatcher } from "undici";
2
2
  import { MiLogger, Signer } from "@milaboratories/ts-helpers";
3
3
  import { PlClient, PlClientConfig } from "@milaboratories/pl-client";
4
4
  import { ClientDownload, ClientUpload, LsDriver } from "@milaboratories/pl-drivers";
5
- //#region src/network_check/network_check.d.ts
6
5
  export interface CheckNetworkOpts {
7
6
  /** Signal to abort all network checks. */
8
7
  signal?: AbortSignal;
@@ -52,5 +51,4 @@ export declare function initNetworkCheck(plCredentials: string, plUser: string |
52
51
  ops: CheckNetworkOpts;
53
52
  terminate: () => Promise<void>;
54
53
  }>;
55
- //#endregion
56
54
  //# sourceMappingURL=network_check.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"network_check.d.ts","names":[],"sources":["../../src/network_check/network_check.ts"],"mappings":";;;;;iBAqEiB;;EAEf,SAAS;;EAGT;EACA;EACA;;EAGA;;EAGA;EACA;EACA;EACA;EACA;EACA;;EAGA;EACA;EACA;;EAGA;;EAGA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;wBAMoB,aACpB,uBACA,4BACA,gCACA,gBAAe,QAAQ,oBACtB;wBA2FmB,iBACpB,uBACA,4BACA,gCACA,gBAAe,QAAQ,oBACtB;EACD,QAAQ;EACR,UAAU;EACV,QAAQ;EACR,QAAQ;EACR,gBAAgB;EAChB,kBAAkB;EAClB,UAAU;EACV,YAAY;EACZ,KAAK;EACL,iBAAiB"}
1
+ {"version":3,"file":"network_check.d.ts","names":[],"sources":["../../src/network_check/network_check.ts"],"mappings":";;;;iBAqEiB;;EAEf,SAAS;;EAGT;EACA;EACA;;EAGA;;EAGA;EACA;EACA;EACA;EACA;EACA;;EAGA;EACA;EACA;;EAGA;;EAGA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;wBAMoB,aACpB,uBACA,4BACA,gCACA,gBAAe,QAAQ,oBACtB;wBA2FmB,iBACpB,uBACA,4BACA,gCACA,gBAAe,QAAQ,oBACtB;EACD,QAAQ;EACR,UAAU;EACV,QAAQ;EACR,QAAQ;EACR,gBAAgB;EAChB,kBAAkB;EAClB,UAAU;EACV,YAAY;EACZ,KAAK;EACL,iBAAiB"}
@@ -5,7 +5,7 @@ import { PlClient, UnauthenticatedPlClient, plAddressToConfig } from "@milaborat
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { LsDriver, createDownloadClient, createUploadBlobClient } from "@milaboratories/pl-drivers";
7
7
  import { channel } from "node:diagnostics_channel";
8
- //#region src/network_check/network_check.ts
8
+ // src/network_check/network_check.ts
9
9
  /** Checks connectivity to Platforma Backend, to block registry
10
10
  * and to auto-update CDN,
11
11
  * and generates a string report. */
@@ -77,7 +77,7 @@ async function initNetworkCheck(plCredentials, plUser, plPassword, optsOverrides
77
77
  bodyLimit: 300,
78
78
  everyStorageBytesLimit: 1024,
79
79
  everyStorageMinFileSize: 1024,
80
- everyStorageMaxFileSize: 200 * 1024 * 1024,
80
+ everyStorageMaxFileSize: 209715200,
81
81
  everyStorageNFilesToCheck: 300,
82
82
  everyStorageMinLsRequests: 50,
83
83
  ...optsOverrides
@@ -214,7 +214,6 @@ const undiciEvents = [
214
214
  "undici:dispatcher:disconnect",
215
215
  "undici:dispatcher:retry"
216
216
  ];
217
- //#endregion
218
217
  export { checkNetwork, initNetworkCheck };
219
218
 
220
219
  //# sourceMappingURL=network_check.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"network_check.js","names":[],"sources":["../../src/network_check/network_check.ts"],"sourcesContent":["/** A utility to check network problems and gather statistics.\n * It's useful when we cannot connect to the server of a company\n * because of security reasons,\n * but they can send us and their DevOps team this report.\n *\n * What we check:\n * - pings to backend\n * - block registry for block overview and ui.\n * - autoupdate CDN.\n * - upload workflow to backend (workflow part via our API).\n * - the desktop could do multipart upload.\n * - the desktop could download files from S3.\n * - backend could download software and run it.\n * - backend could run python software.\n * - try to get something from every storage to work storage.\n *\n * We don't check backend access to S3 storage, it is checked on the start of backend.\n */\n\nimport type { AuthInformation, PlClientConfig } from \"@milaboratories/pl-client\";\nimport { PlClient, UnauthenticatedPlClient, plAddressToConfig } from \"@milaboratories/pl-client\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { ConsoleLoggerAdapter, HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport { channel } from \"node:diagnostics_channel\";\nimport type { ClientDownload, ClientUpload } from \"@milaboratories/pl-drivers\";\nimport { LsDriver, createDownloadClient, createUploadBlobClient } from \"@milaboratories/pl-drivers\";\nimport type { HttpNetworkReport, NetworkReport } from \"./pings\";\nimport {\n autoUpdateCdnPings,\n backendPings,\n blockGARegistryOverviewPings,\n blockGARegistryUiPings,\n blockRegistryOverviewPings,\n blockRegistryUiPings,\n reportToString,\n} from \"./pings\";\nimport type { Dispatcher } from \"undici\";\nimport type { TemplateReport } from \"./template\";\nimport {\n uploadTemplate,\n uploadFile,\n downloadFile,\n createTempFile,\n pythonSoftware,\n softwareCheck,\n createBigTempFile,\n downloadFromEveryStorage,\n} from \"./template\";\nimport { randomUUID } from \"node:crypto\";\n\n/** All reports we need to collect. */\ninterface NetworkReports {\n plPings: NetworkReport<string>[];\n\n blockRegistryOverviewChecks: HttpNetworkReport[];\n blockGARegistryOverviewChecks: HttpNetworkReport[];\n blockRegistryUiChecks: HttpNetworkReport[];\n blockGARegistryUiChecks: HttpNetworkReport[];\n\n autoUpdateCdnChecks: HttpNetworkReport[];\n\n uploadTemplateCheck: TemplateReport;\n uploadFileCheck: TemplateReport;\n downloadFileCheck: TemplateReport;\n softwareCheck: TemplateReport;\n pythonSoftwareCheck: TemplateReport;\n storageToDownloadReport: Record<string, TemplateReport>;\n}\n\nexport interface CheckNetworkOpts {\n /** Signal to abort all network checks. */\n signal?: AbortSignal;\n\n /** Platforma Backend pings options. */\n pingCheckDurationMs: number;\n pingTimeoutMs: number;\n maxPingsPerSecond: number;\n\n /** An options for CDN and block registry. */\n httpTimeoutMs: number;\n\n /** Block registry pings options. */\n blockRegistryDurationMs: number;\n maxRegistryChecksPerSecond: number;\n blockRegistryUrl: string;\n blockGARegistryUrl: string;\n blockOverviewPath: string;\n blockUiPath: string;\n\n /** CDN for auto-update pings options. */\n autoUpdateCdnDurationMs: number;\n maxAutoUpdateCdnChecksPerSecond: number;\n autoUpdateCdnUrl: string;\n\n /** Body limit for requests. */\n bodyLimit: number;\n\n /** Limit for the size of files to download from every storage. */\n everyStorageBytesLimit: number;\n /** Minimal size of files to create a directory from for every storage. */\n everyStorageMinFileSize: number;\n /** Maximal size of files to create a directory from for every storage. */\n everyStorageMaxFileSize: number;\n /** How many files to check from every storage. */\n everyStorageNFilesToCheck: number;\n /** Minimal number of ls requests for every storage. */\n everyStorageMinLsRequests: number;\n}\n\n/** Checks connectivity to Platforma Backend, to block registry\n * and to auto-update CDN,\n * and generates a string report. */\nexport async function checkNetwork(\n plCredentials: string,\n plUser: string | undefined,\n plPassword: string | undefined,\n optsOverrides: Partial<CheckNetworkOpts> = {},\n): Promise<string> {\n const undiciLogs: any[] = [];\n // Subscribe to all Undici diagnostic events\n undiciEvents.forEach((event) => {\n const diagnosticChannel = channel(event);\n diagnosticChannel.subscribe((message: any) => {\n const timestamp = new Date().toISOString();\n const data = { ...message };\n if (data?.response?.headers) {\n data.response = { ...data.response };\n data.response.headers = data.response.headers.slice();\n data.response.headers = data.response.headers.map((h: any) => h.toString());\n }\n\n // we try to upload big files, don't include the buffer in the report.\n if (data?.request?.body) {\n data.request = { ...data.request };\n data.request.body = `too big`;\n }\n\n undiciLogs.push(\n JSON.stringify({\n timestamp,\n event,\n data,\n }),\n );\n });\n });\n\n try {\n const {\n logger,\n plConfig,\n client,\n signer,\n downloadClient,\n uploadBlobClient,\n lsDriver,\n httpClient,\n ops,\n } = await initNetworkCheck(plCredentials, plUser, plPassword, optsOverrides);\n\n const { filePath: filePathToDownload, fileContent: fileContentToDownload } =\n await createTempFile();\n const { filePath: filePathToUpload } = await createBigTempFile();\n\n const report: NetworkReports = {\n plPings: await backendPings(ops, plConfig),\n blockRegistryOverviewChecks: await blockRegistryOverviewPings(ops, httpClient),\n blockGARegistryOverviewChecks: await blockGARegistryOverviewPings(ops, httpClient),\n blockRegistryUiChecks: await blockRegistryUiPings(ops, httpClient),\n blockGARegistryUiChecks: await blockGARegistryUiPings(ops, httpClient),\n\n autoUpdateCdnChecks: await autoUpdateCdnPings(ops, httpClient),\n\n uploadTemplateCheck: await uploadTemplate(logger, client, \"Jack\"),\n uploadFileCheck: await uploadFile(\n logger,\n signer,\n lsDriver,\n uploadBlobClient,\n client,\n filePathToUpload,\n ),\n downloadFileCheck: await downloadFile(\n logger,\n client,\n lsDriver,\n uploadBlobClient,\n downloadClient,\n filePathToDownload,\n fileContentToDownload,\n ),\n softwareCheck: await softwareCheck(client),\n pythonSoftwareCheck: await pythonSoftware(client, \"Jack\"),\n storageToDownloadReport: await downloadFromEveryStorage(logger, client, lsDriver, {\n minLsRequests: ops.everyStorageMinLsRequests,\n bytesLimit: ops.everyStorageBytesLimit,\n minFileSize: ops.everyStorageMinFileSize,\n maxFileSize: ops.everyStorageMaxFileSize,\n nFilesToCheck: ops.everyStorageNFilesToCheck,\n }),\n };\n\n return reportsToString(report, plCredentials, ops, undiciLogs);\n } catch (e) {\n return `Unhandled error while checking the network: ${e}`;\n }\n}\n\nexport async function initNetworkCheck(\n plCredentials: string,\n plUser: string | undefined,\n plPassword: string | undefined,\n optsOverrides: Partial<CheckNetworkOpts> = {},\n): Promise<{\n logger: MiLogger;\n plConfig: PlClientConfig;\n signer: Signer;\n client: PlClient;\n downloadClient: ClientDownload;\n uploadBlobClient: ClientUpload;\n lsDriver: LsDriver;\n httpClient: Dispatcher;\n ops: CheckNetworkOpts;\n terminate: () => Promise<void>;\n}> {\n const ops: CheckNetworkOpts = {\n pingCheckDurationMs: 10000,\n pingTimeoutMs: 3000,\n maxPingsPerSecond: 50,\n\n httpTimeoutMs: 3000,\n\n blockRegistryDurationMs: 3000,\n maxRegistryChecksPerSecond: 1,\n\n blockRegistryUrl: \"https://blocks.pl-open.science\",\n blockGARegistryUrl: \"https://blocks-ga.pl-open.science\",\n blockOverviewPath: \"v2/overview.json\",\n blockUiPath: \"v2/milaboratories/samples-and-data/1.7.0/ui.tgz\",\n\n autoUpdateCdnDurationMs: 5000,\n maxAutoUpdateCdnChecksPerSecond: 1,\n autoUpdateCdnUrl:\n \"https://cdn.platforma.bio/software/platforma-desktop-v2/windows/amd64/latest.yml\",\n\n bodyLimit: 300,\n\n everyStorageBytesLimit: 1024,\n everyStorageMinFileSize: 1024,\n everyStorageMaxFileSize: 200 * 1024 * 1024, // 200 MB\n everyStorageNFilesToCheck: 300,\n everyStorageMinLsRequests: 50,\n ...optsOverrides,\n };\n\n const plConfig = plAddressToConfig(plCredentials, {\n defaultRequestTimeout: ops.pingTimeoutMs,\n });\n\n // exposing alternative root for fields not to interfere with\n // projects of the user.\n plConfig.alternativeRoot = `check_network_${randomUUID()}`;\n\n const uaClient = await UnauthenticatedPlClient.build(plConfig);\n\n let auth: AuthInformation = {};\n if (plUser && plPassword) {\n auth = await uaClient.login(plUser, plPassword);\n }\n\n const client = await PlClient.init(plCredentials, { authInformation: auth });\n\n const httpClient = uaClient.ll.httpDispatcher;\n const logger = new ConsoleLoggerAdapter();\n\n // FIXME: do we need to get an actual secret?\n const signer = new HmacSha256Signer(\"localSecret\");\n\n // We could initialize middle-layer here, but for now it seems like an overkill.\n // Here's the code to do it:\n //\n // const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'platforma-network-check-'));\n // const ml = await MiddleLayer.init(client, tmpDir, {\n // logger,\n // localSecret: '',\n // localProjections: [],\n // openFileDialogCallback: () => Promise.resolve([]),\n // preferredUpdateChannel: 'stable',\n // });\n\n const downloadClient = createDownloadClient(logger, client, []);\n const uploadBlobClient = createUploadBlobClient(client, logger);\n\n const lsDriver = await LsDriver.init(logger, client, signer, [], () => Promise.resolve([]), []);\n\n const terminate = async () => {\n downloadClient.close();\n uploadBlobClient.close();\n await httpClient.close();\n await client.close();\n };\n\n return {\n logger,\n plConfig,\n client,\n signer,\n downloadClient,\n uploadBlobClient,\n lsDriver,\n httpClient,\n ops,\n terminate,\n };\n}\n\nfunction reportsToString(\n report: NetworkReports,\n plEndpoint: string,\n opts: CheckNetworkOpts,\n undiciLogs: any[],\n): string {\n const successPings = report.plPings.filter((p) => p.response.ok);\n const failedPings = report.plPings.filter((p) => !p.response.ok);\n const successPingsBodies = [\n ...new Set(successPings.map((p) => JSON.stringify((p.response as any).value))),\n ];\n\n const summary = (ok: boolean) => (ok ? \"OK\" : \"FAILED\");\n const templateSummary = (report: TemplateReport) =>\n report.status === \"ok\" ? \"OK\" : report.status === \"warn\" ? \"WARN\" : \"FAILED\";\n\n const pings = reportToString(report.plPings);\n const blockRegistryOverview = reportToString(report.blockRegistryOverviewChecks);\n const blockGARegistryOverview = reportToString(report.blockGARegistryOverviewChecks);\n const blockRegistryUi = reportToString(report.blockRegistryUiChecks);\n const blockGARegistryUi = reportToString(report.blockGARegistryUiChecks);\n const autoUpdateCdn = reportToString(report.autoUpdateCdnChecks);\n\n const storagesSummary = Object.entries(report.storageToDownloadReport)\n .map(([storage, report]) => `${templateSummary(report)} ${storage} storage check`)\n .join(\"\\n\");\n\n return `\n${summary(pings.ok)} pings to Platforma Backend\n${summary(blockRegistryOverview.ok)} block registry overview\n${summary(blockGARegistryOverview.ok)} block ga registry overview\n${summary(blockRegistryUi.ok)} block registry ui\n${summary(blockGARegistryUi.ok)} block ga registry ui\n${summary(autoUpdateCdn.ok)} auto-update CDN\n${templateSummary(report.uploadTemplateCheck)} upload template\n${templateSummary(report.uploadFileCheck)} upload file\n${templateSummary(report.downloadFileCheck)} download file\n${templateSummary(report.softwareCheck)} software check\n${templateSummary(report.pythonSoftwareCheck)} python software check\n${storagesSummary}\n\ndetails:\npl endpoint: ${plEndpoint};\noptions: ${JSON.stringify(opts, null, 2)}.\n\nUpload template response: ${report.uploadTemplateCheck.message}\n\nUpload file response: ${report.uploadFileCheck.message}\n\nDownload file response: ${report.downloadFileCheck.message}\n\nSoftware check response: ${report.softwareCheck.message}\nPython software check response: ${report.pythonSoftwareCheck.message}\nStorage to download responses: ${JSON.stringify(report.storageToDownloadReport, null, 2)}\n\nPlatforma pings: ${pings.details}\n\nBlock registry overview responses: ${blockRegistryOverview.details}\n\nBlock ga registry overview responses: ${blockGARegistryOverview.details}\n\nBlock registry ui responses: ${blockRegistryUi.details}\n\nBlock ga registry ui responses: ${blockGARegistryUi.details}\n\nAuto-update CDN responses: ${autoUpdateCdn.details}\n\ndumps:\nBlock registry overview dumps:\n${JSON.stringify(report.blockRegistryOverviewChecks, null, 2)}\n\nBlock ga registry overview dumps:\n${JSON.stringify(report.blockGARegistryOverviewChecks, null, 2)}\n\nBlock registry ui dumps:\n${JSON.stringify(report.blockRegistryUiChecks, null, 2)}\n\nBlock ga registry ui dumps:\n${JSON.stringify(report.blockGARegistryUiChecks, null, 2)}\n\nAuto-update CDN dumps:\n${JSON.stringify(report.autoUpdateCdnChecks, null, 2)}\n\nPlatforma pings error dumps:\n${JSON.stringify(failedPings, null, 2)}\n\nPlatforma pings success dump examples:\n${JSON.stringify(successPingsBodies, null, 2)}\n\nUndici logs:\n${undiciLogs.join(\"\\n\")}\n`;\n}\n\n// List of Undici diagnostic channels\nconst undiciEvents: string[] = [\n \"undici:request:create\", // When a new request is created\n \"undici:request:bodySent\", // When the request body is sent\n \"undici:request:headers\", // When request headers are sent\n \"undici:request:error\", // When a request encounters an error\n \"undici:request:trailers\", // When a response completes.\n\n \"undici:client:sendHeaders\",\n \"undici:client:beforeConnect\",\n \"undici:client:connected\",\n \"undici:client:connectError\",\n\n \"undici:socket:close\", // When a socket is closed\n \"undici:socket:connect\", // When a socket connects\n \"undici:socket:error\", // When a socket encounters an error\n\n \"undici:pool:request\", // When a request is added to the pool\n \"undici:pool:connect\", // When a pool creates a new connection\n \"undici:pool:disconnect\", // When a pool connection is closed\n \"undici:pool:destroy\", // When a pool is destroyed\n \"undici:dispatcher:request\", // When a dispatcher processes a request\n \"undici:dispatcher:connect\", // When a dispatcher connects\n \"undici:dispatcher:disconnect\", // When a dispatcher disconnects\n \"undici:dispatcher:retry\", // When a dispatcher retries a request\n];\n"],"mappings":"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"}
1
+ {"version":3,"file":"network_check.js","names":[],"sources":["../../src/network_check/network_check.ts"],"sourcesContent":["/** A utility to check network problems and gather statistics.\n * It's useful when we cannot connect to the server of a company\n * because of security reasons,\n * but they can send us and their DevOps team this report.\n *\n * What we check:\n * - pings to backend\n * - block registry for block overview and ui.\n * - autoupdate CDN.\n * - upload workflow to backend (workflow part via our API).\n * - the desktop could do multipart upload.\n * - the desktop could download files from S3.\n * - backend could download software and run it.\n * - backend could run python software.\n * - try to get something from every storage to work storage.\n *\n * We don't check backend access to S3 storage, it is checked on the start of backend.\n */\n\nimport type { AuthInformation, PlClientConfig } from \"@milaboratories/pl-client\";\nimport { PlClient, UnauthenticatedPlClient, plAddressToConfig } from \"@milaboratories/pl-client\";\nimport type { MiLogger, Signer } from \"@milaboratories/ts-helpers\";\nimport { ConsoleLoggerAdapter, HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport { channel } from \"node:diagnostics_channel\";\nimport type { ClientDownload, ClientUpload } from \"@milaboratories/pl-drivers\";\nimport { LsDriver, createDownloadClient, createUploadBlobClient } from \"@milaboratories/pl-drivers\";\nimport type { HttpNetworkReport, NetworkReport } from \"./pings\";\nimport {\n autoUpdateCdnPings,\n backendPings,\n blockGARegistryOverviewPings,\n blockGARegistryUiPings,\n blockRegistryOverviewPings,\n blockRegistryUiPings,\n reportToString,\n} from \"./pings\";\nimport type { Dispatcher } from \"undici\";\nimport type { TemplateReport } from \"./template\";\nimport {\n uploadTemplate,\n uploadFile,\n downloadFile,\n createTempFile,\n pythonSoftware,\n softwareCheck,\n createBigTempFile,\n downloadFromEveryStorage,\n} from \"./template\";\nimport { randomUUID } from \"node:crypto\";\n\n/** All reports we need to collect. */\ninterface NetworkReports {\n plPings: NetworkReport<string>[];\n\n blockRegistryOverviewChecks: HttpNetworkReport[];\n blockGARegistryOverviewChecks: HttpNetworkReport[];\n blockRegistryUiChecks: HttpNetworkReport[];\n blockGARegistryUiChecks: HttpNetworkReport[];\n\n autoUpdateCdnChecks: HttpNetworkReport[];\n\n uploadTemplateCheck: TemplateReport;\n uploadFileCheck: TemplateReport;\n downloadFileCheck: TemplateReport;\n softwareCheck: TemplateReport;\n pythonSoftwareCheck: TemplateReport;\n storageToDownloadReport: Record<string, TemplateReport>;\n}\n\nexport interface CheckNetworkOpts {\n /** Signal to abort all network checks. */\n signal?: AbortSignal;\n\n /** Platforma Backend pings options. */\n pingCheckDurationMs: number;\n pingTimeoutMs: number;\n maxPingsPerSecond: number;\n\n /** An options for CDN and block registry. */\n httpTimeoutMs: number;\n\n /** Block registry pings options. */\n blockRegistryDurationMs: number;\n maxRegistryChecksPerSecond: number;\n blockRegistryUrl: string;\n blockGARegistryUrl: string;\n blockOverviewPath: string;\n blockUiPath: string;\n\n /** CDN for auto-update pings options. */\n autoUpdateCdnDurationMs: number;\n maxAutoUpdateCdnChecksPerSecond: number;\n autoUpdateCdnUrl: string;\n\n /** Body limit for requests. */\n bodyLimit: number;\n\n /** Limit for the size of files to download from every storage. */\n everyStorageBytesLimit: number;\n /** Minimal size of files to create a directory from for every storage. */\n everyStorageMinFileSize: number;\n /** Maximal size of files to create a directory from for every storage. */\n everyStorageMaxFileSize: number;\n /** How many files to check from every storage. */\n everyStorageNFilesToCheck: number;\n /** Minimal number of ls requests for every storage. */\n everyStorageMinLsRequests: number;\n}\n\n/** Checks connectivity to Platforma Backend, to block registry\n * and to auto-update CDN,\n * and generates a string report. */\nexport async function checkNetwork(\n plCredentials: string,\n plUser: string | undefined,\n plPassword: string | undefined,\n optsOverrides: Partial<CheckNetworkOpts> = {},\n): Promise<string> {\n const undiciLogs: any[] = [];\n // Subscribe to all Undici diagnostic events\n undiciEvents.forEach((event) => {\n const diagnosticChannel = channel(event);\n diagnosticChannel.subscribe((message: any) => {\n const timestamp = new Date().toISOString();\n const data = { ...message };\n if (data?.response?.headers) {\n data.response = { ...data.response };\n data.response.headers = data.response.headers.slice();\n data.response.headers = data.response.headers.map((h: any) => h.toString());\n }\n\n // we try to upload big files, don't include the buffer in the report.\n if (data?.request?.body) {\n data.request = { ...data.request };\n data.request.body = `too big`;\n }\n\n undiciLogs.push(\n JSON.stringify({\n timestamp,\n event,\n data,\n }),\n );\n });\n });\n\n try {\n const {\n logger,\n plConfig,\n client,\n signer,\n downloadClient,\n uploadBlobClient,\n lsDriver,\n httpClient,\n ops,\n } = await initNetworkCheck(plCredentials, plUser, plPassword, optsOverrides);\n\n const { filePath: filePathToDownload, fileContent: fileContentToDownload } =\n await createTempFile();\n const { filePath: filePathToUpload } = await createBigTempFile();\n\n const report: NetworkReports = {\n plPings: await backendPings(ops, plConfig),\n blockRegistryOverviewChecks: await blockRegistryOverviewPings(ops, httpClient),\n blockGARegistryOverviewChecks: await blockGARegistryOverviewPings(ops, httpClient),\n blockRegistryUiChecks: await blockRegistryUiPings(ops, httpClient),\n blockGARegistryUiChecks: await blockGARegistryUiPings(ops, httpClient),\n\n autoUpdateCdnChecks: await autoUpdateCdnPings(ops, httpClient),\n\n uploadTemplateCheck: await uploadTemplate(logger, client, \"Jack\"),\n uploadFileCheck: await uploadFile(\n logger,\n signer,\n lsDriver,\n uploadBlobClient,\n client,\n filePathToUpload,\n ),\n downloadFileCheck: await downloadFile(\n logger,\n client,\n lsDriver,\n uploadBlobClient,\n downloadClient,\n filePathToDownload,\n fileContentToDownload,\n ),\n softwareCheck: await softwareCheck(client),\n pythonSoftwareCheck: await pythonSoftware(client, \"Jack\"),\n storageToDownloadReport: await downloadFromEveryStorage(logger, client, lsDriver, {\n minLsRequests: ops.everyStorageMinLsRequests,\n bytesLimit: ops.everyStorageBytesLimit,\n minFileSize: ops.everyStorageMinFileSize,\n maxFileSize: ops.everyStorageMaxFileSize,\n nFilesToCheck: ops.everyStorageNFilesToCheck,\n }),\n };\n\n return reportsToString(report, plCredentials, ops, undiciLogs);\n } catch (e) {\n return `Unhandled error while checking the network: ${e}`;\n }\n}\n\nexport async function initNetworkCheck(\n plCredentials: string,\n plUser: string | undefined,\n plPassword: string | undefined,\n optsOverrides: Partial<CheckNetworkOpts> = {},\n): Promise<{\n logger: MiLogger;\n plConfig: PlClientConfig;\n signer: Signer;\n client: PlClient;\n downloadClient: ClientDownload;\n uploadBlobClient: ClientUpload;\n lsDriver: LsDriver;\n httpClient: Dispatcher;\n ops: CheckNetworkOpts;\n terminate: () => Promise<void>;\n}> {\n const ops: CheckNetworkOpts = {\n pingCheckDurationMs: 10000,\n pingTimeoutMs: 3000,\n maxPingsPerSecond: 50,\n\n httpTimeoutMs: 3000,\n\n blockRegistryDurationMs: 3000,\n maxRegistryChecksPerSecond: 1,\n\n blockRegistryUrl: \"https://blocks.pl-open.science\",\n blockGARegistryUrl: \"https://blocks-ga.pl-open.science\",\n blockOverviewPath: \"v2/overview.json\",\n blockUiPath: \"v2/milaboratories/samples-and-data/1.7.0/ui.tgz\",\n\n autoUpdateCdnDurationMs: 5000,\n maxAutoUpdateCdnChecksPerSecond: 1,\n autoUpdateCdnUrl:\n \"https://cdn.platforma.bio/software/platforma-desktop-v2/windows/amd64/latest.yml\",\n\n bodyLimit: 300,\n\n everyStorageBytesLimit: 1024,\n everyStorageMinFileSize: 1024,\n everyStorageMaxFileSize: 200 * 1024 * 1024, // 200 MB\n everyStorageNFilesToCheck: 300,\n everyStorageMinLsRequests: 50,\n ...optsOverrides,\n };\n\n const plConfig = plAddressToConfig(plCredentials, {\n defaultRequestTimeout: ops.pingTimeoutMs,\n });\n\n // exposing alternative root for fields not to interfere with\n // projects of the user.\n plConfig.alternativeRoot = `check_network_${randomUUID()}`;\n\n const uaClient = await UnauthenticatedPlClient.build(plConfig);\n\n let auth: AuthInformation = {};\n if (plUser && plPassword) {\n auth = await uaClient.login(plUser, plPassword);\n }\n\n const client = await PlClient.init(plCredentials, { authInformation: auth });\n\n const httpClient = uaClient.ll.httpDispatcher;\n const logger = new ConsoleLoggerAdapter();\n\n // FIXME: do we need to get an actual secret?\n const signer = new HmacSha256Signer(\"localSecret\");\n\n // We could initialize middle-layer here, but for now it seems like an overkill.\n // Here's the code to do it:\n //\n // const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'platforma-network-check-'));\n // const ml = await MiddleLayer.init(client, tmpDir, {\n // logger,\n // localSecret: '',\n // localProjections: [],\n // openFileDialogCallback: () => Promise.resolve([]),\n // preferredUpdateChannel: 'stable',\n // });\n\n const downloadClient = createDownloadClient(logger, client, []);\n const uploadBlobClient = createUploadBlobClient(client, logger);\n\n const lsDriver = await LsDriver.init(logger, client, signer, [], () => Promise.resolve([]), []);\n\n const terminate = async () => {\n downloadClient.close();\n uploadBlobClient.close();\n await httpClient.close();\n await client.close();\n };\n\n return {\n logger,\n plConfig,\n client,\n signer,\n downloadClient,\n uploadBlobClient,\n lsDriver,\n httpClient,\n ops,\n terminate,\n };\n}\n\nfunction reportsToString(\n report: NetworkReports,\n plEndpoint: string,\n opts: CheckNetworkOpts,\n undiciLogs: any[],\n): string {\n const successPings = report.plPings.filter((p) => p.response.ok);\n const failedPings = report.plPings.filter((p) => !p.response.ok);\n const successPingsBodies = [\n ...new Set(successPings.map((p) => JSON.stringify((p.response as any).value))),\n ];\n\n const summary = (ok: boolean) => (ok ? \"OK\" : \"FAILED\");\n const templateSummary = (report: TemplateReport) =>\n report.status === \"ok\" ? \"OK\" : report.status === \"warn\" ? \"WARN\" : \"FAILED\";\n\n const pings = reportToString(report.plPings);\n const blockRegistryOverview = reportToString(report.blockRegistryOverviewChecks);\n const blockGARegistryOverview = reportToString(report.blockGARegistryOverviewChecks);\n const blockRegistryUi = reportToString(report.blockRegistryUiChecks);\n const blockGARegistryUi = reportToString(report.blockGARegistryUiChecks);\n const autoUpdateCdn = reportToString(report.autoUpdateCdnChecks);\n\n const storagesSummary = Object.entries(report.storageToDownloadReport)\n .map(([storage, report]) => `${templateSummary(report)} ${storage} storage check`)\n .join(\"\\n\");\n\n return `\n${summary(pings.ok)} pings to Platforma Backend\n${summary(blockRegistryOverview.ok)} block registry overview\n${summary(blockGARegistryOverview.ok)} block ga registry overview\n${summary(blockRegistryUi.ok)} block registry ui\n${summary(blockGARegistryUi.ok)} block ga registry ui\n${summary(autoUpdateCdn.ok)} auto-update CDN\n${templateSummary(report.uploadTemplateCheck)} upload template\n${templateSummary(report.uploadFileCheck)} upload file\n${templateSummary(report.downloadFileCheck)} download file\n${templateSummary(report.softwareCheck)} software check\n${templateSummary(report.pythonSoftwareCheck)} python software check\n${storagesSummary}\n\ndetails:\npl endpoint: ${plEndpoint};\noptions: ${JSON.stringify(opts, null, 2)}.\n\nUpload template response: ${report.uploadTemplateCheck.message}\n\nUpload file response: ${report.uploadFileCheck.message}\n\nDownload file response: ${report.downloadFileCheck.message}\n\nSoftware check response: ${report.softwareCheck.message}\nPython software check response: ${report.pythonSoftwareCheck.message}\nStorage to download responses: ${JSON.stringify(report.storageToDownloadReport, null, 2)}\n\nPlatforma pings: ${pings.details}\n\nBlock registry overview responses: ${blockRegistryOverview.details}\n\nBlock ga registry overview responses: ${blockGARegistryOverview.details}\n\nBlock registry ui responses: ${blockRegistryUi.details}\n\nBlock ga registry ui responses: ${blockGARegistryUi.details}\n\nAuto-update CDN responses: ${autoUpdateCdn.details}\n\ndumps:\nBlock registry overview dumps:\n${JSON.stringify(report.blockRegistryOverviewChecks, null, 2)}\n\nBlock ga registry overview dumps:\n${JSON.stringify(report.blockGARegistryOverviewChecks, null, 2)}\n\nBlock registry ui dumps:\n${JSON.stringify(report.blockRegistryUiChecks, null, 2)}\n\nBlock ga registry ui dumps:\n${JSON.stringify(report.blockGARegistryUiChecks, null, 2)}\n\nAuto-update CDN dumps:\n${JSON.stringify(report.autoUpdateCdnChecks, null, 2)}\n\nPlatforma pings error dumps:\n${JSON.stringify(failedPings, null, 2)}\n\nPlatforma pings success dump examples:\n${JSON.stringify(successPingsBodies, null, 2)}\n\nUndici logs:\n${undiciLogs.join(\"\\n\")}\n`;\n}\n\n// List of Undici diagnostic channels\nconst undiciEvents: string[] = [\n \"undici:request:create\", // When a new request is created\n \"undici:request:bodySent\", // When the request body is sent\n \"undici:request:headers\", // When request headers are sent\n \"undici:request:error\", // When a request encounters an error\n \"undici:request:trailers\", // When a response completes.\n\n \"undici:client:sendHeaders\",\n \"undici:client:beforeConnect\",\n \"undici:client:connected\",\n \"undici:client:connectError\",\n\n \"undici:socket:close\", // When a socket is closed\n \"undici:socket:connect\", // When a socket connects\n \"undici:socket:error\", // When a socket encounters an error\n\n \"undici:pool:request\", // When a request is added to the pool\n \"undici:pool:connect\", // When a pool creates a new connection\n \"undici:pool:disconnect\", // When a pool connection is closed\n \"undici:pool:destroy\", // When a pool is destroyed\n \"undici:dispatcher:request\", // When a dispatcher processes a request\n \"undici:dispatcher:connect\", // When a dispatcher connects\n \"undici:dispatcher:disconnect\", // When a dispatcher disconnects\n \"undici:dispatcher:retry\", // When a dispatcher retries a request\n];\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  let undici = require("undici");
2
2
  let _milaboratories_pl_client = require("@milaboratories/pl-client");
3
3
  let node_timers_promises = require("node:timers/promises");
4
- //#region src/network_check/pings.ts
4
+ // src/network_check/pings.ts
5
5
  async function backendPings(ops, plConfig) {
6
6
  return await recordPings(ops.pingCheckDurationMs, ops.maxPingsPerSecond, async () => {
7
7
  const response = await (await _milaboratories_pl_client.UnauthenticatedPlClient.build(plConfig)).ping();
@@ -104,7 +104,6 @@ function elapsedStat(reports) {
104
104
  median
105
105
  };
106
106
  }
107
- //#endregion
108
107
  exports.autoUpdateCdnPings = autoUpdateCdnPings;
109
108
  exports.backendPings = backendPings;
110
109
  exports.blockGARegistryOverviewPings = blockGARegistryOverviewPings;
@@ -1 +1 @@
1
- {"version":3,"file":"pings.cjs","names":["UnauthenticatedPlClient"],"sources":["../../src/network_check/pings.ts"],"sourcesContent":["import type { ValueOrError } from \"@milaboratories/ts-helpers\";\nimport { setTimeout } from \"node:timers/promises\";\nimport { request } from \"undici\";\nimport type { Dispatcher } from \"undici\";\nimport type { CheckNetworkOpts } from \"./network_check\";\nimport { UnauthenticatedPlClient, type PlClientConfig } from \"@milaboratories/pl-client\";\n\n/** A report about one concrete ping to the service. */\nexport interface NetworkReport<T> {\n elapsedMs: number;\n response: ValueOrError<T>;\n}\n\nexport type HttpNetworkReport = NetworkReport<{\n statusCode: number;\n beginningOfBody: string;\n}>;\n\nexport async function backendPings(\n ops: CheckNetworkOpts,\n plConfig: PlClientConfig,\n): Promise<NetworkReport<string>[]> {\n return await recordPings(\n ops.pingCheckDurationMs,\n ops.maxPingsPerSecond,\n async () => {\n const uaClient = await UnauthenticatedPlClient.build(plConfig);\n const response = await uaClient.ping();\n return JSON.stringify(response).slice(0, ops.bodyLimit) + \"...\";\n },\n ops.signal,\n );\n}\n\nexport async function blockRegistryOverviewPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.blockRegistryDurationMs,\n ops.maxRegistryChecksPerSecond,\n async () =>\n await requestUrl(new URL(ops.blockOverviewPath, ops.blockRegistryUrl), ops, httpClient),\n ops.signal,\n );\n}\n\nexport async function blockGARegistryOverviewPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.blockRegistryDurationMs,\n ops.maxRegistryChecksPerSecond,\n async () =>\n await requestUrl(new URL(ops.blockOverviewPath, ops.blockGARegistryUrl), ops, httpClient),\n ops.signal,\n );\n}\n\nexport async function blockRegistryUiPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.blockRegistryDurationMs,\n ops.maxRegistryChecksPerSecond,\n async () => await requestUrl(new URL(ops.blockUiPath, ops.blockRegistryUrl), ops, httpClient),\n ops.signal,\n );\n}\n\nexport async function blockGARegistryUiPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.blockRegistryDurationMs,\n ops.maxRegistryChecksPerSecond,\n async () => await requestUrl(new URL(ops.blockUiPath, ops.blockGARegistryUrl), ops, httpClient),\n ops.signal,\n );\n}\n\nexport async function autoUpdateCdnPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.autoUpdateCdnDurationMs,\n ops.maxAutoUpdateCdnChecksPerSecond,\n async () => await requestUrl(ops.autoUpdateCdnUrl, ops, httpClient),\n ops.signal,\n );\n}\n\n/** Executes a body several times per second up to the given duration,\n * and returns results and elapsed time for every result. */\nexport async function recordPings<T>(\n pingCheckDurationMs: number,\n maxPingsPerSecond: number,\n body: () => Promise<T>,\n signal?: AbortSignal,\n): Promise<NetworkReport<T>[]> {\n const startPings = Date.now();\n const reports: NetworkReport<T>[] = [];\n\n while (elapsed(startPings) < pingCheckDurationMs && !signal?.aborted) {\n const startPing = Date.now();\n let response: ValueOrError<T>;\n try {\n response = { ok: true, value: await body() };\n } catch (e) {\n response = { ok: false, error: e };\n }\n const elapsedPing = elapsed(startPing);\n\n reports.push({\n elapsedMs: elapsedPing,\n response,\n });\n\n const sleepBetweenPings = 1000 / maxPingsPerSecond - elapsedPing;\n\n if (sleepBetweenPings > 0) {\n try {\n await setTimeout(sleepBetweenPings, undefined, signal ? { signal } : undefined);\n } catch (e: unknown) {\n if (e instanceof Error && e.name === \"AbortError\") break;\n throw e;\n }\n }\n }\n\n return reports;\n}\n\nexport async function requestUrl(url: string | URL, ops: CheckNetworkOpts, httpClient: Dispatcher) {\n const { body: rawBody, statusCode } = await request(url, {\n dispatcher: httpClient,\n headersTimeout: ops.httpTimeoutMs,\n bodyTimeout: ops.httpTimeoutMs,\n });\n const body = await rawBody.text();\n\n return {\n statusCode: statusCode,\n beginningOfBody: body.slice(0, ops.bodyLimit) + \"...\",\n };\n}\n\nexport function elapsed(startMs: number): number {\n return Date.now() - startMs;\n}\n\nexport function reportToString<T>(report: NetworkReport<T>[]): {\n ok: boolean;\n details: string;\n} {\n const successes = report.filter((r) => r.response.ok);\n const errorsLen = report.length - successes.length;\n const { mean, median } = elapsedStat(report);\n\n const details = `\n total: ${report.length};\n successes: ${successes.length};\n errors: ${errorsLen};\n mean in ms: ${mean};\n median in ms: ${median};\n `;\n\n return {\n ok: errorsLen === 0,\n details,\n };\n}\n\nfunction elapsedStat(reports: { elapsedMs: number }[]) {\n if (reports.length === 0) return { mean: 0, median: undefined };\n const checks = reports.map((p) => p.elapsedMs).sort();\n const mean = checks.reduce((sum, p) => sum + p) / checks.length;\n\n let median = undefined;\n if (checks.length > 0) {\n const mid = Math.floor(checks.length / 2);\n median = checks.length % 2 ? checks[mid] : (checks[mid - 1] + checks[mid]) / 2;\n }\n\n return { mean, median };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"pings.cjs","names":["UnauthenticatedPlClient","setTimeout","request"],"sources":["../../src/network_check/pings.ts"],"sourcesContent":["import type { ValueOrError } from \"@milaboratories/ts-helpers\";\nimport { setTimeout } from \"node:timers/promises\";\nimport { request } from \"undici\";\nimport type { Dispatcher } from \"undici\";\nimport type { CheckNetworkOpts } from \"./network_check\";\nimport { UnauthenticatedPlClient, type PlClientConfig } from \"@milaboratories/pl-client\";\n\n/** A report about one concrete ping to the service. */\nexport interface NetworkReport<T> {\n elapsedMs: number;\n response: ValueOrError<T>;\n}\n\nexport type HttpNetworkReport = NetworkReport<{\n statusCode: number;\n beginningOfBody: string;\n}>;\n\nexport async function backendPings(\n ops: CheckNetworkOpts,\n plConfig: PlClientConfig,\n): Promise<NetworkReport<string>[]> {\n return await recordPings(\n ops.pingCheckDurationMs,\n ops.maxPingsPerSecond,\n async () => {\n const uaClient = await UnauthenticatedPlClient.build(plConfig);\n const response = await uaClient.ping();\n return JSON.stringify(response).slice(0, ops.bodyLimit) + \"...\";\n },\n ops.signal,\n );\n}\n\nexport async function blockRegistryOverviewPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.blockRegistryDurationMs,\n ops.maxRegistryChecksPerSecond,\n async () =>\n await requestUrl(new URL(ops.blockOverviewPath, ops.blockRegistryUrl), ops, httpClient),\n ops.signal,\n );\n}\n\nexport async function blockGARegistryOverviewPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.blockRegistryDurationMs,\n ops.maxRegistryChecksPerSecond,\n async () =>\n await requestUrl(new URL(ops.blockOverviewPath, ops.blockGARegistryUrl), ops, httpClient),\n ops.signal,\n );\n}\n\nexport async function blockRegistryUiPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.blockRegistryDurationMs,\n ops.maxRegistryChecksPerSecond,\n async () => await requestUrl(new URL(ops.blockUiPath, ops.blockRegistryUrl), ops, httpClient),\n ops.signal,\n );\n}\n\nexport async function blockGARegistryUiPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.blockRegistryDurationMs,\n ops.maxRegistryChecksPerSecond,\n async () => await requestUrl(new URL(ops.blockUiPath, ops.blockGARegistryUrl), ops, httpClient),\n ops.signal,\n );\n}\n\nexport async function autoUpdateCdnPings(\n ops: CheckNetworkOpts,\n httpClient: Dispatcher,\n): Promise<HttpNetworkReport[]> {\n return await recordPings(\n ops.autoUpdateCdnDurationMs,\n ops.maxAutoUpdateCdnChecksPerSecond,\n async () => await requestUrl(ops.autoUpdateCdnUrl, ops, httpClient),\n ops.signal,\n );\n}\n\n/** Executes a body several times per second up to the given duration,\n * and returns results and elapsed time for every result. */\nexport async function recordPings<T>(\n pingCheckDurationMs: number,\n maxPingsPerSecond: number,\n body: () => Promise<T>,\n signal?: AbortSignal,\n): Promise<NetworkReport<T>[]> {\n const startPings = Date.now();\n const reports: NetworkReport<T>[] = [];\n\n while (elapsed(startPings) < pingCheckDurationMs && !signal?.aborted) {\n const startPing = Date.now();\n let response: ValueOrError<T>;\n try {\n response = { ok: true, value: await body() };\n } catch (e) {\n response = { ok: false, error: e };\n }\n const elapsedPing = elapsed(startPing);\n\n reports.push({\n elapsedMs: elapsedPing,\n response,\n });\n\n const sleepBetweenPings = 1000 / maxPingsPerSecond - elapsedPing;\n\n if (sleepBetweenPings > 0) {\n try {\n await setTimeout(sleepBetweenPings, undefined, signal ? { signal } : undefined);\n } catch (e: unknown) {\n if (e instanceof Error && e.name === \"AbortError\") break;\n throw e;\n }\n }\n }\n\n return reports;\n}\n\nexport async function requestUrl(url: string | URL, ops: CheckNetworkOpts, httpClient: Dispatcher) {\n const { body: rawBody, statusCode } = await request(url, {\n dispatcher: httpClient,\n headersTimeout: ops.httpTimeoutMs,\n bodyTimeout: ops.httpTimeoutMs,\n });\n const body = await rawBody.text();\n\n return {\n statusCode: statusCode,\n beginningOfBody: body.slice(0, ops.bodyLimit) + \"...\",\n };\n}\n\nexport function elapsed(startMs: number): number {\n return Date.now() - startMs;\n}\n\nexport function reportToString<T>(report: NetworkReport<T>[]): {\n ok: boolean;\n details: string;\n} {\n const successes = report.filter((r) => r.response.ok);\n const errorsLen = report.length - successes.length;\n const { mean, median } = elapsedStat(report);\n\n const details = `\n total: ${report.length};\n successes: ${successes.length};\n errors: ${errorsLen};\n mean in ms: ${mean};\n median in ms: ${median};\n `;\n\n return {\n ok: errorsLen === 0,\n details,\n };\n}\n\nfunction elapsedStat(reports: { elapsedMs: number }[]) {\n if (reports.length === 0) return { mean: 0, median: undefined };\n const checks = reports.map((p) => p.elapsedMs).sort();\n const mean = checks.reduce((sum, p) => sum + p) / checks.length;\n\n let median = undefined;\n if (checks.length > 0) {\n const mid = Math.floor(checks.length / 2);\n median = checks.length % 2 ? checks[mid] : (checks[mid - 1] + checks[mid]) / 2;\n }\n\n return { mean, median };\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { request } from "undici";
2
2
  import { UnauthenticatedPlClient } from "@milaboratories/pl-client";
3
3
  import { setTimeout } from "node:timers/promises";
4
- //#region src/network_check/pings.ts
4
+ // src/network_check/pings.ts
5
5
  async function backendPings(ops, plConfig) {
6
6
  return await recordPings(ops.pingCheckDurationMs, ops.maxPingsPerSecond, async () => {
7
7
  const response = await (await UnauthenticatedPlClient.build(plConfig)).ping();
@@ -104,7 +104,6 @@ function elapsedStat(reports) {
104
104
  median
105
105
  };
106
106
  }
107
- //#endregion
108
107
  export { autoUpdateCdnPings, backendPings, blockGARegistryOverviewPings, blockGARegistryUiPings, blockRegistryOverviewPings, blockRegistryUiPings, elapsed, recordPings, reportToString, requestUrl };
109
108
 
110
109
  //# sourceMappingURL=pings.js.map
@@ -13,7 +13,7 @@ let _milaboratories_pl_drivers = require("@milaboratories/pl-drivers");
13
13
  let node_stream_consumers = require("node:stream/consumers");
14
14
  let node_os = require("node:os");
15
15
  node_os = require_runtime.__toESM(node_os, 1);
16
- //#region src/network_check/template.ts
16
+ // src/network_check/template.ts
17
17
  /** Uploads `hello-world` template and checks the output is correct. */
18
18
  async function uploadTemplate(logger, pl, name) {
19
19
  try {
@@ -252,7 +252,7 @@ async function* listFilesSequence(lsDriver, storage, parent, nLsRequests) {
252
252
  /** Creates a big temporary file with random content. */
253
253
  async function createBigTempFile() {
254
254
  const filePath = node_path.default.join(node_os.default.tmpdir(), `check-network-big-temp-${Date.now()}.bin`);
255
- const fileContent = (0, node_crypto.randomBytes)(20 * 1024 * 1024);
255
+ const fileContent = (0, node_crypto.randomBytes)(20971520);
256
256
  await node_fs_promises.default.appendFile(filePath, fileContent);
257
257
  return { filePath };
258
258
  }
@@ -271,7 +271,9 @@ async function createTempFile() {
271
271
  */
272
272
  async function runTemplate(client, tpl, ephemeral, inputs, outputs) {
273
273
  return await client.withWriteTx("TemplateRender", async (tx) => {
274
- const outputFields = require_render_template.createRenderTemplate(tx, require_template_loading.loadTemplate(tx, await require_template_loading.prepareTemplateSpec(tpl)), ephemeral, inputs(tx), outputs);
274
+ const preparedTemplate = await require_template_loading.prepareTemplateSpec(tpl);
275
+ const tplResource = require_template_loading.loadTemplate(tx, preparedTemplate);
276
+ const outputFields = require_render_template.createRenderTemplate(tx, tplResource, ephemeral, inputs(tx), outputs);
275
277
  const outputsIds = {};
276
278
  for (const output of outputs) {
277
279
  const fieldRef = (0, _milaboratories_pl_client.field)(client.clientRoot, output);
@@ -300,7 +302,6 @@ async function deleteFields(client, fieldIds) {
300
302
  await tx.commit();
301
303
  });
302
304
  }
303
- //#endregion
304
305
  exports.chooseFile = chooseFile;
305
306
  exports.createBigTempFile = createBigTempFile;
306
307
  exports.createTempFile = createTempFile;
@@ -1 +1 @@
1
- {"version":3,"file":"template.cjs","names":["SdkTemplates","Pl","ImportFileHandleUploadData","path","os","fs","createRenderTemplate","loadTemplate","prepareTemplateSpec","ContinuePolling"],"sources":["../../src/network_check/template.ts"],"sourcesContent":["import type { FieldId, FieldRef, PlClient, ResourceData } from \"@milaboratories/pl-client\";\nimport {\n type PlTransaction,\n ContinuePolling,\n field,\n isNotNullSignedResourceId,\n isNullSignedResourceId,\n Pl,\n poll,\n toGlobalFieldId,\n} from \"@milaboratories/pl-client\";\nimport { createRenderTemplate } from \"../mutator/template/render_template\";\nimport { Templates as SdkTemplates } from \"@platforma-sdk/workflow-tengo\";\nimport type { TemplateSpecAny } from \"../model/template_spec\";\nimport { loadTemplate, prepareTemplateSpec } from \"../mutator/template/template_loading\";\nimport type { ClientDownload, LsDriver } from \"@milaboratories/pl-drivers\";\nimport {\n ImportFileHandleUploadData,\n isSignMatch,\n isUpload,\n uploadBlob,\n type ClientUpload,\n type LsEntryWithFileStats,\n} from \"@milaboratories/pl-drivers\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { notEmpty, type MiLogger } from \"@milaboratories/ts-helpers\";\nimport type { ResourceInfo } from \"@milaboratories/pl-tree\";\nimport { text } from \"node:stream/consumers\";\nimport path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport { randomBytes } from \"node:crypto\";\nimport type { StorageEntry } from \"@milaboratories/pl-model-common\";\n\nexport interface TemplateReport {\n status: \"ok\" | \"warn\" | \"failed\";\n message: string;\n}\n\n/** Uploads `hello-world` template and checks the output is correct. */\nexport async function uploadTemplate(\n logger: MiLogger,\n pl: PlClient,\n name: string,\n): Promise<TemplateReport> {\n try {\n const gotGreeting = await runUploadTemplate(logger, pl, name);\n if (gotGreeting !== `Hello, ${name}`) {\n return {\n status: \"failed\",\n message: `Template uploading failed: expected: ${name}, got: ${gotGreeting}`,\n };\n }\n\n return { status: \"ok\", message: `Template uploading succeeded: ${gotGreeting}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `Template uploading failed: error occurred: ${e}` };\n }\n}\n\nexport async function runUploadTemplate(\n logger: MiLogger,\n pl: PlClient,\n name: string,\n): Promise<string> {\n const outputs = await runTemplate(\n pl,\n SdkTemplates[\"check_network.upload_template\"],\n true,\n (tx) => ({\n name: tx.createValue(Pl.JsonObject, JSON.stringify(name)),\n }),\n [\"greeting\"],\n );\n\n try {\n return JSON.parse(notEmpty((await getFieldValue(pl, outputs.greeting)).data?.toString()));\n } finally {\n if (outputs != undefined) {\n await deleteFields(pl, Object.values(outputs));\n }\n }\n}\n\n/** Uploads a file to the backend and checks the output is a Blob resource. */\nexport async function uploadFile(\n logger: MiLogger,\n signer: Signer,\n lsDriver: LsDriver,\n uploadClient: ClientUpload,\n pl: PlClient,\n filePath: string,\n): Promise<TemplateReport> {\n try {\n const gotBlob = await runUploadFile(logger, signer, lsDriver, uploadClient, pl, filePath);\n\n if (gotBlob.type.name !== \"Blob\") {\n return { status: \"failed\", message: `File uploading failed: ${gotBlob.type.name}` };\n }\n\n return { status: \"ok\", message: `File uploading succeeded: ${gotBlob.type.name}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `File uploading failed: error occurred: ${e}` };\n }\n}\n\nexport async function runUploadFile(\n logger: MiLogger,\n signer: Signer,\n lsDriver: LsDriver,\n uploadClient: ClientUpload,\n pl: PlClient,\n filePath: string,\n): Promise<ResourceInfo> {\n const handle = await lsDriver.getLocalFileHandle(filePath);\n const result = await runTemplate(\n pl,\n SdkTemplates[\"check_network.upload_blob\"],\n true,\n (tx) => ({\n file: tx.createValue(Pl.JsonObject, JSON.stringify(handle)),\n }),\n [\"progress\", \"file\"],\n );\n\n try {\n const progress = await getFieldValue(pl, result.progress);\n\n if (isUpload(progress)) {\n const uploadData = ImportFileHandleUploadData.parse(\n JSON.parse(notEmpty(progress.data?.toString())),\n );\n const isUploadSignMatch = isSignMatch(signer, uploadData.localPath, uploadData.pathSignature);\n\n if (isUploadSignMatch) {\n await uploadBlob(logger, uploadClient, progress, uploadData, () => false, {\n nPartsWithThisUploadSpeed: 10,\n nPartsToIncreaseUpload: 10,\n currentSpeed: 10,\n maxSpeed: 10,\n });\n }\n }\n\n return await getFieldValue(pl, result.file);\n } finally {\n await deleteFields(pl, Object.values(result));\n }\n}\n\n/** Uploads a file to the backend and then tries to download it back. */\nexport async function downloadFile(\n logger: MiLogger,\n pl: PlClient,\n lsDriver: LsDriver,\n uploadClient: ClientUpload,\n downloadClient: ClientDownload,\n filePath: string,\n fileContent: string,\n): Promise<TemplateReport> {\n try {\n const gotFileContent = await runDownloadFile(\n logger,\n pl,\n lsDriver,\n uploadClient,\n downloadClient,\n filePath,\n );\n\n if (gotFileContent !== fileContent) {\n return {\n status: \"failed\",\n message: `File downloading failed: expected: ${fileContent}, got: ${gotFileContent}`,\n };\n }\n return { status: \"ok\", message: `File downloading succeeded: ${gotFileContent}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `File downloading failed: error occurred: ${e}` };\n }\n}\n\nexport async function runDownloadFile(\n logger: MiLogger,\n pl: PlClient,\n lsDriver: LsDriver,\n uploadClient: ClientUpload,\n downloadClient: ClientDownload,\n filePath: string,\n) {\n const handle = await lsDriver.getLocalFileHandle(filePath);\n\n const outputs = await runTemplate(\n pl,\n SdkTemplates[\"check_network.download_blob\"],\n true,\n (tx) => ({ file: tx.createValue(Pl.JsonObject, JSON.stringify(handle)) }),\n [\"progress\", \"file\"],\n );\n\n try {\n const progress = await getFieldValue(pl, outputs.progress);\n\n await uploadBlob(\n logger,\n uploadClient,\n progress,\n ImportFileHandleUploadData.parse(JSON.parse(notEmpty(progress.data?.toString()))),\n () => false,\n {\n nPartsWithThisUploadSpeed: 1,\n nPartsToIncreaseUpload: 1,\n currentSpeed: 1,\n maxSpeed: 1,\n },\n );\n\n const fileInfo = await getFieldValue(pl, outputs.file);\n\n return await downloadClient.withBlobContent(\n fileInfo,\n {},\n {},\n async (content) => await text(content),\n );\n } finally {\n await deleteFields(pl, Object.values(outputs));\n }\n}\n\n/** Runs Go's hello-world binary. */\nexport async function softwareCheck(pl: PlClient): Promise<TemplateReport> {\n try {\n const gotGreeting = await runSoftware(pl);\n\n if (gotGreeting !== \"Hello from go binary\\n\") {\n return { status: \"failed\", message: `Software check failed: got: ${gotGreeting}` };\n }\n return { status: \"ok\", message: `Software check succeeded: ${gotGreeting}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `Software check failed: error occurred: ${e}` };\n }\n}\n\nexport async function runSoftware(pl: PlClient): Promise<string> {\n const result = await runTemplate(\n pl,\n SdkTemplates[\"check_network.run_hello_world\"],\n true,\n (_: PlTransaction) => ({}),\n [\"greeting\"],\n );\n\n try {\n return notEmpty((await getFieldValue(pl, result.greeting)).data?.toString());\n } finally {\n await deleteFields(pl, Object.values(result));\n }\n}\n\n/** Runs Python hello-world. */\nexport async function pythonSoftware(pl: PlClient, name: string): Promise<TemplateReport> {\n try {\n const gotGreeting = await runPythonSoftware(pl, name);\n\n if (gotGreeting !== `Hello, ${name}!\\n`) {\n return { status: \"failed\", message: `Python software check failed: got: ${gotGreeting}` };\n }\n return { status: \"ok\", message: `Python software check succeeded: ${gotGreeting}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `Python software check failed: error occurred: ${e}` };\n }\n}\n\nexport async function runPythonSoftware(pl: PlClient, name: string): Promise<string> {\n const result = await runTemplate(\n pl,\n SdkTemplates[\"check_network.run_hello_world_py\"],\n true,\n (tx) => ({ name: tx.createValue(Pl.JsonObject, JSON.stringify(name)) }),\n [\"greeting\"],\n );\n\n try {\n return notEmpty((await getFieldValue(pl, result.greeting)).data?.toString());\n } finally {\n await deleteFields(pl, Object.values(result));\n }\n}\n\n/** Tries to download a file from every storage. */\nexport async function downloadFromEveryStorage(\n logger: MiLogger,\n pl: PlClient,\n lsDriver: LsDriver,\n ops: {\n minLsRequests: number;\n bytesLimit: number;\n minFileSize: number;\n maxFileSize: number;\n nFilesToCheck: number;\n },\n): Promise<Record<string, TemplateReport>> {\n try {\n const storages = await lsDriver.getStorageList();\n const results: Record<string, TemplateReport> = {};\n\n for (const storage of storages) {\n const result = await chooseFile(\n lsDriver,\n storage,\n ops.nFilesToCheck,\n ops.minFileSize,\n ops.maxFileSize,\n ops.minLsRequests,\n );\n if (result.file === undefined) {\n results[storage.id] = {\n status: \"warn\",\n message:\n `No file between ${ops.minFileSize} and ${ops.maxFileSize} bytes ` +\n `found in storage ${storage.name}, checked ${result.nCheckedFiles} files, ` +\n `did ${result.nLsRequests} ls requests`,\n };\n continue;\n }\n\n logger.info(`Downloading file ${JSON.stringify(result)} from storage ${storage.name}`);\n const outputs = await runTemplate(\n pl,\n SdkTemplates[\"check_network.create_workdir_from_storage\"],\n true,\n (tx) => ({\n file: tx.createValue(\n Pl.JsonObject,\n JSON.stringify((result.file as { handle: string }).handle),\n ),\n }),\n [\"workdirTypeName\"],\n );\n\n try {\n const workdirTypeName = JSON.parse(\n Buffer.from((await getFieldValue(pl, outputs.workdirTypeName)).data!).toString(),\n ) as string;\n\n if (workdirTypeName?.startsWith(\"WorkingDirectory\")) {\n results[storage.id] = {\n status: \"ok\",\n message:\n `Workdir creation succeeded, size of file: ${result.file?.size}, ` +\n `checked ${result.nCheckedFiles} files, did ${result.nLsRequests} ls requests`,\n };\n } else {\n results[storage.id] = {\n status: \"failed\",\n message:\n `Workdir creation failed: ${workdirTypeName}, size of file: ${result.file?.size}, ` +\n `checked ${result.nCheckedFiles} files, did ${result.nLsRequests} ls requests`,\n };\n }\n } finally {\n await deleteFields(pl, Object.values(outputs));\n }\n }\n\n return results;\n } catch (e: unknown) {\n return {\n unknown: {\n status: \"failed\",\n message: `Download from every storage failed: error occurred: ${e}`,\n },\n };\n }\n}\n\n/** Chooses a random file from the storage in a size range.\n * If we couldn't find a normal-sized file, we'll return a small file to check at least something.\n */\nexport async function chooseFile(\n lsDriver: LsDriver,\n storage: StorageEntry,\n limit: number,\n minSize: number,\n maxSize: number,\n minLsRequests: number,\n): Promise<{\n file: LsEntryWithFileStats | undefined;\n nLsRequests: number;\n nCheckedFiles: number;\n}> {\n const files = listFilesSequence(lsDriver, storage, \"\", 0);\n\n // return small file in case we don't have many normal-sized files.\n // While we'll download only a small range of bytes from the file,\n // we don't want to return a big file in case the underlying S3 doesn't support range requests.\n let smallFile: LsEntryWithFileStats | undefined;\n let nCheckedFiles = 0;\n let maxNLsRequests = 0;\n\n for await (const { file, nLsRequests } of files) {\n maxNLsRequests = Math.max(maxNLsRequests, nLsRequests);\n\n if (nCheckedFiles >= limit && maxNLsRequests > minLsRequests) {\n // we reached a limit on both the number of files and the number of ls requests.\n return { file: smallFile, nLsRequests: maxNLsRequests, nCheckedFiles };\n }\n nCheckedFiles++;\n if (minSize <= file.size && file.size <= maxSize) {\n return { file, nLsRequests: maxNLsRequests, nCheckedFiles };\n } else if (file.size < minSize) {\n smallFile = file;\n }\n }\n\n return { file: smallFile, nLsRequests: maxNLsRequests, nCheckedFiles };\n}\n\n/** Deep-first search for files in the storage. */\nexport async function* listFilesSequence(\n lsDriver: LsDriver,\n storage: StorageEntry,\n parent: string,\n nLsRequests: number,\n): AsyncGenerator<{ file: LsEntryWithFileStats; nLsRequests: number }, void, unknown> {\n nLsRequests++;\n const files = await lsDriver.listRemoteFilesWithFileStats(storage.handle, parent);\n\n for (const file of files.entries) {\n if (file.type === \"file\" && file.fullPath.startsWith(parent)) {\n yield {\n file,\n nLsRequests,\n };\n } else if (file.type === \"dir\") {\n for await (const nestedFile of listFilesSequence(\n lsDriver,\n storage,\n file.fullPath,\n nLsRequests,\n )) {\n nLsRequests = Math.max(nestedFile.nLsRequests, nLsRequests);\n yield nestedFile;\n }\n }\n }\n}\n\n/** Creates a big temporary file with random content. */\nexport async function createBigTempFile(): Promise<{ filePath: string }> {\n const filePath = path.join(os.tmpdir(), `check-network-big-temp-${Date.now()}.bin`);\n const fileSize = 20 * 1024 * 1024; // 20 MiB\n\n const fileContent = randomBytes(fileSize);\n\n await fs.appendFile(filePath, fileContent);\n\n return { filePath };\n}\n\n/** Creates a temporarly file we could use for uploading and downloading. */\nexport async function createTempFile(): Promise<{ filePath: string; fileContent: string }> {\n const filePath = path.join(os.tmpdir(), `check-network-temp-${Date.now()}.txt`);\n\n const fileContent = \"Hello, world! \" + new Date().toISOString();\n await fs.writeFile(filePath, fileContent);\n\n return { filePath, fileContent };\n}\n\n/** Creates a template and RenderTemplate resources, gets all resources from outputs.\n * Throws a error if any of the outputs failed.\n */\nasync function runTemplate(\n client: PlClient,\n tpl: TemplateSpecAny,\n ephemeral: boolean,\n inputs: (tx: PlTransaction) => Pl.PlRecord,\n outputs: string[],\n): Promise<Record<string, FieldId>> {\n return await client.withWriteTx(\"TemplateRender\", async (tx) => {\n const preparedTemplate = await prepareTemplateSpec(tpl);\n const tplResource = loadTemplate(tx, preparedTemplate);\n\n const outputFields: Record<string, FieldRef> = createRenderTemplate(\n tx,\n tplResource,\n ephemeral,\n inputs(tx),\n outputs,\n );\n\n const outputsIds: Record<string, FieldId> = {};\n\n for (const output of outputs) {\n const fieldRef = field(client.clientRoot, output) as FieldId;\n tx.createField(fieldRef, \"Dynamic\", outputFields[output]);\n outputsIds[output] = await toGlobalFieldId(fieldRef);\n }\n\n await tx.commit();\n\n return outputsIds;\n });\n}\n\n/** Gets a resource from field's value or throws a error. */\nasync function getFieldValue(client: PlClient, fieldId: FieldId): Promise<ResourceData> {\n // We could also do polling with pl-tree, but it seemed like an overkill,\n // that's why we have a simple polling here.\n\n return await poll(client, async (tx) => {\n const field = await tx.tx.getField(fieldId);\n if (isNotNullSignedResourceId(field.error)) {\n const err = await tx.tx.getResourceData(field.error, true);\n throw new Error(`getFieldValue of \"${fieldId.fieldName}\" field failed: ${err.data}`);\n }\n\n if (isNullSignedResourceId(field.value)) {\n throw new ContinuePolling();\n }\n\n return await tx.tx.getResourceData(field.value, true);\n });\n}\n\nasync function deleteFields(client: PlClient, fieldIds: FieldId[]) {\n await client.withWriteTx(\"DeleteFields\", async (tx) => {\n for (const fieldId of fieldIds) {\n tx.resetField(fieldId);\n }\n await tx.commit();\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"template.cjs","names":["SdkTemplates","Pl","notEmpty","isUpload","ImportFileHandleUploadData","isSignMatch","uploadBlob","text","path","os","randomBytes","fs","prepareTemplateSpec","loadTemplate","createRenderTemplate","field","toGlobalFieldId","poll","isNotNullSignedResourceId","isNullSignedResourceId","ContinuePolling"],"sources":["../../src/network_check/template.ts"],"sourcesContent":["import type { FieldId, FieldRef, PlClient, ResourceData } from \"@milaboratories/pl-client\";\nimport {\n type PlTransaction,\n ContinuePolling,\n field,\n isNotNullSignedResourceId,\n isNullSignedResourceId,\n Pl,\n poll,\n toGlobalFieldId,\n} from \"@milaboratories/pl-client\";\nimport { createRenderTemplate } from \"../mutator/template/render_template\";\nimport { Templates as SdkTemplates } from \"@platforma-sdk/workflow-tengo\";\nimport type { TemplateSpecAny } from \"../model/template_spec\";\nimport { loadTemplate, prepareTemplateSpec } from \"../mutator/template/template_loading\";\nimport type { ClientDownload, LsDriver } from \"@milaboratories/pl-drivers\";\nimport {\n ImportFileHandleUploadData,\n isSignMatch,\n isUpload,\n uploadBlob,\n type ClientUpload,\n type LsEntryWithFileStats,\n} from \"@milaboratories/pl-drivers\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { notEmpty, type MiLogger } from \"@milaboratories/ts-helpers\";\nimport type { ResourceInfo } from \"@milaboratories/pl-tree\";\nimport { text } from \"node:stream/consumers\";\nimport path from \"node:path\";\nimport fs from \"node:fs/promises\";\nimport os from \"node:os\";\nimport { randomBytes } from \"node:crypto\";\nimport type { StorageEntry } from \"@milaboratories/pl-model-common\";\n\nexport interface TemplateReport {\n status: \"ok\" | \"warn\" | \"failed\";\n message: string;\n}\n\n/** Uploads `hello-world` template and checks the output is correct. */\nexport async function uploadTemplate(\n logger: MiLogger,\n pl: PlClient,\n name: string,\n): Promise<TemplateReport> {\n try {\n const gotGreeting = await runUploadTemplate(logger, pl, name);\n if (gotGreeting !== `Hello, ${name}`) {\n return {\n status: \"failed\",\n message: `Template uploading failed: expected: ${name}, got: ${gotGreeting}`,\n };\n }\n\n return { status: \"ok\", message: `Template uploading succeeded: ${gotGreeting}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `Template uploading failed: error occurred: ${e}` };\n }\n}\n\nexport async function runUploadTemplate(\n logger: MiLogger,\n pl: PlClient,\n name: string,\n): Promise<string> {\n const outputs = await runTemplate(\n pl,\n SdkTemplates[\"check_network.upload_template\"],\n true,\n (tx) => ({\n name: tx.createValue(Pl.JsonObject, JSON.stringify(name)),\n }),\n [\"greeting\"],\n );\n\n try {\n return JSON.parse(notEmpty((await getFieldValue(pl, outputs.greeting)).data?.toString()));\n } finally {\n if (outputs != undefined) {\n await deleteFields(pl, Object.values(outputs));\n }\n }\n}\n\n/** Uploads a file to the backend and checks the output is a Blob resource. */\nexport async function uploadFile(\n logger: MiLogger,\n signer: Signer,\n lsDriver: LsDriver,\n uploadClient: ClientUpload,\n pl: PlClient,\n filePath: string,\n): Promise<TemplateReport> {\n try {\n const gotBlob = await runUploadFile(logger, signer, lsDriver, uploadClient, pl, filePath);\n\n if (gotBlob.type.name !== \"Blob\") {\n return { status: \"failed\", message: `File uploading failed: ${gotBlob.type.name}` };\n }\n\n return { status: \"ok\", message: `File uploading succeeded: ${gotBlob.type.name}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `File uploading failed: error occurred: ${e}` };\n }\n}\n\nexport async function runUploadFile(\n logger: MiLogger,\n signer: Signer,\n lsDriver: LsDriver,\n uploadClient: ClientUpload,\n pl: PlClient,\n filePath: string,\n): Promise<ResourceInfo> {\n const handle = await lsDriver.getLocalFileHandle(filePath);\n const result = await runTemplate(\n pl,\n SdkTemplates[\"check_network.upload_blob\"],\n true,\n (tx) => ({\n file: tx.createValue(Pl.JsonObject, JSON.stringify(handle)),\n }),\n [\"progress\", \"file\"],\n );\n\n try {\n const progress = await getFieldValue(pl, result.progress);\n\n if (isUpload(progress)) {\n const uploadData = ImportFileHandleUploadData.parse(\n JSON.parse(notEmpty(progress.data?.toString())),\n );\n const isUploadSignMatch = isSignMatch(signer, uploadData.localPath, uploadData.pathSignature);\n\n if (isUploadSignMatch) {\n await uploadBlob(logger, uploadClient, progress, uploadData, () => false, {\n nPartsWithThisUploadSpeed: 10,\n nPartsToIncreaseUpload: 10,\n currentSpeed: 10,\n maxSpeed: 10,\n });\n }\n }\n\n return await getFieldValue(pl, result.file);\n } finally {\n await deleteFields(pl, Object.values(result));\n }\n}\n\n/** Uploads a file to the backend and then tries to download it back. */\nexport async function downloadFile(\n logger: MiLogger,\n pl: PlClient,\n lsDriver: LsDriver,\n uploadClient: ClientUpload,\n downloadClient: ClientDownload,\n filePath: string,\n fileContent: string,\n): Promise<TemplateReport> {\n try {\n const gotFileContent = await runDownloadFile(\n logger,\n pl,\n lsDriver,\n uploadClient,\n downloadClient,\n filePath,\n );\n\n if (gotFileContent !== fileContent) {\n return {\n status: \"failed\",\n message: `File downloading failed: expected: ${fileContent}, got: ${gotFileContent}`,\n };\n }\n return { status: \"ok\", message: `File downloading succeeded: ${gotFileContent}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `File downloading failed: error occurred: ${e}` };\n }\n}\n\nexport async function runDownloadFile(\n logger: MiLogger,\n pl: PlClient,\n lsDriver: LsDriver,\n uploadClient: ClientUpload,\n downloadClient: ClientDownload,\n filePath: string,\n) {\n const handle = await lsDriver.getLocalFileHandle(filePath);\n\n const outputs = await runTemplate(\n pl,\n SdkTemplates[\"check_network.download_blob\"],\n true,\n (tx) => ({ file: tx.createValue(Pl.JsonObject, JSON.stringify(handle)) }),\n [\"progress\", \"file\"],\n );\n\n try {\n const progress = await getFieldValue(pl, outputs.progress);\n\n await uploadBlob(\n logger,\n uploadClient,\n progress,\n ImportFileHandleUploadData.parse(JSON.parse(notEmpty(progress.data?.toString()))),\n () => false,\n {\n nPartsWithThisUploadSpeed: 1,\n nPartsToIncreaseUpload: 1,\n currentSpeed: 1,\n maxSpeed: 1,\n },\n );\n\n const fileInfo = await getFieldValue(pl, outputs.file);\n\n return await downloadClient.withBlobContent(\n fileInfo,\n {},\n {},\n async (content) => await text(content),\n );\n } finally {\n await deleteFields(pl, Object.values(outputs));\n }\n}\n\n/** Runs Go's hello-world binary. */\nexport async function softwareCheck(pl: PlClient): Promise<TemplateReport> {\n try {\n const gotGreeting = await runSoftware(pl);\n\n if (gotGreeting !== \"Hello from go binary\\n\") {\n return { status: \"failed\", message: `Software check failed: got: ${gotGreeting}` };\n }\n return { status: \"ok\", message: `Software check succeeded: ${gotGreeting}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `Software check failed: error occurred: ${e}` };\n }\n}\n\nexport async function runSoftware(pl: PlClient): Promise<string> {\n const result = await runTemplate(\n pl,\n SdkTemplates[\"check_network.run_hello_world\"],\n true,\n (_: PlTransaction) => ({}),\n [\"greeting\"],\n );\n\n try {\n return notEmpty((await getFieldValue(pl, result.greeting)).data?.toString());\n } finally {\n await deleteFields(pl, Object.values(result));\n }\n}\n\n/** Runs Python hello-world. */\nexport async function pythonSoftware(pl: PlClient, name: string): Promise<TemplateReport> {\n try {\n const gotGreeting = await runPythonSoftware(pl, name);\n\n if (gotGreeting !== `Hello, ${name}!\\n`) {\n return { status: \"failed\", message: `Python software check failed: got: ${gotGreeting}` };\n }\n return { status: \"ok\", message: `Python software check succeeded: ${gotGreeting}` };\n } catch (e: unknown) {\n return { status: \"failed\", message: `Python software check failed: error occurred: ${e}` };\n }\n}\n\nexport async function runPythonSoftware(pl: PlClient, name: string): Promise<string> {\n const result = await runTemplate(\n pl,\n SdkTemplates[\"check_network.run_hello_world_py\"],\n true,\n (tx) => ({ name: tx.createValue(Pl.JsonObject, JSON.stringify(name)) }),\n [\"greeting\"],\n );\n\n try {\n return notEmpty((await getFieldValue(pl, result.greeting)).data?.toString());\n } finally {\n await deleteFields(pl, Object.values(result));\n }\n}\n\n/** Tries to download a file from every storage. */\nexport async function downloadFromEveryStorage(\n logger: MiLogger,\n pl: PlClient,\n lsDriver: LsDriver,\n ops: {\n minLsRequests: number;\n bytesLimit: number;\n minFileSize: number;\n maxFileSize: number;\n nFilesToCheck: number;\n },\n): Promise<Record<string, TemplateReport>> {\n try {\n const storages = await lsDriver.getStorageList();\n const results: Record<string, TemplateReport> = {};\n\n for (const storage of storages) {\n const result = await chooseFile(\n lsDriver,\n storage,\n ops.nFilesToCheck,\n ops.minFileSize,\n ops.maxFileSize,\n ops.minLsRequests,\n );\n if (result.file === undefined) {\n results[storage.id] = {\n status: \"warn\",\n message:\n `No file between ${ops.minFileSize} and ${ops.maxFileSize} bytes ` +\n `found in storage ${storage.name}, checked ${result.nCheckedFiles} files, ` +\n `did ${result.nLsRequests} ls requests`,\n };\n continue;\n }\n\n logger.info(`Downloading file ${JSON.stringify(result)} from storage ${storage.name}`);\n const outputs = await runTemplate(\n pl,\n SdkTemplates[\"check_network.create_workdir_from_storage\"],\n true,\n (tx) => ({\n file: tx.createValue(\n Pl.JsonObject,\n JSON.stringify((result.file as { handle: string }).handle),\n ),\n }),\n [\"workdirTypeName\"],\n );\n\n try {\n const workdirTypeName = JSON.parse(\n Buffer.from((await getFieldValue(pl, outputs.workdirTypeName)).data!).toString(),\n ) as string;\n\n if (workdirTypeName?.startsWith(\"WorkingDirectory\")) {\n results[storage.id] = {\n status: \"ok\",\n message:\n `Workdir creation succeeded, size of file: ${result.file?.size}, ` +\n `checked ${result.nCheckedFiles} files, did ${result.nLsRequests} ls requests`,\n };\n } else {\n results[storage.id] = {\n status: \"failed\",\n message:\n `Workdir creation failed: ${workdirTypeName}, size of file: ${result.file?.size}, ` +\n `checked ${result.nCheckedFiles} files, did ${result.nLsRequests} ls requests`,\n };\n }\n } finally {\n await deleteFields(pl, Object.values(outputs));\n }\n }\n\n return results;\n } catch (e: unknown) {\n return {\n unknown: {\n status: \"failed\",\n message: `Download from every storage failed: error occurred: ${e}`,\n },\n };\n }\n}\n\n/** Chooses a random file from the storage in a size range.\n * If we couldn't find a normal-sized file, we'll return a small file to check at least something.\n */\nexport async function chooseFile(\n lsDriver: LsDriver,\n storage: StorageEntry,\n limit: number,\n minSize: number,\n maxSize: number,\n minLsRequests: number,\n): Promise<{\n file: LsEntryWithFileStats | undefined;\n nLsRequests: number;\n nCheckedFiles: number;\n}> {\n const files = listFilesSequence(lsDriver, storage, \"\", 0);\n\n // return small file in case we don't have many normal-sized files.\n // While we'll download only a small range of bytes from the file,\n // we don't want to return a big file in case the underlying S3 doesn't support range requests.\n let smallFile: LsEntryWithFileStats | undefined;\n let nCheckedFiles = 0;\n let maxNLsRequests = 0;\n\n for await (const { file, nLsRequests } of files) {\n maxNLsRequests = Math.max(maxNLsRequests, nLsRequests);\n\n if (nCheckedFiles >= limit && maxNLsRequests > minLsRequests) {\n // we reached a limit on both the number of files and the number of ls requests.\n return { file: smallFile, nLsRequests: maxNLsRequests, nCheckedFiles };\n }\n nCheckedFiles++;\n if (minSize <= file.size && file.size <= maxSize) {\n return { file, nLsRequests: maxNLsRequests, nCheckedFiles };\n } else if (file.size < minSize) {\n smallFile = file;\n }\n }\n\n return { file: smallFile, nLsRequests: maxNLsRequests, nCheckedFiles };\n}\n\n/** Deep-first search for files in the storage. */\nexport async function* listFilesSequence(\n lsDriver: LsDriver,\n storage: StorageEntry,\n parent: string,\n nLsRequests: number,\n): AsyncGenerator<{ file: LsEntryWithFileStats; nLsRequests: number }, void, unknown> {\n nLsRequests++;\n const files = await lsDriver.listRemoteFilesWithFileStats(storage.handle, parent);\n\n for (const file of files.entries) {\n if (file.type === \"file\" && file.fullPath.startsWith(parent)) {\n yield {\n file,\n nLsRequests,\n };\n } else if (file.type === \"dir\") {\n for await (const nestedFile of listFilesSequence(\n lsDriver,\n storage,\n file.fullPath,\n nLsRequests,\n )) {\n nLsRequests = Math.max(nestedFile.nLsRequests, nLsRequests);\n yield nestedFile;\n }\n }\n }\n}\n\n/** Creates a big temporary file with random content. */\nexport async function createBigTempFile(): Promise<{ filePath: string }> {\n const filePath = path.join(os.tmpdir(), `check-network-big-temp-${Date.now()}.bin`);\n const fileSize = 20 * 1024 * 1024; // 20 MiB\n\n const fileContent = randomBytes(fileSize);\n\n await fs.appendFile(filePath, fileContent);\n\n return { filePath };\n}\n\n/** Creates a temporarly file we could use for uploading and downloading. */\nexport async function createTempFile(): Promise<{ filePath: string; fileContent: string }> {\n const filePath = path.join(os.tmpdir(), `check-network-temp-${Date.now()}.txt`);\n\n const fileContent = \"Hello, world! \" + new Date().toISOString();\n await fs.writeFile(filePath, fileContent);\n\n return { filePath, fileContent };\n}\n\n/** Creates a template and RenderTemplate resources, gets all resources from outputs.\n * Throws a error if any of the outputs failed.\n */\nasync function runTemplate(\n client: PlClient,\n tpl: TemplateSpecAny,\n ephemeral: boolean,\n inputs: (tx: PlTransaction) => Pl.PlRecord,\n outputs: string[],\n): Promise<Record<string, FieldId>> {\n return await client.withWriteTx(\"TemplateRender\", async (tx) => {\n const preparedTemplate = await prepareTemplateSpec(tpl);\n const tplResource = loadTemplate(tx, preparedTemplate);\n\n const outputFields: Record<string, FieldRef> = createRenderTemplate(\n tx,\n tplResource,\n ephemeral,\n inputs(tx),\n outputs,\n );\n\n const outputsIds: Record<string, FieldId> = {};\n\n for (const output of outputs) {\n const fieldRef = field(client.clientRoot, output) as FieldId;\n tx.createField(fieldRef, \"Dynamic\", outputFields[output]);\n outputsIds[output] = await toGlobalFieldId(fieldRef);\n }\n\n await tx.commit();\n\n return outputsIds;\n });\n}\n\n/** Gets a resource from field's value or throws a error. */\nasync function getFieldValue(client: PlClient, fieldId: FieldId): Promise<ResourceData> {\n // We could also do polling with pl-tree, but it seemed like an overkill,\n // that's why we have a simple polling here.\n\n return await poll(client, async (tx) => {\n const field = await tx.tx.getField(fieldId);\n if (isNotNullSignedResourceId(field.error)) {\n const err = await tx.tx.getResourceData(field.error, true);\n throw new Error(`getFieldValue of \"${fieldId.fieldName}\" field failed: ${err.data}`);\n }\n\n if (isNullSignedResourceId(field.value)) {\n throw new ContinuePolling();\n }\n\n return await tx.tx.getResourceData(field.value, true);\n });\n}\n\nasync function deleteFields(client: PlClient, fieldIds: FieldId[]) {\n await client.withWriteTx(\"DeleteFields\", async (tx) => {\n for (const fieldId of fieldIds) {\n tx.resetField(fieldId);\n }\n await tx.commit();\n });\n}\n"],"mappings":"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"}