@milaboratories/pl-middle-layer 1.64.36 → 1.64.38
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.
- package/dist/_virtual/{_@oxc-project_runtime@0.123.0/helpers → _@oxc-project_runtime@0.137.0/helpers/esm}/usingCtx.cjs +2 -2
- package/dist/_virtual/{_@oxc-project_runtime@0.123.0/helpers → _@oxc-project_runtime@0.137.0/helpers/esm}/usingCtx.js +2 -2
- package/dist/block_registry/index.cjs +11 -3
- package/dist/block_registry/index.d.ts +3 -2
- package/dist/block_registry/index.js +4 -3
- package/dist/block_registry/registry-v2-provider.cjs +1 -1
- package/dist/block_registry/registry-v2-provider.cjs.map +1 -1
- package/dist/block_registry/registry-v2-provider.d.ts.map +1 -1
- package/dist/block_registry/registry-v2-provider.js +1 -0
- package/dist/block_registry/registry-v2-provider.js.map +1 -1
- package/dist/block_registry/registry.cjs +20 -3
- package/dist/block_registry/registry.cjs.map +1 -1
- package/dist/block_registry/registry.d.ts +7 -1
- package/dist/block_registry/registry.d.ts.map +1 -1
- package/dist/block_registry/registry.js +17 -1
- package/dist/block_registry/registry.js.map +1 -1
- package/dist/block_registry/watcher.cjs +21 -2
- package/dist/block_registry/watcher.cjs.map +1 -1
- package/dist/block_registry/watcher.d.ts +1 -1
- package/dist/block_registry/watcher.d.ts.map +1 -1
- package/dist/block_registry/watcher.js +21 -3
- package/dist/block_registry/watcher.js.map +1 -1
- package/dist/block_registry/well_known_registries.cjs.map +1 -1
- package/dist/block_registry/well_known_registries.d.ts.map +1 -1
- package/dist/block_registry/well_known_registries.js.map +1 -1
- package/dist/cfg_render/executor.cjs +2 -2
- package/dist/cfg_render/executor.cjs.map +1 -1
- package/dist/cfg_render/executor.js.map +1 -1
- package/dist/cfg_render/renderer.cjs +0 -1
- package/dist/cfg_render/renderer.cjs.map +1 -1
- package/dist/cfg_render/renderer.js.map +1 -1
- package/dist/cfg_render/traverse.cjs +0 -1
- package/dist/cfg_render/traverse.cjs.map +1 -1
- package/dist/cfg_render/traverse.js.map +1 -1
- package/dist/cfg_render/util.cjs.map +1 -1
- package/dist/cfg_render/util.js.map +1 -1
- package/dist/debug/index.cjs +1 -0
- package/dist/debug/index.cjs.map +1 -1
- package/dist/debug/index.js +1 -1
- package/dist/debug/index.js.map +1 -1
- package/dist/dev_env/index.cjs +9 -1
- package/dist/dev_env/index.cjs.map +1 -1
- package/dist/dev_env/index.js +1 -1
- package/dist/dev_env/index.js.map +1 -1
- package/dist/dev_env/util.cjs +1 -1
- package/dist/dev_env/util.cjs.map +1 -1
- package/dist/dev_env/util.js.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/js_render/computable_context.cjs +8 -5
- package/dist/js_render/computable_context.cjs.map +1 -1
- package/dist/js_render/computable_context.js +5 -1
- package/dist/js_render/computable_context.js.map +1 -1
- package/dist/js_render/context.cjs +4 -1
- package/dist/js_render/context.cjs.map +1 -1
- package/dist/js_render/context.js +4 -1
- package/dist/js_render/context.js.map +1 -1
- package/dist/js_render/index.cjs +0 -1
- package/dist/js_render/index.cjs.map +1 -1
- package/dist/js_render/index.js.map +1 -1
- package/dist/js_render/service_injectors.cjs +2 -3
- package/dist/js_render/service_injectors.cjs.map +1 -1
- package/dist/js_render/service_injectors.js +1 -1
- package/dist/js_render/service_injectors.js.map +1 -1
- package/dist/middle_layer/active_cfg.cjs +0 -1
- package/dist/middle_layer/active_cfg.cjs.map +1 -1
- package/dist/middle_layer/active_cfg.js.map +1 -1
- package/dist/middle_layer/block.cjs +0 -1
- package/dist/middle_layer/block.cjs.map +1 -1
- package/dist/middle_layer/block.js.map +1 -1
- package/dist/middle_layer/block_ctx.cjs +0 -1
- package/dist/middle_layer/block_ctx.cjs.map +1 -1
- package/dist/middle_layer/block_ctx.js.map +1 -1
- package/dist/middle_layer/block_ctx_unsafe.cjs.map +1 -1
- package/dist/middle_layer/block_ctx_unsafe.js.map +1 -1
- package/dist/middle_layer/driver_kit.cjs +0 -1
- package/dist/middle_layer/driver_kit.cjs.map +1 -1
- package/dist/middle_layer/driver_kit.js.map +1 -1
- package/dist/middle_layer/frontend_path.cjs +5 -1
- package/dist/middle_layer/frontend_path.cjs.map +1 -1
- package/dist/middle_layer/frontend_path.js +6 -1
- package/dist/middle_layer/frontend_path.js.map +1 -1
- package/dist/middle_layer/index.cjs +13 -5
- package/dist/middle_layer/index.d.ts +2 -1
- package/dist/middle_layer/index.js +6 -5
- package/dist/middle_layer/middle_layer.cjs +7 -1
- package/dist/middle_layer/middle_layer.cjs.map +1 -1
- package/dist/middle_layer/middle_layer.d.ts.map +1 -1
- package/dist/middle_layer/middle_layer.js +7 -0
- package/dist/middle_layer/middle_layer.js.map +1 -1
- package/dist/middle_layer/navigation_states.cjs +0 -1
- package/dist/middle_layer/navigation_states.cjs.map +1 -1
- package/dist/middle_layer/navigation_states.js.map +1 -1
- package/dist/middle_layer/ops.cjs +1 -1
- package/dist/middle_layer/ops.cjs.map +1 -1
- package/dist/middle_layer/ops.d.ts.map +1 -1
- package/dist/middle_layer/ops.js.map +1 -1
- package/dist/middle_layer/project.cjs +9 -2
- package/dist/middle_layer/project.cjs.map +1 -1
- package/dist/middle_layer/project.d.ts.map +1 -1
- package/dist/middle_layer/project.js +5 -1
- package/dist/middle_layer/project.js.map +1 -1
- package/dist/middle_layer/project_list.cjs +2 -1
- package/dist/middle_layer/project_list.cjs.map +1 -1
- package/dist/middle_layer/project_list.js +1 -1
- package/dist/middle_layer/project_list.js.map +1 -1
- package/dist/middle_layer/project_overview.cjs +0 -1
- package/dist/middle_layer/project_overview.cjs.map +1 -1
- package/dist/middle_layer/project_overview.js.map +1 -1
- package/dist/middle_layer/project_overview_light.cjs +0 -1
- package/dist/middle_layer/project_overview_light.cjs.map +1 -1
- package/dist/middle_layer/project_overview_light.js.map +1 -1
- package/dist/middle_layer/render.cjs +0 -1
- package/dist/middle_layer/render.cjs.map +1 -1
- package/dist/middle_layer/render.js.map +1 -1
- package/dist/middle_layer/types.d.ts.map +1 -1
- package/dist/middle_layer/util.cjs +0 -1
- package/dist/middle_layer/util.cjs.map +1 -1
- package/dist/middle_layer/util.js.map +1 -1
- package/dist/model/args.cjs +1 -1
- package/dist/model/args.cjs.map +1 -1
- package/dist/model/args.js +1 -1
- package/dist/model/args.js.map +1 -1
- package/dist/model/block_pack_spec.cjs +5 -0
- package/dist/model/block_pack_spec.cjs.map +1 -1
- package/dist/model/block_pack_spec.d.ts +19 -2
- package/dist/model/block_pack_spec.d.ts.map +1 -1
- package/dist/model/block_pack_spec.js +5 -1
- package/dist/model/block_pack_spec.js.map +1 -1
- package/dist/model/frontend.d.ts.map +1 -1
- package/dist/model/index.d.ts +2 -1
- package/dist/model/project_helper.cjs +2 -1
- package/dist/model/project_helper.cjs.map +1 -1
- package/dist/model/project_helper.d.ts +1 -1
- package/dist/model/project_helper.d.ts.map +1 -1
- package/dist/model/project_helper.js +2 -0
- package/dist/model/project_helper.js.map +1 -1
- package/dist/model/project_model.cjs +2 -1
- package/dist/model/project_model.cjs.map +1 -1
- package/dist/model/project_model.d.ts.map +1 -1
- package/dist/model/project_model.js +2 -2
- package/dist/model/project_model.js.map +1 -1
- package/dist/model/project_model_util.cjs +4 -3
- package/dist/model/project_model_util.cjs.map +1 -1
- package/dist/model/project_model_util.js +4 -4
- package/dist/model/project_model_util.js.map +1 -1
- package/dist/model/template_spec.d.ts.map +1 -1
- package/dist/mutator/block-pack/block_pack.cjs +41 -4
- package/dist/mutator/block-pack/block_pack.cjs.map +1 -1
- package/dist/mutator/block-pack/block_pack.d.ts.map +1 -1
- package/dist/mutator/block-pack/block_pack.js +40 -4
- package/dist/mutator/block-pack/block_pack.js.map +1 -1
- package/dist/mutator/block-pack/frontend.cjs +5 -1
- package/dist/mutator/block-pack/frontend.cjs.map +1 -1
- package/dist/mutator/block-pack/frontend.js +6 -1
- package/dist/mutator/block-pack/frontend.js.map +1 -1
- package/dist/mutator/block-pack/required_capabilities.cjs +0 -1
- package/dist/mutator/block-pack/required_capabilities.cjs.map +1 -1
- package/dist/mutator/block-pack/required_capabilities.js.map +1 -1
- package/dist/mutator/context_export.cjs +0 -1
- package/dist/mutator/context_export.cjs.map +1 -1
- package/dist/mutator/context_export.js.map +1 -1
- package/dist/mutator/migration.cjs +0 -1
- package/dist/mutator/migration.cjs.map +1 -1
- package/dist/mutator/migration.js.map +1 -1
- package/dist/mutator/project.cjs +22 -4
- package/dist/mutator/project.cjs.map +1 -1
- package/dist/mutator/project.js +21 -4
- package/dist/mutator/project.js.map +1 -1
- package/dist/mutator/template/direct_template_loader.cjs +0 -1
- package/dist/mutator/template/direct_template_loader.cjs.map +1 -1
- package/dist/mutator/template/direct_template_loader.js.map +1 -1
- package/dist/mutator/template/direct_template_loader_v3.cjs +0 -1
- package/dist/mutator/template/direct_template_loader_v3.cjs.map +1 -1
- package/dist/mutator/template/direct_template_loader_v3.js.map +1 -1
- package/dist/mutator/template/render_block.cjs +4 -1
- package/dist/mutator/template/render_block.cjs.map +1 -1
- package/dist/mutator/template/render_block.js +1 -1
- package/dist/mutator/template/render_block.js.map +1 -1
- package/dist/mutator/template/render_template.cjs +0 -1
- package/dist/mutator/template/render_template.cjs.map +1 -1
- package/dist/mutator/template/render_template.d.ts.map +1 -1
- package/dist/mutator/template/render_template.js.map +1 -1
- package/dist/mutator/template/template_cache.cjs +6 -3
- package/dist/mutator/template/template_cache.cjs.map +1 -1
- package/dist/mutator/template/template_cache.d.ts +1 -2
- package/dist/mutator/template/template_cache.d.ts.map +1 -1
- package/dist/mutator/template/template_cache.js +6 -2
- package/dist/mutator/template/template_cache.js.map +1 -1
- package/dist/mutator/template/template_loading.cjs +1 -1
- package/dist/mutator/template/template_loading.cjs.map +1 -1
- package/dist/mutator/template/template_loading.d.ts.map +1 -1
- package/dist/mutator/template/template_loading.js.map +1 -1
- package/dist/network_check/network_check.cjs +0 -1
- package/dist/network_check/network_check.cjs.map +1 -1
- package/dist/network_check/network_check.d.ts.map +1 -1
- package/dist/network_check/network_check.js.map +1 -1
- package/dist/network_check/pings.cjs +3 -1
- package/dist/network_check/pings.cjs.map +1 -1
- package/dist/network_check/pings.js +1 -1
- package/dist/network_check/pings.js.map +1 -1
- package/dist/network_check/template.cjs +10 -3
- package/dist/network_check/template.cjs.map +1 -1
- package/dist/network_check/template.js +1 -1
- package/dist/network_check/template.js.map +1 -1
- package/dist/pool/data.cjs +12 -1
- package/dist/pool/data.cjs.map +1 -1
- package/dist/pool/data.d.ts.map +1 -1
- package/dist/pool/data.js +3 -1
- package/dist/pool/data.js.map +1 -1
- package/dist/pool/driver.cjs +8 -1
- package/dist/pool/driver.cjs.map +1 -1
- package/dist/pool/driver.d.ts.map +1 -1
- package/dist/pool/driver.js +7 -0
- package/dist/pool/driver.js.map +1 -1
- package/dist/pool/index.cjs +5 -2
- package/dist/pool/index.d.ts +2 -1
- package/dist/pool/index.js +3 -2
- package/dist/pool/p_object_collection.cjs +1 -1
- package/dist/pool/p_object_collection.cjs.map +1 -1
- package/dist/pool/p_object_collection.js.map +1 -1
- package/dist/pool/result_pool.cjs +2 -1
- package/dist/pool/result_pool.cjs.map +1 -1
- package/dist/pool/result_pool.js +2 -0
- package/dist/pool/result_pool.js.map +1 -1
- package/dist/service_factories.cjs +0 -1
- package/dist/service_factories.cjs.map +1 -1
- package/dist/service_factories.js.map +1 -1
- package/dist/worker/WorkerManager.cjs +3 -2
- package/dist/worker/WorkerManager.cjs.map +1 -1
- package/dist/worker/WorkerManager.js +2 -1
- package/dist/worker/WorkerManager.js.map +1 -1
- package/dist/worker/worker.cjs +0 -1
- package/dist/worker/worker.cjs.map +1 -1
- package/dist/worker/worker.js.map +1 -1
- package/dist/worker/workerApi.cjs +0 -1
- package/dist/worker/workerApi.cjs.map +1 -1
- package/dist/worker/workerApi.js.map +1 -1
- package/package.json +10 -10
- package/src/block_registry/registry.ts +19 -0
- package/src/block_registry/watcher.ts +25 -2
- package/src/middle_layer/frontend_path.ts +22 -2
- package/src/model/block_pack_spec.ts +24 -1
- package/src/mutator/block-pack/block_pack.ts +61 -2
- package/src/mutator/block-pack/frontend.ts +20 -2
- package/src/mutator/template/template_cache.ts +10 -2
|
@@ -198,6 +198,7 @@ function flattenV3Tree(data) {
|
|
|
198
198
|
return hash;
|
|
199
199
|
}
|
|
200
200
|
function processTemplate(tpl) {
|
|
201
|
+
const version = tpl.version ?? "0.0.0";
|
|
201
202
|
const childHashes = [];
|
|
202
203
|
const children = [];
|
|
203
204
|
for (const [libId, lib] of Object.entries(tpl.libs ?? {})) {
|
|
@@ -240,7 +241,7 @@ function flattenV3Tree(data) {
|
|
|
240
241
|
hash: h
|
|
241
242
|
});
|
|
242
243
|
}
|
|
243
|
-
const h = createHash("sha256").update(PlTemplateV1.type.name).update(PlTemplateV1.type.version).update(tpl.hashOverride ?? "no-override").update(tpl.name).update(
|
|
244
|
+
const h = createHash("sha256").update(PlTemplateV1.type.name).update(PlTemplateV1.type.version).update(tpl.hashOverride ?? "no-override").update(tpl.name).update(version).update(tpl.sourceHash);
|
|
244
245
|
for (const child of children) h.update("child:" + child.fieldName + ":" + child.hash);
|
|
245
246
|
const hash = h.digest("hex");
|
|
246
247
|
if (seen.has(hash)) return hash;
|
|
@@ -249,7 +250,10 @@ function flattenV3Tree(data) {
|
|
|
249
250
|
hash,
|
|
250
251
|
create: (tx, childRefs) => {
|
|
251
252
|
const sourceCode = getSourceCode(tpl.name, sources, tpl.sourceHash);
|
|
252
|
-
const tplRef = tx.createStruct(PlTemplateV1.type, JSON.stringify(PlTemplateV1.fromV3Data(
|
|
253
|
+
const tplRef = tx.createStruct(PlTemplateV1.type, JSON.stringify(PlTemplateV1.fromV3Data({
|
|
254
|
+
...tpl,
|
|
255
|
+
version
|
|
256
|
+
}, sourceCode).data));
|
|
253
257
|
for (const child of children) {
|
|
254
258
|
const fld = field(tplRef, child.fieldName);
|
|
255
259
|
tx.createField(fld, "Input");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template_cache.js","names":[],"sources":["../../../src/mutator/template/template_cache.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type {\n AnyResourceRef,\n PlClient,\n PlTransaction,\n SignedResourceId,\n ResourceRef,\n} from \"@milaboratories/pl-client\";\nimport {\n ensureSignedResourceIdNotNull,\n field,\n resourceType,\n toGlobalResourceId,\n} from \"@milaboratories/pl-client\";\nimport {\n parseTemplate,\n PlTemplateLibV1,\n PlTemplateOverrideV1,\n PlTemplateSoftwareV1,\n PlTemplateV1,\n PlWasmV1,\n} from \"@milaboratories/pl-model-backend\";\nimport type {\n CompiledTemplateV3,\n TemplateData,\n TemplateDataV3,\n TemplateLibData,\n TemplateLibDataV3,\n TemplateSoftwareData,\n TemplateSoftwareDataV3,\n TemplateWasmDataV3,\n} from \"@milaboratories/pl-model-backend\";\nimport { notEmpty } from \"@milaboratories/ts-helpers\";\nimport type { BlockPackSpecPrepared } from \"../../model\";\nimport type { TemplateSpecPrepared } from \"../../model/template_spec\";\nimport { getDebugFlags } from \"../../debug\";\n\nexport const TemplateCacheType = resourceType(\"TemplateCache\", \"1\");\n\nexport const TemplateCacheFieldName = \"__templateCache\";\nconst BATCH_SIZE = 50;\n/** @internal exported for testing */\nexport const GC_ACCESS_THRESHOLD = 30;\n/** @internal exported for testing */\nexport const GC_MAX_ENTRIES = 3000;\n/** @internal exported for testing */\nexport const ACCESS_COUNT_KEY = \"_accessCount\";\n/** @internal exported for testing */\nexport const ACCESS_KEY_PREFIX = \"access_\";\n\n// ─── Stats ───────────────────────────────────────────────────────────────────\n\nexport type TemplateCacheStat = {\n totalMs: number;\n flattenMs: number;\n cacheInitMs: number;\n materializeMs: number;\n totalNodes: number;\n cacheHits: number;\n cacheMisses: number;\n batchCount: number;\n happyPath: boolean;\n gcTriggered: boolean;\n retries: number;\n templateFormat: string;\n};\n\nfunction initialStat(): TemplateCacheStat {\n return {\n totalMs: 0,\n flattenMs: 0,\n cacheInitMs: 0,\n materializeMs: 0,\n totalNodes: 0,\n cacheHits: 0,\n cacheMisses: 0,\n batchCount: 0,\n happyPath: false,\n gcTriggered: false,\n retries: 0,\n templateFormat: \"\",\n };\n}\n\n// ─── Tree node abstraction ───────────────────────────────────────────────────\n\ninterface CacheableNode {\n /** SHA-256 content hash (includes all descendant content) */\n hash: string;\n /** Creates this node's resource in a transaction.\n * childRefs maps child hash → already-resolved ResourceRef or SignedResourceId */\n create: (tx: PlTransaction, childRefs: ReadonlyMap<string, AnyResourceRef>) => ResourceRef;\n /** Hashes of direct child nodes this node depends on */\n childHashes: string[];\n}\n\n// ─── Hash computation helpers ────────────────────────────────────────────────\n\nfunction getSourceCode(name: string, sources: Record<string, string>, sourceHash: string): string {\n return notEmpty(\n sources[sourceHash],\n `trying to get \"${name}\" source: sources map doesn't contain source hash ${sourceHash}`,\n );\n}\n\n/**\n * Bottom-up hash composition: each node hashes its OWN content + child hash STRINGS.\n * This means each unique node is hashed exactly once → O(n) instead of O(n * depth).\n */\n\n// V2 leaf hashes (libs, software — no children)\n\nfunction hashLibV2(lib: TemplateLibData): string {\n return createHash(\"sha256\")\n .update(PlTemplateLibV1.type.name)\n .update(PlTemplateLibV1.type.version)\n .update(lib.name)\n .update(lib.version)\n .update(lib.src)\n .digest(\"hex\");\n}\n\nfunction hashSoftwareV2(sw: TemplateSoftwareData): string {\n return createHash(\"sha256\")\n .update(PlTemplateSoftwareV1.type.name)\n .update(PlTemplateSoftwareV1.type.version)\n .update(sw.name)\n .update(sw.version)\n .update(sw.src)\n .digest(\"hex\");\n}\n\n// V3 leaf hashes — use sourceHash directly instead of resolving source content\n\nfunction hashLibV3(lib: TemplateLibDataV3): string {\n return createHash(\"sha256\")\n .update(PlTemplateLibV1.type.name)\n .update(PlTemplateLibV1.type.version)\n .update(lib.name)\n .update(lib.version)\n .update(lib.sourceHash)\n .digest(\"hex\");\n}\n\nfunction hashSoftwareV3(sw: TemplateSoftwareDataV3): string {\n return createHash(\"sha256\")\n .update(PlTemplateSoftwareV1.type.name)\n .update(PlTemplateSoftwareV1.type.version)\n .update(sw.name)\n .update(sw.version)\n .update(sw.sourceHash)\n .digest(\"hex\");\n}\n\nfunction hashWasmV3(wasm: TemplateWasmDataV3): string {\n return createHash(\"sha256\")\n .update(PlWasmV1.type.name)\n .update(PlWasmV1.type.version)\n .update(wasm.name)\n .update(wasm.version)\n .update(wasm.sourceHash)\n .digest(\"hex\");\n}\n\n// ─── Tree flattening ─────────────────────────────────────────────────────────\n\nfunction flattenV2Tree(data: TemplateData): CacheableNode[] {\n const nodes: CacheableNode[] = [];\n const seen = new Set<string>();\n\n function processLib(lib: TemplateLibData): string {\n const hash = hashLibV2(lib);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) =>\n tx.createValue(\n PlTemplateLibV1.type,\n JSON.stringify(PlTemplateLibV1.fromV2Data(lib).data),\n ),\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processSoftware(sw: TemplateSoftwareData): string {\n const hash = hashSoftwareV2(sw);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) => {\n const swData = PlTemplateSoftwareV1.fromV2Data(sw);\n const ref = tx.createStruct(PlTemplateSoftwareV1.type, swData.data);\n tx.setKValue(ref, PlTemplateSoftwareV1.metaNameKey, JSON.stringify(swData.name));\n tx.lock(ref);\n return ref;\n },\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processTemplate(tpl: TemplateData): string {\n // Process children first (bottom-up) — their hashes are computed before ours\n const childHashes: string[] = [];\n const children: { fieldName: string; hash: string }[] = [];\n\n for (const [libId, lib] of Object.entries(tpl.libs ?? {})) {\n const h = processLib(lib);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.libPrefix}/${libId}`, hash: h });\n }\n for (const [swId, sw] of Object.entries(tpl.software ?? {})) {\n const h = processSoftware(sw);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.softPrefix}/${swId}`, hash: h });\n }\n for (const [swId, sw] of Object.entries(tpl.assets ?? {})) {\n const h = processSoftware(sw);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.softPrefix}/${swId}`, hash: h });\n }\n for (const [tplId, sub] of Object.entries(tpl.templates ?? {})) {\n const h = processTemplate(sub);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.tplPrefix}/${tplId}`, hash: h });\n }\n\n // Compose hash from own content + child hash strings (NOT child content)\n const h = createHash(\"sha256\")\n .update(PlTemplateV1.type.name)\n .update(PlTemplateV1.type.version)\n .update(tpl.hashOverride ?? \"no-override\")\n .update(tpl.name)\n .update(tpl.version)\n .update(tpl.src);\n for (const child of children) {\n h.update(\"child:\" + child.fieldName + \":\" + child.hash);\n }\n const hash = h.digest(\"hex\");\n\n if (seen.has(hash)) return hash;\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx, childRefs) => {\n const tplRef = tx.createStruct(\n PlTemplateV1.type,\n JSON.stringify(PlTemplateV1.fromV2Data(tpl).data),\n );\n for (const child of children) {\n const fld = field(tplRef, child.fieldName);\n tx.createField(fld, \"Input\");\n tx.setField(fld, notEmpty(childRefs.get(child.hash), `missing child ref ${child.hash}`));\n }\n tx.lock(tplRef);\n\n if (!tpl.hashOverride) return tplRef;\n\n const overrideRef = tx.createStruct(\n PlTemplateOverrideV1.type,\n JSON.stringify(PlTemplateOverrideV1.fromV2Data(tpl)),\n );\n const overrideFld = PlTemplateOverrideV1.tplField(overrideRef);\n tx.createField(overrideFld, \"Service\");\n tx.setField(overrideFld, tplRef);\n tx.lock(overrideRef);\n return overrideRef;\n },\n childHashes,\n });\n\n return hash;\n }\n\n processTemplate(data);\n return nodes;\n}\n\nfunction flattenV3Tree(data: CompiledTemplateV3): CacheableNode[] {\n const nodes: CacheableNode[] = [];\n const seen = new Set<string>();\n const sources = data.hashToSource;\n\n function processLib(lib: TemplateLibDataV3): string {\n const hash = hashLibV3(lib);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) =>\n tx.createValue(\n PlTemplateLibV1.type,\n JSON.stringify(\n PlTemplateLibV1.fromV3Data(lib, getSourceCode(lib.name, sources, lib.sourceHash))\n .data,\n ),\n ),\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processSoftware(sw: TemplateSoftwareDataV3): string {\n const hash = hashSoftwareV3(sw);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) => {\n const swData = PlTemplateSoftwareV1.fromV3Data(\n sw,\n getSourceCode(sw.name, sources, sw.sourceHash),\n );\n const ref = tx.createStruct(PlTemplateSoftwareV1.type, swData.data);\n tx.setKValue(ref, PlTemplateSoftwareV1.metaNameKey, JSON.stringify(swData.name));\n tx.lock(ref);\n return ref;\n },\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processWasm(wasm: TemplateWasmDataV3): string {\n const hash = hashWasmV3(wasm);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) =>\n tx.createValue(\n PlWasmV1.type,\n JSON.stringify(\n PlWasmV1.fromV3Data(wasm, getSourceCode(wasm.name, sources, wasm.sourceHash)).data,\n ),\n ),\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processTemplate(tpl: TemplateDataV3): string {\n // Process children first (bottom-up)\n const childHashes: string[] = [];\n const children: { fieldName: string; hash: string }[] = [];\n\n for (const [libId, lib] of Object.entries(tpl.libs ?? {})) {\n const h = processLib(lib);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.libPrefix}/${libId}`, hash: h });\n }\n for (const [swId, sw] of Object.entries(tpl.software ?? {})) {\n const h = processSoftware(sw);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.softPrefix}/${swId}`, hash: h });\n }\n for (const [swId, sw] of Object.entries(tpl.assets ?? {})) {\n const h = processSoftware(sw);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.softPrefix}/${swId}`, hash: h });\n }\n for (const [tplId, sub] of Object.entries(tpl.templates ?? {})) {\n const h = processTemplate(sub);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.tplPrefix}/${tplId}`, hash: h });\n }\n for (const [wasmId, wasm] of Object.entries(tpl.wasm ?? {})) {\n const h = processWasm(wasm);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.wasmPrefix}/${wasmId}`, hash: h });\n }\n\n // Compose hash from own content + child hash strings (NOT child content).\n // Uses sourceHash directly — it already uniquely identifies the source.\n const h = createHash(\"sha256\")\n .update(PlTemplateV1.type.name)\n .update(PlTemplateV1.type.version)\n .update(tpl.hashOverride ?? \"no-override\")\n .update(tpl.name)\n .update(tpl.version)\n .update(tpl.sourceHash);\n for (const child of children) {\n h.update(\"child:\" + child.fieldName + \":\" + child.hash);\n }\n const hash = h.digest(\"hex\");\n\n if (seen.has(hash)) return hash;\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx, childRefs) => {\n const sourceCode = getSourceCode(tpl.name, sources, tpl.sourceHash);\n const tplRef = tx.createStruct(\n PlTemplateV1.type,\n JSON.stringify(PlTemplateV1.fromV3Data(tpl, sourceCode).data),\n );\n for (const child of children) {\n const fld = field(tplRef, child.fieldName);\n tx.createField(fld, \"Input\");\n tx.setField(fld, notEmpty(childRefs.get(child.hash), `missing child ref ${child.hash}`));\n }\n tx.lock(tplRef);\n\n if (!tpl.hashOverride) return tplRef;\n\n const overrideRef = tx.createStruct(\n PlTemplateOverrideV1.type,\n JSON.stringify(PlTemplateOverrideV1.fromV3Data(tpl)),\n );\n const overrideFld = PlTemplateOverrideV1.tplField(overrideRef);\n tx.createField(overrideFld, \"Service\");\n tx.setField(overrideFld, tplRef);\n tx.lock(overrideRef);\n return overrideRef;\n },\n childHashes,\n });\n\n return hash;\n }\n\n processTemplate(data.template);\n return nodes;\n}\n\n/** Flatten template tree into a topologically ordered list of cacheable nodes (leaves first). */\nexport function flattenTemplateTree(data: TemplateData | CompiledTemplateV3): CacheableNode[] {\n if (data.type === \"pl.tengo-template.v2\") {\n return flattenV2Tree(data);\n } else {\n return flattenV3Tree(data);\n }\n}\n\n// ─── Cache operations ────────────────────────────────────────────────────────\n\n/** In-memory cache for the TemplateCache SignedResourceId per PlClient instance. */\nconst cacheRidMap = new WeakMap<PlClient, SignedResourceId>();\n\n/** Clear the in-memory cacheRid entry (call on errors referencing the cache resource). */\nexport function invalidateTemplateCacheId(pl: PlClient): void {\n cacheRidMap.delete(pl);\n}\n\n/** Find or create the TemplateCache/1 resource on user root. */\nexport async function getOrCreateTemplateCache(pl: PlClient): Promise<SignedResourceId> {\n // Check in-memory cache first (0ms after first call)\n const cached = cacheRidMap.get(pl);\n if (cached !== undefined) return cached;\n\n // Try read-only check\n const existing = await pl.withReadTx(\"templateCache:check\", async (tx) => {\n const fd = await tx.getFieldIfExists(field(pl.clientRoot, TemplateCacheFieldName));\n return fd ? ensureSignedResourceIdNotNull(fd.value) : undefined;\n });\n if (existing) {\n cacheRidMap.set(pl, existing);\n return existing;\n }\n\n const result = await pl.withWriteTx(\"templateCache:init\", async (tx) => {\n // Double-check inside write tx (another instance may have created it)\n const fd = await tx.getFieldIfExists(field(pl.clientRoot, TemplateCacheFieldName));\n if (fd) return ensureSignedResourceIdNotNull(fd.value);\n\n const cache = tx.createStruct(TemplateCacheType);\n tx.createField(field(pl.clientRoot, TemplateCacheFieldName), \"Dynamic\", cache);\n tx.lock(cache);\n await tx.commit();\n return await cache.globalId;\n });\n cacheRidMap.set(pl, result);\n return result;\n}\n\n/** Remove the template cache from user root. */\nexport async function dropTemplateCache(pl: PlClient): Promise<void> {\n await pl.withWriteTx(\"templateCache:drop\", async (tx) => {\n const cacheField = field(pl.clientRoot, TemplateCacheFieldName);\n const fd = await tx.getFieldIfExists(cacheField);\n if (fd) {\n tx.removeField(cacheField);\n await tx.commit();\n }\n });\n invalidateTemplateCacheId(pl);\n}\n\n// ─── GC ──────────────────────────────────────────────────────────────────────\n\n/**\n * Run count-based garbage collection on the template cache.\n * Evicts least-recently-used entries when the cache exceeds maxEntries.\n * Always resets the access counter to 0.\n *\n * @internal exported for testing (maxEntries parameter allows low thresholds in tests)\n * @returns true if entries were evicted\n */\nexport async function runGc(\n pl: PlClient,\n cacheRid: SignedResourceId,\n maxEntries: number = GC_MAX_ENTRIES,\n): Promise<boolean> {\n return await pl.withWriteTx(\"templateCache:gc\", async (tx) => {\n const kvs = await tx.listKeyValuesString(cacheRid);\n const entries: { hash: string; timestamp: number }[] = [];\n for (const { key, value } of kvs) {\n if (!key.startsWith(ACCESS_KEY_PREFIX)) continue;\n entries.push({\n hash: key.slice(ACCESS_KEY_PREFIX.length),\n timestamp: parseInt(value, 10),\n });\n }\n\n // Always reset counter\n tx.setKValue(cacheRid, ACCESS_COUNT_KEY, \"0\");\n\n if (entries.length <= maxEntries) {\n await tx.commit();\n return false;\n }\n\n // Sort oldest first, evict until under limit\n entries.sort((a, b) => a.timestamp - b.timestamp);\n const toEvict = entries.length - maxEntries;\n for (let i = 0; i < toEvict; i++) {\n tx.removeField(field(cacheRid, entries[i].hash));\n tx.deleteKValue(cacheRid, ACCESS_KEY_PREFIX + entries[i].hash);\n }\n\n await tx.commit();\n return true;\n });\n}\n\n// ─── Batched materialization ─────────────────────────────────────────────────\n\n/** Create a batch of cache nodes in the current transaction. */\nfunction createBatchNodes(\n tx: PlTransaction,\n cacheRid: SignedResourceId,\n batch: CacheableNode[],\n resolvedIds: ReadonlyMap<string, SignedResourceId>,\n newRefs: Map<string, ResourceRef>,\n now: string,\n): void {\n for (const node of batch) {\n const childRefs = new Map<string, AnyResourceRef>();\n for (const ch of node.childHashes) {\n const resolved = resolvedIds.get(ch) ?? newRefs.get(ch);\n if (resolved === undefined) {\n throw new Error(`BUG: child ${ch} not resolved`);\n }\n childRefs.set(ch, resolved);\n }\n const ref = node.create(tx, childRefs);\n newRefs.set(node.hash, ref);\n tx.createField(field(cacheRid, node.hash), \"Dynamic\", ref);\n tx.setKValue(cacheRid, ACCESS_KEY_PREFIX + node.hash, now);\n }\n}\n\n/**\n * Materialize a template tree via the cache using \"probe all + batched creation\".\n *\n * Phase 1 (single write tx):\n * - Check existence of ALL hashes in one roundtrip\n * - Happy path: if root cached, update access tracking and return\n * - Otherwise: fetch ResourceIds for all cache hits, create first batch of missing nodes\n *\n * Phase 2..N (one write tx per batch):\n * - Create remaining missing nodes in BATCH_SIZE chunks\n *\n * @returns root SignedResourceId and current access count (for GC decision)\n */\nasync function materialize(\n pl: PlClient,\n cacheRid: SignedResourceId,\n rootHash: string,\n nodes: CacheableNode[],\n stat: TemplateCacheStat,\n): Promise<{ rootId: SignedResourceId; accessCount: number }> {\n const allHashes = nodes.map((n) => n.hash);\n const resolvedIds = new Map<string, SignedResourceId>();\n\n // Phase 1: probe all + first batch\n const phase1 = await pl.withWriteTx(\"templateCache:materialize\", async (tx) => {\n // 1 roundtrip: check all hashes + read access count\n const [exists, countStr] = await Promise.all([\n Promise.all(allHashes.map((h) => tx.fieldExists(field(cacheRid, h)))),\n tx.getKValueStringIfExists(cacheRid, ACCESS_COUNT_KEY),\n ]);\n\n const prevCount = countStr ? parseInt(countStr, 10) : 0;\n const newCount = prevCount + 1;\n const now = Date.now().toString();\n const rootIdx = allHashes.length - 1;\n\n // Happy path: root already cached\n if (exists[rootIdx]) {\n const rootFd = await tx.getField(field(cacheRid, rootHash));\n const rootRid = ensureSignedResourceIdNotNull(rootFd.value);\n tx.setKValue(cacheRid, ACCESS_KEY_PREFIX + rootHash, now);\n tx.setKValue(cacheRid, ACCESS_COUNT_KEY, newCount.toString());\n await tx.commit();\n stat.happyPath = true;\n stat.cacheHits = stat.totalNodes;\n stat.batchCount = 1;\n return { done: true as const, rootId: rootRid, accessCount: newCount };\n }\n\n // Fetch ResourceIds for all cache hits (1 roundtrip)\n const hitIndices: number[] = [];\n for (let i = 0; i < allHashes.length; i++) {\n if (exists[i]) hitIndices.push(i);\n }\n\n if (hitIndices.length > 0) {\n const hitFields = await Promise.all(\n hitIndices.map((i) => tx.getField(field(cacheRid, allHashes[i]))),\n );\n for (let j = 0; j < hitIndices.length; j++) {\n resolvedIds.set(\n allHashes[hitIndices[j]],\n ensureSignedResourceIdNotNull(hitFields[j].value),\n );\n }\n }\n stat.cacheHits = hitIndices.length;\n\n // Missing nodes (topo order preserved from flatten)\n const missing = nodes.filter((n) => !resolvedIds.has(n.hash));\n stat.cacheMisses = missing.length;\n\n // Create first batch of missing nodes\n const firstBatch = missing.slice(0, BATCH_SIZE);\n const newRefs = new Map<string, ResourceRef>();\n createBatchNodes(tx, cacheRid, firstBatch, resolvedIds, newRefs, now);\n\n // Update access tracking for cache hits\n for (const i of hitIndices) {\n tx.setKValue(cacheRid, ACCESS_KEY_PREFIX + allHashes[i], now);\n }\n tx.setKValue(cacheRid, ACCESS_COUNT_KEY, newCount.toString());\n\n await tx.commit();\n\n // Resolve new refs to global IDs (after commit)\n for (const [hash, ref] of newRefs) {\n resolvedIds.set(hash, await toGlobalResourceId(ref));\n }\n\n return {\n done: false as const,\n remaining: missing.slice(BATCH_SIZE),\n accessCount: newCount,\n };\n });\n\n if (phase1.done) {\n return { rootId: phase1.rootId, accessCount: phase1.accessCount };\n }\n\n stat.batchCount = 1;\n\n // Phase 2+: remaining batches\n const { remaining } = phase1;\n for (let i = 0; i < remaining.length; i += BATCH_SIZE) {\n const batch = remaining.slice(i, i + BATCH_SIZE);\n stat.batchCount++;\n\n await pl.withWriteTx(\"templateCache:create\", async (tx) => {\n const newRefs = new Map<string, ResourceRef>();\n const now = Date.now().toString();\n createBatchNodes(tx, cacheRid, batch, resolvedIds, newRefs, now);\n await tx.commit();\n\n for (const [hash, ref] of newRefs) {\n resolvedIds.set(hash, await toGlobalResourceId(ref));\n }\n });\n }\n\n const rootId = resolvedIds.get(rootHash);\n if (!rootId) throw new Error(\"BUG: root hash not resolved after all batches\");\n return { rootId, accessCount: phase1.accessCount };\n}\n\n/**\n * Materialize a template tree via the cache.\n * Manages its own transactions internally — do NOT call inside an existing tx.\n *\n * @returns concrete SignedResourceId of the root template\n */\nexport async function loadTemplateCached(\n pl: PlClient,\n spec: TemplateSpecPrepared,\n options?: { cacheResourceId?: SignedResourceId },\n): Promise<SignedResourceId> {\n const stat = initialStat();\n const t0 = performance.now();\n\n try {\n // Parse to data if needed\n let tplData: TemplateData | CompiledTemplateV3;\n switch (spec.type) {\n case \"explicit\":\n tplData = parseTemplate(spec.content);\n break;\n case \"prepared\":\n tplData = spec.data;\n break;\n case \"cached\":\n return spec.resourceId;\n case \"from-registry\":\n throw new Error(\n \"loadTemplateCached does not support from-registry specs; use loadTemplate instead\",\n );\n default: {\n const _: never = spec;\n throw new Error(`unexpected spec type: ${(_ as any).type}`);\n }\n }\n\n stat.templateFormat = tplData.type;\n\n // Flatten to ordered nodes\n const tFlatten = performance.now();\n const nodes = flattenTemplateTree(tplData);\n stat.flattenMs = performance.now() - tFlatten;\n if (nodes.length === 0) throw new Error(\"template tree produced no nodes\");\n\n stat.totalNodes = nodes.length;\n const rootHash = nodes[nodes.length - 1].hash;\n\n // Resolve or create cache resource\n const tCacheInit = performance.now();\n const cacheRid = options?.cacheResourceId ?? (await getOrCreateTemplateCache(pl));\n stat.cacheInitMs = performance.now() - tCacheInit;\n\n // Retry loop: if a write tx fails (e.g. concurrent GC invalidated a cached resource),\n // restart materialization from scratch.\n const MAX_RETRIES = 3;\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const tMat = performance.now();\n const result = await materialize(pl, cacheRid, rootHash, nodes, stat);\n stat.materializeMs = performance.now() - tMat;\n stat.retries = attempt;\n\n // GC in separate tx if access count exceeded threshold\n if (result.accessCount >= GC_ACCESS_THRESHOLD) {\n await runGc(pl, cacheRid);\n stat.gcTriggered = true;\n }\n\n return result.rootId;\n } catch (e) {\n if (attempt === MAX_RETRIES - 1) throw e;\n // Retry from scratch — previous batch results may reference GC'd resources\n stat.cacheHits = 0;\n stat.cacheMisses = 0;\n }\n }\n\n throw new Error(\"BUG: unreachable\");\n } finally {\n stat.totalMs = performance.now() - t0;\n if (getDebugFlags().logTemplateCacheStat) {\n console.log(`[templateCache] ${JSON.stringify(stat)}`);\n }\n }\n}\n\n// ─── Caller helper ───────────────────────────────────────────────────────────\n\n/**\n * Pre-materialize a block pack's template via cache.\n * Returns a copy of the spec with the template replaced by a cached reference.\n * If the template is already cached, returns the spec unchanged.\n */\nexport async function cacheBlockPackTemplate(\n pl: PlClient,\n spec: BlockPackSpecPrepared,\n options?: { cacheResourceId?: SignedResourceId },\n): Promise<BlockPackSpecPrepared> {\n if (spec.template.type === \"cached\") return spec;\n\n const resourceId = await loadTemplateCached(pl, spec.template, options);\n return {\n ...spec,\n template: { type: \"cached\", resourceId },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"template_cache.js","names":["_"],"sources":["../../../src/mutator/template/template_cache.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type {\n AnyResourceRef,\n PlClient,\n PlTransaction,\n SignedResourceId,\n ResourceRef,\n} from \"@milaboratories/pl-client\";\nimport {\n ensureSignedResourceIdNotNull,\n field,\n resourceType,\n toGlobalResourceId,\n} from \"@milaboratories/pl-client\";\nimport {\n parseTemplate,\n PlTemplateLibV1,\n PlTemplateOverrideV1,\n PlTemplateSoftwareV1,\n PlTemplateV1,\n PlWasmV1,\n} from \"@milaboratories/pl-model-backend\";\nimport type {\n CompiledTemplateV3,\n TemplateData,\n TemplateDataV3,\n TemplateLibData,\n TemplateLibDataV3,\n TemplateSoftwareData,\n TemplateSoftwareDataV3,\n TemplateWasmDataV3,\n} from \"@milaboratories/pl-model-backend\";\nimport { notEmpty } from \"@milaboratories/ts-helpers\";\nimport type { BlockPackSpecPrepared } from \"../../model\";\nimport type { TemplateSpecPrepared } from \"../../model/template_spec\";\nimport { getDebugFlags } from \"../../debug\";\n\nexport const TemplateCacheType = resourceType(\"TemplateCache\", \"1\");\n\nexport const TemplateCacheFieldName = \"__templateCache\";\nconst BATCH_SIZE = 50;\n/** @internal exported for testing */\nexport const GC_ACCESS_THRESHOLD = 30;\n/** @internal exported for testing */\nexport const GC_MAX_ENTRIES = 3000;\n/** @internal exported for testing */\nexport const ACCESS_COUNT_KEY = \"_accessCount\";\n/** @internal exported for testing */\nexport const ACCESS_KEY_PREFIX = \"access_\";\n\n// ─── Stats ───────────────────────────────────────────────────────────────────\n\nexport type TemplateCacheStat = {\n totalMs: number;\n flattenMs: number;\n cacheInitMs: number;\n materializeMs: number;\n totalNodes: number;\n cacheHits: number;\n cacheMisses: number;\n batchCount: number;\n happyPath: boolean;\n gcTriggered: boolean;\n retries: number;\n templateFormat: string;\n};\n\nfunction initialStat(): TemplateCacheStat {\n return {\n totalMs: 0,\n flattenMs: 0,\n cacheInitMs: 0,\n materializeMs: 0,\n totalNodes: 0,\n cacheHits: 0,\n cacheMisses: 0,\n batchCount: 0,\n happyPath: false,\n gcTriggered: false,\n retries: 0,\n templateFormat: \"\",\n };\n}\n\n// ─── Tree node abstraction ───────────────────────────────────────────────────\n\ninterface CacheableNode {\n /** SHA-256 content hash (includes all descendant content) */\n hash: string;\n /** Creates this node's resource in a transaction.\n * childRefs maps child hash → already-resolved ResourceRef or SignedResourceId */\n create: (tx: PlTransaction, childRefs: ReadonlyMap<string, AnyResourceRef>) => ResourceRef;\n /** Hashes of direct child nodes this node depends on */\n childHashes: string[];\n}\n\n// ─── Hash computation helpers ────────────────────────────────────────────────\n\nfunction getSourceCode(name: string, sources: Record<string, string>, sourceHash: string): string {\n return notEmpty(\n sources[sourceHash],\n `trying to get \"${name}\" source: sources map doesn't contain source hash ${sourceHash}`,\n );\n}\n\n/**\n * Bottom-up hash composition: each node hashes its OWN content + child hash STRINGS.\n * This means each unique node is hashed exactly once → O(n) instead of O(n * depth).\n */\n\n// V2 leaf hashes (libs, software — no children)\n\nfunction hashLibV2(lib: TemplateLibData): string {\n return createHash(\"sha256\")\n .update(PlTemplateLibV1.type.name)\n .update(PlTemplateLibV1.type.version)\n .update(lib.name)\n .update(lib.version)\n .update(lib.src)\n .digest(\"hex\");\n}\n\nfunction hashSoftwareV2(sw: TemplateSoftwareData): string {\n return createHash(\"sha256\")\n .update(PlTemplateSoftwareV1.type.name)\n .update(PlTemplateSoftwareV1.type.version)\n .update(sw.name)\n .update(sw.version)\n .update(sw.src)\n .digest(\"hex\");\n}\n\n// V3 leaf hashes — use sourceHash directly instead of resolving source content\n\nfunction hashLibV3(lib: TemplateLibDataV3): string {\n return createHash(\"sha256\")\n .update(PlTemplateLibV1.type.name)\n .update(PlTemplateLibV1.type.version)\n .update(lib.name)\n .update(lib.version)\n .update(lib.sourceHash)\n .digest(\"hex\");\n}\n\nfunction hashSoftwareV3(sw: TemplateSoftwareDataV3): string {\n return createHash(\"sha256\")\n .update(PlTemplateSoftwareV1.type.name)\n .update(PlTemplateSoftwareV1.type.version)\n .update(sw.name)\n .update(sw.version)\n .update(sw.sourceHash)\n .digest(\"hex\");\n}\n\nfunction hashWasmV3(wasm: TemplateWasmDataV3): string {\n return createHash(\"sha256\")\n .update(PlWasmV1.type.name)\n .update(PlWasmV1.type.version)\n .update(wasm.name)\n .update(wasm.version)\n .update(wasm.sourceHash)\n .digest(\"hex\");\n}\n\n// ─── Tree flattening ─────────────────────────────────────────────────────────\n\nfunction flattenV2Tree(data: TemplateData): CacheableNode[] {\n const nodes: CacheableNode[] = [];\n const seen = new Set<string>();\n\n function processLib(lib: TemplateLibData): string {\n const hash = hashLibV2(lib);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) =>\n tx.createValue(\n PlTemplateLibV1.type,\n JSON.stringify(PlTemplateLibV1.fromV2Data(lib).data),\n ),\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processSoftware(sw: TemplateSoftwareData): string {\n const hash = hashSoftwareV2(sw);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) => {\n const swData = PlTemplateSoftwareV1.fromV2Data(sw);\n const ref = tx.createStruct(PlTemplateSoftwareV1.type, swData.data);\n tx.setKValue(ref, PlTemplateSoftwareV1.metaNameKey, JSON.stringify(swData.name));\n tx.lock(ref);\n return ref;\n },\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processTemplate(tpl: TemplateData): string {\n // Process children first (bottom-up) — their hashes are computed before ours\n const childHashes: string[] = [];\n const children: { fieldName: string; hash: string }[] = [];\n\n for (const [libId, lib] of Object.entries(tpl.libs ?? {})) {\n const h = processLib(lib);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.libPrefix}/${libId}`, hash: h });\n }\n for (const [swId, sw] of Object.entries(tpl.software ?? {})) {\n const h = processSoftware(sw);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.softPrefix}/${swId}`, hash: h });\n }\n for (const [swId, sw] of Object.entries(tpl.assets ?? {})) {\n const h = processSoftware(sw);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.softPrefix}/${swId}`, hash: h });\n }\n for (const [tplId, sub] of Object.entries(tpl.templates ?? {})) {\n const h = processTemplate(sub);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.tplPrefix}/${tplId}`, hash: h });\n }\n\n // Compose hash from own content + child hash strings (NOT child content)\n const h = createHash(\"sha256\")\n .update(PlTemplateV1.type.name)\n .update(PlTemplateV1.type.version)\n .update(tpl.hashOverride ?? \"no-override\")\n .update(tpl.name)\n .update(tpl.version)\n .update(tpl.src);\n for (const child of children) {\n h.update(\"child:\" + child.fieldName + \":\" + child.hash);\n }\n const hash = h.digest(\"hex\");\n\n if (seen.has(hash)) return hash;\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx, childRefs) => {\n const tplRef = tx.createStruct(\n PlTemplateV1.type,\n JSON.stringify(PlTemplateV1.fromV2Data(tpl).data),\n );\n for (const child of children) {\n const fld = field(tplRef, child.fieldName);\n tx.createField(fld, \"Input\");\n tx.setField(fld, notEmpty(childRefs.get(child.hash), `missing child ref ${child.hash}`));\n }\n tx.lock(tplRef);\n\n if (!tpl.hashOverride) return tplRef;\n\n const overrideRef = tx.createStruct(\n PlTemplateOverrideV1.type,\n JSON.stringify(PlTemplateOverrideV1.fromV2Data(tpl)),\n );\n const overrideFld = PlTemplateOverrideV1.tplField(overrideRef);\n tx.createField(overrideFld, \"Service\");\n tx.setField(overrideFld, tplRef);\n tx.lock(overrideRef);\n return overrideRef;\n },\n childHashes,\n });\n\n return hash;\n }\n\n processTemplate(data);\n return nodes;\n}\n\nfunction flattenV3Tree(data: CompiledTemplateV3): CacheableNode[] {\n const nodes: CacheableNode[] = [];\n const seen = new Set<string>();\n const sources = data.hashToSource;\n\n function processLib(lib: TemplateLibDataV3): string {\n const hash = hashLibV3(lib);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) =>\n tx.createValue(\n PlTemplateLibV1.type,\n JSON.stringify(\n PlTemplateLibV1.fromV3Data(lib, getSourceCode(lib.name, sources, lib.sourceHash))\n .data,\n ),\n ),\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processSoftware(sw: TemplateSoftwareDataV3): string {\n const hash = hashSoftwareV3(sw);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) => {\n const swData = PlTemplateSoftwareV1.fromV3Data(\n sw,\n getSourceCode(sw.name, sources, sw.sourceHash),\n );\n const ref = tx.createStruct(PlTemplateSoftwareV1.type, swData.data);\n tx.setKValue(ref, PlTemplateSoftwareV1.metaNameKey, JSON.stringify(swData.name));\n tx.lock(ref);\n return ref;\n },\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processWasm(wasm: TemplateWasmDataV3): string {\n const hash = hashWasmV3(wasm);\n if (!seen.has(hash)) {\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx) =>\n tx.createValue(\n PlWasmV1.type,\n JSON.stringify(\n PlWasmV1.fromV3Data(wasm, getSourceCode(wasm.name, sources, wasm.sourceHash)).data,\n ),\n ),\n childHashes: [],\n });\n }\n return hash;\n }\n\n function processTemplate(tpl: TemplateDataV3): string {\n // Defensive: a template can carry no embedded version at runtime even\n // though the type says `string` — the compiler writes `packageJson.version`\n // unchecked, so a template built from a package with no `version` field\n // yields undefined. Identity is fully covered by name + sourceHash, so the\n // placeholder is safe for both the cache key and the backend resource's\n // required Version field.\n const version = tpl.version ?? \"0.0.0\";\n\n // Process children first (bottom-up)\n const childHashes: string[] = [];\n const children: { fieldName: string; hash: string }[] = [];\n\n for (const [libId, lib] of Object.entries(tpl.libs ?? {})) {\n const h = processLib(lib);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.libPrefix}/${libId}`, hash: h });\n }\n for (const [swId, sw] of Object.entries(tpl.software ?? {})) {\n const h = processSoftware(sw);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.softPrefix}/${swId}`, hash: h });\n }\n for (const [swId, sw] of Object.entries(tpl.assets ?? {})) {\n const h = processSoftware(sw);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.softPrefix}/${swId}`, hash: h });\n }\n for (const [tplId, sub] of Object.entries(tpl.templates ?? {})) {\n const h = processTemplate(sub);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.tplPrefix}/${tplId}`, hash: h });\n }\n for (const [wasmId, wasm] of Object.entries(tpl.wasm ?? {})) {\n const h = processWasm(wasm);\n childHashes.push(h);\n children.push({ fieldName: `${PlTemplateV1.wasmPrefix}/${wasmId}`, hash: h });\n }\n\n // Compose hash from own content + child hash strings (NOT child content).\n // Uses sourceHash directly — it already uniquely identifies the source.\n const h = createHash(\"sha256\")\n .update(PlTemplateV1.type.name)\n .update(PlTemplateV1.type.version)\n .update(tpl.hashOverride ?? \"no-override\")\n .update(tpl.name)\n .update(version)\n .update(tpl.sourceHash);\n for (const child of children) {\n h.update(\"child:\" + child.fieldName + \":\" + child.hash);\n }\n const hash = h.digest(\"hex\");\n\n if (seen.has(hash)) return hash;\n seen.add(hash);\n nodes.push({\n hash,\n create: (tx, childRefs) => {\n const sourceCode = getSourceCode(tpl.name, sources, tpl.sourceHash);\n const tplRef = tx.createStruct(\n PlTemplateV1.type,\n JSON.stringify(PlTemplateV1.fromV3Data({ ...tpl, version }, sourceCode).data),\n );\n for (const child of children) {\n const fld = field(tplRef, child.fieldName);\n tx.createField(fld, \"Input\");\n tx.setField(fld, notEmpty(childRefs.get(child.hash), `missing child ref ${child.hash}`));\n }\n tx.lock(tplRef);\n\n if (!tpl.hashOverride) return tplRef;\n\n const overrideRef = tx.createStruct(\n PlTemplateOverrideV1.type,\n JSON.stringify(PlTemplateOverrideV1.fromV3Data(tpl)),\n );\n const overrideFld = PlTemplateOverrideV1.tplField(overrideRef);\n tx.createField(overrideFld, \"Service\");\n tx.setField(overrideFld, tplRef);\n tx.lock(overrideRef);\n return overrideRef;\n },\n childHashes,\n });\n\n return hash;\n }\n\n processTemplate(data.template);\n return nodes;\n}\n\n/** Flatten template tree into a topologically ordered list of cacheable nodes (leaves first). */\nexport function flattenTemplateTree(data: TemplateData | CompiledTemplateV3): CacheableNode[] {\n if (data.type === \"pl.tengo-template.v2\") {\n return flattenV2Tree(data);\n } else {\n return flattenV3Tree(data);\n }\n}\n\n// ─── Cache operations ────────────────────────────────────────────────────────\n\n/** In-memory cache for the TemplateCache SignedResourceId per PlClient instance. */\nconst cacheRidMap = new WeakMap<PlClient, SignedResourceId>();\n\n/** Clear the in-memory cacheRid entry (call on errors referencing the cache resource). */\nexport function invalidateTemplateCacheId(pl: PlClient): void {\n cacheRidMap.delete(pl);\n}\n\n/** Find or create the TemplateCache/1 resource on user root. */\nexport async function getOrCreateTemplateCache(pl: PlClient): Promise<SignedResourceId> {\n // Check in-memory cache first (0ms after first call)\n const cached = cacheRidMap.get(pl);\n if (cached !== undefined) return cached;\n\n // Try read-only check\n const existing = await pl.withReadTx(\"templateCache:check\", async (tx) => {\n const fd = await tx.getFieldIfExists(field(pl.clientRoot, TemplateCacheFieldName));\n return fd ? ensureSignedResourceIdNotNull(fd.value) : undefined;\n });\n if (existing) {\n cacheRidMap.set(pl, existing);\n return existing;\n }\n\n const result = await pl.withWriteTx(\"templateCache:init\", async (tx) => {\n // Double-check inside write tx (another instance may have created it)\n const fd = await tx.getFieldIfExists(field(pl.clientRoot, TemplateCacheFieldName));\n if (fd) return ensureSignedResourceIdNotNull(fd.value);\n\n const cache = tx.createStruct(TemplateCacheType);\n tx.createField(field(pl.clientRoot, TemplateCacheFieldName), \"Dynamic\", cache);\n tx.lock(cache);\n await tx.commit();\n return await cache.globalId;\n });\n cacheRidMap.set(pl, result);\n return result;\n}\n\n/** Remove the template cache from user root. */\nexport async function dropTemplateCache(pl: PlClient): Promise<void> {\n await pl.withWriteTx(\"templateCache:drop\", async (tx) => {\n const cacheField = field(pl.clientRoot, TemplateCacheFieldName);\n const fd = await tx.getFieldIfExists(cacheField);\n if (fd) {\n tx.removeField(cacheField);\n await tx.commit();\n }\n });\n invalidateTemplateCacheId(pl);\n}\n\n// ─── GC ──────────────────────────────────────────────────────────────────────\n\n/**\n * Run count-based garbage collection on the template cache.\n * Evicts least-recently-used entries when the cache exceeds maxEntries.\n * Always resets the access counter to 0.\n *\n * @internal exported for testing (maxEntries parameter allows low thresholds in tests)\n * @returns true if entries were evicted\n */\nexport async function runGc(\n pl: PlClient,\n cacheRid: SignedResourceId,\n maxEntries: number = GC_MAX_ENTRIES,\n): Promise<boolean> {\n return await pl.withWriteTx(\"templateCache:gc\", async (tx) => {\n const kvs = await tx.listKeyValuesString(cacheRid);\n const entries: { hash: string; timestamp: number }[] = [];\n for (const { key, value } of kvs) {\n if (!key.startsWith(ACCESS_KEY_PREFIX)) continue;\n entries.push({\n hash: key.slice(ACCESS_KEY_PREFIX.length),\n timestamp: parseInt(value, 10),\n });\n }\n\n // Always reset counter\n tx.setKValue(cacheRid, ACCESS_COUNT_KEY, \"0\");\n\n if (entries.length <= maxEntries) {\n await tx.commit();\n return false;\n }\n\n // Sort oldest first, evict until under limit\n entries.sort((a, b) => a.timestamp - b.timestamp);\n const toEvict = entries.length - maxEntries;\n for (let i = 0; i < toEvict; i++) {\n tx.removeField(field(cacheRid, entries[i].hash));\n tx.deleteKValue(cacheRid, ACCESS_KEY_PREFIX + entries[i].hash);\n }\n\n await tx.commit();\n return true;\n });\n}\n\n// ─── Batched materialization ─────────────────────────────────────────────────\n\n/** Create a batch of cache nodes in the current transaction. */\nfunction createBatchNodes(\n tx: PlTransaction,\n cacheRid: SignedResourceId,\n batch: CacheableNode[],\n resolvedIds: ReadonlyMap<string, SignedResourceId>,\n newRefs: Map<string, ResourceRef>,\n now: string,\n): void {\n for (const node of batch) {\n const childRefs = new Map<string, AnyResourceRef>();\n for (const ch of node.childHashes) {\n const resolved = resolvedIds.get(ch) ?? newRefs.get(ch);\n if (resolved === undefined) {\n throw new Error(`BUG: child ${ch} not resolved`);\n }\n childRefs.set(ch, resolved);\n }\n const ref = node.create(tx, childRefs);\n newRefs.set(node.hash, ref);\n tx.createField(field(cacheRid, node.hash), \"Dynamic\", ref);\n tx.setKValue(cacheRid, ACCESS_KEY_PREFIX + node.hash, now);\n }\n}\n\n/**\n * Materialize a template tree via the cache using \"probe all + batched creation\".\n *\n * Phase 1 (single write tx):\n * - Check existence of ALL hashes in one roundtrip\n * - Happy path: if root cached, update access tracking and return\n * - Otherwise: fetch ResourceIds for all cache hits, create first batch of missing nodes\n *\n * Phase 2..N (one write tx per batch):\n * - Create remaining missing nodes in BATCH_SIZE chunks\n *\n * @returns root SignedResourceId and current access count (for GC decision)\n */\nasync function materialize(\n pl: PlClient,\n cacheRid: SignedResourceId,\n rootHash: string,\n nodes: CacheableNode[],\n stat: TemplateCacheStat,\n): Promise<{ rootId: SignedResourceId; accessCount: number }> {\n const allHashes = nodes.map((n) => n.hash);\n const resolvedIds = new Map<string, SignedResourceId>();\n\n // Phase 1: probe all + first batch\n const phase1 = await pl.withWriteTx(\"templateCache:materialize\", async (tx) => {\n // 1 roundtrip: check all hashes + read access count\n const [exists, countStr] = await Promise.all([\n Promise.all(allHashes.map((h) => tx.fieldExists(field(cacheRid, h)))),\n tx.getKValueStringIfExists(cacheRid, ACCESS_COUNT_KEY),\n ]);\n\n const prevCount = countStr ? parseInt(countStr, 10) : 0;\n const newCount = prevCount + 1;\n const now = Date.now().toString();\n const rootIdx = allHashes.length - 1;\n\n // Happy path: root already cached\n if (exists[rootIdx]) {\n const rootFd = await tx.getField(field(cacheRid, rootHash));\n const rootRid = ensureSignedResourceIdNotNull(rootFd.value);\n tx.setKValue(cacheRid, ACCESS_KEY_PREFIX + rootHash, now);\n tx.setKValue(cacheRid, ACCESS_COUNT_KEY, newCount.toString());\n await tx.commit();\n stat.happyPath = true;\n stat.cacheHits = stat.totalNodes;\n stat.batchCount = 1;\n return { done: true as const, rootId: rootRid, accessCount: newCount };\n }\n\n // Fetch ResourceIds for all cache hits (1 roundtrip)\n const hitIndices: number[] = [];\n for (let i = 0; i < allHashes.length; i++) {\n if (exists[i]) hitIndices.push(i);\n }\n\n if (hitIndices.length > 0) {\n const hitFields = await Promise.all(\n hitIndices.map((i) => tx.getField(field(cacheRid, allHashes[i]))),\n );\n for (let j = 0; j < hitIndices.length; j++) {\n resolvedIds.set(\n allHashes[hitIndices[j]],\n ensureSignedResourceIdNotNull(hitFields[j].value),\n );\n }\n }\n stat.cacheHits = hitIndices.length;\n\n // Missing nodes (topo order preserved from flatten)\n const missing = nodes.filter((n) => !resolvedIds.has(n.hash));\n stat.cacheMisses = missing.length;\n\n // Create first batch of missing nodes\n const firstBatch = missing.slice(0, BATCH_SIZE);\n const newRefs = new Map<string, ResourceRef>();\n createBatchNodes(tx, cacheRid, firstBatch, resolvedIds, newRefs, now);\n\n // Update access tracking for cache hits\n for (const i of hitIndices) {\n tx.setKValue(cacheRid, ACCESS_KEY_PREFIX + allHashes[i], now);\n }\n tx.setKValue(cacheRid, ACCESS_COUNT_KEY, newCount.toString());\n\n await tx.commit();\n\n // Resolve new refs to global IDs (after commit)\n for (const [hash, ref] of newRefs) {\n resolvedIds.set(hash, await toGlobalResourceId(ref));\n }\n\n return {\n done: false as const,\n remaining: missing.slice(BATCH_SIZE),\n accessCount: newCount,\n };\n });\n\n if (phase1.done) {\n return { rootId: phase1.rootId, accessCount: phase1.accessCount };\n }\n\n stat.batchCount = 1;\n\n // Phase 2+: remaining batches\n const { remaining } = phase1;\n for (let i = 0; i < remaining.length; i += BATCH_SIZE) {\n const batch = remaining.slice(i, i + BATCH_SIZE);\n stat.batchCount++;\n\n await pl.withWriteTx(\"templateCache:create\", async (tx) => {\n const newRefs = new Map<string, ResourceRef>();\n const now = Date.now().toString();\n createBatchNodes(tx, cacheRid, batch, resolvedIds, newRefs, now);\n await tx.commit();\n\n for (const [hash, ref] of newRefs) {\n resolvedIds.set(hash, await toGlobalResourceId(ref));\n }\n });\n }\n\n const rootId = resolvedIds.get(rootHash);\n if (!rootId) throw new Error(\"BUG: root hash not resolved after all batches\");\n return { rootId, accessCount: phase1.accessCount };\n}\n\n/**\n * Materialize a template tree via the cache.\n * Manages its own transactions internally — do NOT call inside an existing tx.\n *\n * @returns concrete SignedResourceId of the root template\n */\nexport async function loadTemplateCached(\n pl: PlClient,\n spec: TemplateSpecPrepared,\n options?: { cacheResourceId?: SignedResourceId },\n): Promise<SignedResourceId> {\n const stat = initialStat();\n const t0 = performance.now();\n\n try {\n // Parse to data if needed\n let tplData: TemplateData | CompiledTemplateV3;\n switch (spec.type) {\n case \"explicit\":\n tplData = parseTemplate(spec.content);\n break;\n case \"prepared\":\n tplData = spec.data;\n break;\n case \"cached\":\n return spec.resourceId;\n case \"from-registry\":\n throw new Error(\n \"loadTemplateCached does not support from-registry specs; use loadTemplate instead\",\n );\n default: {\n const _: never = spec;\n throw new Error(`unexpected spec type: ${(_ as any).type}`);\n }\n }\n\n stat.templateFormat = tplData.type;\n\n // Flatten to ordered nodes\n const tFlatten = performance.now();\n const nodes = flattenTemplateTree(tplData);\n stat.flattenMs = performance.now() - tFlatten;\n if (nodes.length === 0) throw new Error(\"template tree produced no nodes\");\n\n stat.totalNodes = nodes.length;\n const rootHash = nodes[nodes.length - 1].hash;\n\n // Resolve or create cache resource\n const tCacheInit = performance.now();\n const cacheRid = options?.cacheResourceId ?? (await getOrCreateTemplateCache(pl));\n stat.cacheInitMs = performance.now() - tCacheInit;\n\n // Retry loop: if a write tx fails (e.g. concurrent GC invalidated a cached resource),\n // restart materialization from scratch.\n const MAX_RETRIES = 3;\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n const tMat = performance.now();\n const result = await materialize(pl, cacheRid, rootHash, nodes, stat);\n stat.materializeMs = performance.now() - tMat;\n stat.retries = attempt;\n\n // GC in separate tx if access count exceeded threshold\n if (result.accessCount >= GC_ACCESS_THRESHOLD) {\n await runGc(pl, cacheRid);\n stat.gcTriggered = true;\n }\n\n return result.rootId;\n } catch (e) {\n if (attempt === MAX_RETRIES - 1) throw e;\n // Retry from scratch — previous batch results may reference GC'd resources\n stat.cacheHits = 0;\n stat.cacheMisses = 0;\n }\n }\n\n throw new Error(\"BUG: unreachable\");\n } finally {\n stat.totalMs = performance.now() - t0;\n if (getDebugFlags().logTemplateCacheStat) {\n console.log(`[templateCache] ${JSON.stringify(stat)}`);\n }\n }\n}\n\n// ─── Caller helper ───────────────────────────────────────────────────────────\n\n/**\n * Pre-materialize a block pack's template via cache.\n * Returns a copy of the spec with the template replaced by a cached reference.\n * If the template is already cached, returns the spec unchanged.\n */\nexport async function cacheBlockPackTemplate(\n pl: PlClient,\n spec: BlockPackSpecPrepared,\n options?: { cacheResourceId?: SignedResourceId },\n): Promise<BlockPackSpecPrepared> {\n if (spec.template.type === \"cached\") return spec;\n\n const resourceId = await loadTemplateCached(pl, spec.template, options);\n return {\n ...spec,\n template: { type: \"cached\", resourceId },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
|
|
2
2
|
const require_direct_template_loader = require("./direct_template_loader.cjs");
|
|
3
3
|
let node_fs = require("node:fs");
|
|
4
|
-
node_fs = require_runtime.__toESM(node_fs);
|
|
4
|
+
node_fs = require_runtime.__toESM(node_fs, 1);
|
|
5
5
|
let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
|
|
6
6
|
let _milaboratories_pl_client = require("@milaboratories/pl-client");
|
|
7
7
|
//#region src/mutator/template/template_loading.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template_loading.cjs","names":["fs","Pl","loadTemplateFromExplicitDirect","loadTemplateFromPrepared"],"sources":["../../../src/mutator/template/template_loading.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport fs from \"node:fs\";\nimport type {\n TemplateFromRegistry,\n TemplateSpecAny,\n TemplateSpecPrepared,\n} from \"../../model/template_spec\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { loadTemplateFromExplicitDirect, loadTemplateFromPrepared } from \"./direct_template_loader\";\n\n//\n// Resource schema\n//\n\nexport const TengoTemplateGet: ResourceType = { name: \"TengoTemplateGet\", version: \"1\" };\nexport const TengoTemplateGetRegistry = \"registry\";\nexport const TengoTemplateGetTemplateURI = \"templateURI\";\nexport const TengoTemplateGetTemplate = \"template\";\n\nexport const TengoTemplatePack: ResourceType = { name: \"TengoTemplatePack\", version: \"1\" };\nexport const TengoTemplatePackConvert: ResourceType = {\n name: \"TengoTemplatePackConvert\",\n version: \"1\",\n};\nexport const TengoTemplatePackConvertTemplatePack = \"templatePack\";\nexport const TengoTemplatePackConvertTemplate = \"template\";\n\nexport async function prepareTemplateSpec(tpl: TemplateSpecAny): Promise<TemplateSpecPrepared> {\n switch (tpl.type) {\n case \"from-file\":\n return {\n type: \"explicit\",\n content: await fs.promises.readFile(tpl.path),\n };\n case \"from-registry\":\n case \"explicit\":\n case \"cached\":\n return tpl;\n case \"prepared\":\n return tpl;\n default:\n return assertNever(tpl);\n }\n}\n\nfunction loadTemplateFromRegistry(tx: PlTransaction, spec: TemplateFromRegistry): AnyRef {\n const getTemplate = tx.createStruct(TengoTemplateGet);\n const registry = field(getTemplate, TengoTemplateGetRegistry);\n const uri = field(getTemplate, TengoTemplateGetTemplateURI);\n const templateFromRegistry = field(getTemplate, TengoTemplateGetTemplate);\n\n // Note: it has a resource schema, so platforma creates fields by itself.\n\n tx.setField(registry, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.registry))));\n tx.setField(uri, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.path))));\n\n return templateFromRegistry;\n}\n\nexport function loadTemplate(tx: PlTransaction, spec: TemplateSpecPrepared): AnyRef {\n switch (spec.type) {\n case \"from-registry\":\n return loadTemplateFromRegistry(tx, spec);\n case \"explicit\":\n return loadTemplateFromExplicitDirect(tx, spec);\n case \"prepared\":\n return loadTemplateFromPrepared(tx, spec);\n case \"cached\":\n return spec.resourceId;\n default:\n return assertNever(spec);\n }\n}\n"],"mappings":";;;;;;;AAeA,MAAa,mBAAiC;CAAE,MAAM;CAAoB,SAAS;
|
|
1
|
+
{"version":3,"file":"template_loading.cjs","names":["fs","Pl","loadTemplateFromExplicitDirect","loadTemplateFromPrepared"],"sources":["../../../src/mutator/template/template_loading.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport fs from \"node:fs\";\nimport type {\n TemplateFromRegistry,\n TemplateSpecAny,\n TemplateSpecPrepared,\n} from \"../../model/template_spec\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { loadTemplateFromExplicitDirect, loadTemplateFromPrepared } from \"./direct_template_loader\";\n\n//\n// Resource schema\n//\n\nexport const TengoTemplateGet: ResourceType = { name: \"TengoTemplateGet\", version: \"1\" };\nexport const TengoTemplateGetRegistry = \"registry\";\nexport const TengoTemplateGetTemplateURI = \"templateURI\";\nexport const TengoTemplateGetTemplate = \"template\";\n\nexport const TengoTemplatePack: ResourceType = { name: \"TengoTemplatePack\", version: \"1\" };\nexport const TengoTemplatePackConvert: ResourceType = {\n name: \"TengoTemplatePackConvert\",\n version: \"1\",\n};\nexport const TengoTemplatePackConvertTemplatePack = \"templatePack\";\nexport const TengoTemplatePackConvertTemplate = \"template\";\n\nexport async function prepareTemplateSpec(tpl: TemplateSpecAny): Promise<TemplateSpecPrepared> {\n switch (tpl.type) {\n case \"from-file\":\n return {\n type: \"explicit\",\n content: await fs.promises.readFile(tpl.path),\n };\n case \"from-registry\":\n case \"explicit\":\n case \"cached\":\n return tpl;\n case \"prepared\":\n return tpl;\n default:\n return assertNever(tpl);\n }\n}\n\nfunction loadTemplateFromRegistry(tx: PlTransaction, spec: TemplateFromRegistry): AnyRef {\n const getTemplate = tx.createStruct(TengoTemplateGet);\n const registry = field(getTemplate, TengoTemplateGetRegistry);\n const uri = field(getTemplate, TengoTemplateGetTemplateURI);\n const templateFromRegistry = field(getTemplate, TengoTemplateGetTemplate);\n\n // Note: it has a resource schema, so platforma creates fields by itself.\n\n tx.setField(registry, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.registry))));\n tx.setField(uri, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.path))));\n\n return templateFromRegistry;\n}\n\nexport function loadTemplate(tx: PlTransaction, spec: TemplateSpecPrepared): AnyRef {\n switch (spec.type) {\n case \"from-registry\":\n return loadTemplateFromRegistry(tx, spec);\n case \"explicit\":\n return loadTemplateFromExplicitDirect(tx, spec);\n case \"prepared\":\n return loadTemplateFromPrepared(tx, spec);\n case \"cached\":\n return spec.resourceId;\n default:\n return assertNever(spec);\n }\n}\n"],"mappings":";;;;;;;AAeA,MAAa,mBAAiC;CAAE,MAAM;CAAoB,SAAS;AAAI;AACvF,MAAa,2BAA2B;AACxC,MAAa,8BAA8B;AAC3C,MAAa,2BAA2B;AAExC,MAAa,oBAAkC;CAAE,MAAM;CAAqB,SAAS;AAAI;AACzF,MAAa,2BAAyC;CACpD,MAAM;CACN,SAAS;AACX;AACA,MAAa,uCAAuC;AACpD,MAAa,mCAAmC;AAEhD,eAAsB,oBAAoB,KAAqD;CAC7F,QAAQ,IAAI,MAAZ;EACE,KAAK,aACH,OAAO;GACL,MAAM;GACN,SAAS,MAAMA,QAAAA,QAAG,SAAS,SAAS,IAAI,IAAI;EAC9C;EACF,KAAK;EACL,KAAK;EACL,KAAK,UACH,OAAO;EACT,KAAK,YACH,OAAO;EACT,SACE,QAAA,GAAA,2BAAA,YAAA,CAAmB,GAAG;CAC1B;AACF;AAEA,SAAS,yBAAyB,IAAmB,MAAoC;CACvF,MAAM,cAAc,GAAG,aAAa,gBAAgB;CACpD,MAAM,YAAA,GAAA,0BAAA,MAAA,CAAiB,aAAa,wBAAwB;CAC5D,MAAM,OAAA,GAAA,0BAAA,MAAA,CAAY,aAAa,2BAA2B;CAC1D,MAAM,wBAAA,GAAA,0BAAA,MAAA,CAA6B,aAAa,wBAAwB;CAIxE,GAAG,SAAS,UAAU,GAAG,YAAYC,0BAAAA,GAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC;CAC/F,GAAG,SAAS,KAAK,GAAG,YAAYA,0BAAAA,GAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC;CAEtF,OAAO;AACT;AAEA,SAAgB,aAAa,IAAmB,MAAoC;CAClF,QAAQ,KAAK,MAAb;EACE,KAAK,iBACH,OAAO,yBAAyB,IAAI,IAAI;EAC1C,KAAK,YACH,OAAOC,+BAAAA,+BAA+B,IAAI,IAAI;EAChD,KAAK,YACH,OAAOC,+BAAAA,yBAAyB,IAAI,IAAI;EAC1C,KAAK,UACH,OAAO,KAAK;EACd,SACE,QAAA,GAAA,2BAAA,YAAA,CAAmB,IAAI;CAC3B;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template_loading.d.ts","names":[],"sources":["../../../src/mutator/template/template_loading.ts"],"mappings":";;;;cAea,gBAAA,EAAkB,
|
|
1
|
+
{"version":3,"file":"template_loading.d.ts","names":[],"sources":["../../../src/mutator/template/template_loading.ts"],"mappings":";;;;cAea,gBAAA,EAAkB,YAAyD;AAAA,cAC3E,wBAAA;AAAA,cACA,2BAAA;AAAA,cACA,wBAAA;AAAA,cAEA,iBAAA,EAAmB,YAA0D;AAAA,cAC7E,wBAAA,EAA0B,YAGtC;AAAA,cACY,oCAAA;AAAA,cACA,gCAAA;AAAA,iBAES,mBAAA,CAAoB,GAAA,EAAK,eAAA,GAAkB,OAAA,CAAQ,oBAAA;AAAA,iBAgCzD,YAAA,CAAa,EAAA,EAAI,aAAA,EAAe,IAAA,EAAM,oBAAA,GAAuB,MAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template_loading.js","names":[],"sources":["../../../src/mutator/template/template_loading.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport fs from \"node:fs\";\nimport type {\n TemplateFromRegistry,\n TemplateSpecAny,\n TemplateSpecPrepared,\n} from \"../../model/template_spec\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { loadTemplateFromExplicitDirect, loadTemplateFromPrepared } from \"./direct_template_loader\";\n\n//\n// Resource schema\n//\n\nexport const TengoTemplateGet: ResourceType = { name: \"TengoTemplateGet\", version: \"1\" };\nexport const TengoTemplateGetRegistry = \"registry\";\nexport const TengoTemplateGetTemplateURI = \"templateURI\";\nexport const TengoTemplateGetTemplate = \"template\";\n\nexport const TengoTemplatePack: ResourceType = { name: \"TengoTemplatePack\", version: \"1\" };\nexport const TengoTemplatePackConvert: ResourceType = {\n name: \"TengoTemplatePackConvert\",\n version: \"1\",\n};\nexport const TengoTemplatePackConvertTemplatePack = \"templatePack\";\nexport const TengoTemplatePackConvertTemplate = \"template\";\n\nexport async function prepareTemplateSpec(tpl: TemplateSpecAny): Promise<TemplateSpecPrepared> {\n switch (tpl.type) {\n case \"from-file\":\n return {\n type: \"explicit\",\n content: await fs.promises.readFile(tpl.path),\n };\n case \"from-registry\":\n case \"explicit\":\n case \"cached\":\n return tpl;\n case \"prepared\":\n return tpl;\n default:\n return assertNever(tpl);\n }\n}\n\nfunction loadTemplateFromRegistry(tx: PlTransaction, spec: TemplateFromRegistry): AnyRef {\n const getTemplate = tx.createStruct(TengoTemplateGet);\n const registry = field(getTemplate, TengoTemplateGetRegistry);\n const uri = field(getTemplate, TengoTemplateGetTemplateURI);\n const templateFromRegistry = field(getTemplate, TengoTemplateGetTemplate);\n\n // Note: it has a resource schema, so platforma creates fields by itself.\n\n tx.setField(registry, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.registry))));\n tx.setField(uri, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.path))));\n\n return templateFromRegistry;\n}\n\nexport function loadTemplate(tx: PlTransaction, spec: TemplateSpecPrepared): AnyRef {\n switch (spec.type) {\n case \"from-registry\":\n return loadTemplateFromRegistry(tx, spec);\n case \"explicit\":\n return loadTemplateFromExplicitDirect(tx, spec);\n case \"prepared\":\n return loadTemplateFromPrepared(tx, spec);\n case \"cached\":\n return spec.resourceId;\n default:\n return assertNever(spec);\n }\n}\n"],"mappings":";;;;;AAeA,MAAa,mBAAiC;CAAE,MAAM;CAAoB,SAAS;
|
|
1
|
+
{"version":3,"file":"template_loading.js","names":[],"sources":["../../../src/mutator/template/template_loading.ts"],"sourcesContent":["import type { AnyRef, PlTransaction, ResourceType } from \"@milaboratories/pl-client\";\nimport { field, Pl } from \"@milaboratories/pl-client\";\nimport fs from \"node:fs\";\nimport type {\n TemplateFromRegistry,\n TemplateSpecAny,\n TemplateSpecPrepared,\n} from \"../../model/template_spec\";\nimport { assertNever } from \"@milaboratories/ts-helpers\";\nimport { loadTemplateFromExplicitDirect, loadTemplateFromPrepared } from \"./direct_template_loader\";\n\n//\n// Resource schema\n//\n\nexport const TengoTemplateGet: ResourceType = { name: \"TengoTemplateGet\", version: \"1\" };\nexport const TengoTemplateGetRegistry = \"registry\";\nexport const TengoTemplateGetTemplateURI = \"templateURI\";\nexport const TengoTemplateGetTemplate = \"template\";\n\nexport const TengoTemplatePack: ResourceType = { name: \"TengoTemplatePack\", version: \"1\" };\nexport const TengoTemplatePackConvert: ResourceType = {\n name: \"TengoTemplatePackConvert\",\n version: \"1\",\n};\nexport const TengoTemplatePackConvertTemplatePack = \"templatePack\";\nexport const TengoTemplatePackConvertTemplate = \"template\";\n\nexport async function prepareTemplateSpec(tpl: TemplateSpecAny): Promise<TemplateSpecPrepared> {\n switch (tpl.type) {\n case \"from-file\":\n return {\n type: \"explicit\",\n content: await fs.promises.readFile(tpl.path),\n };\n case \"from-registry\":\n case \"explicit\":\n case \"cached\":\n return tpl;\n case \"prepared\":\n return tpl;\n default:\n return assertNever(tpl);\n }\n}\n\nfunction loadTemplateFromRegistry(tx: PlTransaction, spec: TemplateFromRegistry): AnyRef {\n const getTemplate = tx.createStruct(TengoTemplateGet);\n const registry = field(getTemplate, TengoTemplateGetRegistry);\n const uri = field(getTemplate, TengoTemplateGetTemplateURI);\n const templateFromRegistry = field(getTemplate, TengoTemplateGetTemplate);\n\n // Note: it has a resource schema, so platforma creates fields by itself.\n\n tx.setField(registry, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.registry))));\n tx.setField(uri, tx.createValue(Pl.JsonString, Buffer.from(JSON.stringify(spec.path))));\n\n return templateFromRegistry;\n}\n\nexport function loadTemplate(tx: PlTransaction, spec: TemplateSpecPrepared): AnyRef {\n switch (spec.type) {\n case \"from-registry\":\n return loadTemplateFromRegistry(tx, spec);\n case \"explicit\":\n return loadTemplateFromExplicitDirect(tx, spec);\n case \"prepared\":\n return loadTemplateFromPrepared(tx, spec);\n case \"cached\":\n return spec.resourceId;\n default:\n return assertNever(spec);\n }\n}\n"],"mappings":";;;;;AAeA,MAAa,mBAAiC;CAAE,MAAM;CAAoB,SAAS;AAAI;AACvF,MAAa,2BAA2B;AACxC,MAAa,8BAA8B;AAC3C,MAAa,2BAA2B;AAExC,MAAa,oBAAkC;CAAE,MAAM;CAAqB,SAAS;AAAI;AACzF,MAAa,2BAAyC;CACpD,MAAM;CACN,SAAS;AACX;AACA,MAAa,uCAAuC;AACpD,MAAa,mCAAmC;AAEhD,eAAsB,oBAAoB,KAAqD;CAC7F,QAAQ,IAAI,MAAZ;EACE,KAAK,aACH,OAAO;GACL,MAAM;GACN,SAAS,MAAM,GAAG,SAAS,SAAS,IAAI,IAAI;EAC9C;EACF,KAAK;EACL,KAAK;EACL,KAAK,UACH,OAAO;EACT,KAAK,YACH,OAAO;EACT,SACE,OAAO,YAAY,GAAG;CAC1B;AACF;AAEA,SAAS,yBAAyB,IAAmB,MAAoC;CACvF,MAAM,cAAc,GAAG,aAAa,gBAAgB;CACpD,MAAM,WAAW,MAAM,aAAa,wBAAwB;CAC5D,MAAM,MAAM,MAAM,aAAa,2BAA2B;CAC1D,MAAM,uBAAuB,MAAM,aAAa,wBAAwB;CAIxE,GAAG,SAAS,UAAU,GAAG,YAAY,GAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC;CAC/F,GAAG,SAAS,KAAK,GAAG,YAAY,GAAG,YAAY,OAAO,KAAK,KAAK,UAAU,KAAK,IAAI,CAAC,CAAC,CAAC;CAEtF,OAAO;AACT;AAEA,SAAgB,aAAa,IAAmB,MAAoC;CAClF,QAAQ,KAAK,MAAb;EACE,KAAK,iBACH,OAAO,yBAAyB,IAAI,IAAI;EAC1C,KAAK,YACH,OAAO,+BAA+B,IAAI,IAAI;EAChD,KAAK,YACH,OAAO,yBAAyB,IAAI,IAAI;EAC1C,KAAK,UACH,OAAO,KAAK;EACd,SACE,OAAO,YAAY,IAAI;CAC3B;AACF"}
|
|
@@ -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":["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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network_check.d.ts","names":[],"sources":["../../src/network_check/network_check.ts"],"mappings":";;;;;;UAqEiB,gBAAA;EA0Bf;EAxBA,MAAA,GAAS,
|
|
1
|
+
{"version":3,"file":"network_check.d.ts","names":[],"sources":["../../src/network_check/network_check.ts"],"mappings":";;;;;;UAqEiB,gBAAA;EA0Bf;EAxBA,MAAA,GAAS,WAAW;EA6BpB;EA1BA,mBAAA;EACA,aAAA;EACA,iBAAA;EA8ByB;EA3BzB,aAAA;EAiCoB;EA9BpB,uBAAA;EACA,0BAAA;EACA,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,WAAA;EA8BQ;EA3BR,uBAAA;EACA,+BAAA;EACA,gBAAA;EAwBe;EArBf,SAAA;EAqBA;EAlBA,sBAAA;EAmBQ;EAjBR,uBAAA;EA4GoB;EA1GpB,uBAAA;;EAEA,yBAAA;EA4Ge;EA1Gf,yBAAA;AAAA;;;;iBAMoB,YAAA,CACpB,aAAA,UACA,MAAA,sBACA,UAAA,sBACA,aAAA,GAAe,OAAA,CAAQ,gBAAA,IACtB,OAAA;AAAA,iBA2FmB,gBAAA,CACpB,aAAA,UACA,MAAA,sBACA,UAAA,sBACA,aAAA,GAAe,OAAA,CAAQ,gBAAA,IACtB,OAAA;EACD,MAAA,EAAQ,QAAA;EACR,QAAA,EAAU,cAAA;EACV,MAAA,EAAQ,MAAA;EACR,MAAA,EAAQ,QAAA;EACR,cAAA,EAAgB,cAAA;EAChB,gBAAA,EAAkB,YAAA;EAClB,QAAA,EAAU,QAAA;EACV,UAAA,EAAY,UAAA;EACZ,GAAA,EAAK,gBAAA;EACL,SAAA,QAAiB,OAAA;AAAA"}
|
|
@@ -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,4 +1,3 @@
|
|
|
1
|
-
require("../_virtual/_rolldown/runtime.cjs");
|
|
2
1
|
let undici = require("undici");
|
|
3
2
|
let _milaboratories_pl_client = require("@milaboratories/pl-client");
|
|
4
3
|
let node_timers_promises = require("node:timers/promises");
|
|
@@ -112,6 +111,9 @@ exports.blockGARegistryOverviewPings = blockGARegistryOverviewPings;
|
|
|
112
111
|
exports.blockGARegistryUiPings = blockGARegistryUiPings;
|
|
113
112
|
exports.blockRegistryOverviewPings = blockRegistryOverviewPings;
|
|
114
113
|
exports.blockRegistryUiPings = blockRegistryUiPings;
|
|
114
|
+
exports.elapsed = elapsed;
|
|
115
|
+
exports.recordPings = recordPings;
|
|
115
116
|
exports.reportToString = reportToString;
|
|
117
|
+
exports.requestUrl = requestUrl;
|
|
116
118
|
|
|
117
119
|
//# sourceMappingURL=pings.cjs.map
|
|
@@ -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":"
|
|
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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"}
|
|
@@ -105,6 +105,6 @@ function elapsedStat(reports) {
|
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
//#endregion
|
|
108
|
-
export { autoUpdateCdnPings, backendPings, blockGARegistryOverviewPings, blockGARegistryUiPings, blockRegistryOverviewPings, blockRegistryUiPings, reportToString };
|
|
108
|
+
export { autoUpdateCdnPings, backendPings, blockGARegistryOverviewPings, blockGARegistryUiPings, blockRegistryOverviewPings, blockRegistryUiPings, elapsed, recordPings, reportToString, requestUrl };
|
|
109
109
|
|
|
110
110
|
//# sourceMappingURL=pings.js.map
|