@milaboratories/pl-middle-layer 1.71.1 → 1.71.2

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.
@@ -2,7 +2,10 @@ const require_index = require("../debug/index.cjs");
2
2
  const require_context = require("./context.cjs");
3
3
  let _milaboratories_computable = require("@milaboratories/computable");
4
4
  let quickjs_emscripten = require("quickjs-emscripten");
5
+ let _milaboratories_pl_flight_recorder = require("@milaboratories/pl-flight-recorder");
5
6
  //#region src/js_render/index.ts
7
+ /** Memory ceiling applied to every QuickJS runtime that evaluates model code. */
8
+ const QUICK_JS_MEMORY_LIMIT = 1024 * 1024 * 8;
6
9
  function logOutputStatus(handle, renderedResult, stable, recalculationCounter, unstableMarker) {
7
10
  if (require_index.getDebugFlags().logOutputStatus && (require_index.getDebugFlags().logOutputStatus === "any" || !stable)) if (stable) console.log(`Stable output ${handle} calculated ${renderedResult !== void 0 ? "defined" : "undefined"}; (#${recalculationCounter})`);
8
11
  else console.log(`Unstable output ${handle}; marker = ${unstableMarker}; ${renderedResult !== void 0 ? "defined" : "undefined"} (#${recalculationCounter})`);
@@ -72,7 +75,7 @@ function computableFromRF(env, ctx, fh, codeWithInfo, configKey, ops = {}) {
72
75
  });
73
76
  try {
74
77
  const runtime = scope.manage(env.quickJs.newRuntime());
75
- runtime.setMemoryLimit(1024 * 1024 * 8);
78
+ runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);
76
79
  runtime.setMaxStackSize(1024 * 320);
77
80
  let deadlineSettings;
78
81
  runtime.setInterruptHandler(() => {
@@ -87,30 +90,45 @@ function computableFromRF(env, ctx, fh, codeWithInfo, configKey, ops = {}) {
87
90
  blockCtx: ctx,
88
91
  mlEnv: env
89
92
  });
90
- rCtx.evaluateBundle(code.content);
91
- const result = rCtx.runCallback(fh.handle);
92
- rCtx.resetComputableCtx();
93
- const toBeResolved = rCtx.computableHelper.computablesToResolve;
94
- if (Object.keys(toBeResolved).length === 0) {
95
- const importedResult = rCtx.importObjectUniversal(result);
96
- if (require_index.getDebugFlags().logJsExecStat) console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);
97
- logOutputStatus(fh.handle, importedResult, cCtx.unstableMarker === void 0, -1, cCtx.unstableMarker);
98
- return { ir: importedResult };
99
- }
100
- let recalculationCounter = 0;
101
- if (require_index.getDebugFlags().logOutputStatus) console.log(`Output ${fh.handle} scaffold calculated (not all computables resolved yet).`);
102
- keepVmAlive = true;
103
- return {
104
- ir: toBeResolved,
105
- postprocessValue: (resolved, { unstableMarker, stable }) => {
106
- for (const [handle, value] of Object.entries(resolved)) rCtx.runCallback(handle, value);
107
- const renderedResult = rCtx.importObjectUniversal(result);
108
- recalculationCounter++;
109
- if (require_index.getDebugFlags().logJsExecStat) console.log(`[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`);
110
- logOutputStatus(fh.handle, renderedResult, stable, recalculationCounter, unstableMarker);
111
- return renderedResult;
112
- }
93
+ const flightRecorder = env.driverKit.flightRecorder;
94
+ const renderInfo = {
95
+ blockId: ctx.blockId,
96
+ key,
97
+ lambda: fh.handle,
98
+ sandboxMemoryLimit: QUICK_JS_MEMORY_LIMIT,
99
+ getStats: () => ({ ...rCtx.stats })
113
100
  };
101
+ return (0, _milaboratories_pl_flight_recorder.recordModelRenderSync)(flightRecorder, renderInfo, () => {
102
+ rCtx.evaluateBundle(code.content);
103
+ const result = rCtx.runCallback(fh.handle);
104
+ rCtx.resetComputableCtx();
105
+ const toBeResolved = rCtx.computableHelper.computablesToResolve;
106
+ if (Object.keys(toBeResolved).length === 0) {
107
+ const importedResult = rCtx.importObjectUniversal(result);
108
+ if (require_index.getDebugFlags().logJsExecStat) console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);
109
+ logOutputStatus(fh.handle, importedResult, cCtx.unstableMarker === void 0, -1, cCtx.unstableMarker);
110
+ return { ir: importedResult };
111
+ }
112
+ let recalculationCounter = 0;
113
+ if (require_index.getDebugFlags().logOutputStatus) console.log(`Output ${fh.handle} scaffold calculated (not all computables resolved yet).`);
114
+ keepVmAlive = true;
115
+ return {
116
+ ir: toBeResolved,
117
+ postprocessValue: (resolved, { unstableMarker, stable }) => {
118
+ recalculationCounter++;
119
+ return (0, _milaboratories_pl_flight_recorder.recordModelRenderSync)(flightRecorder, {
120
+ ...renderInfo,
121
+ recalculation: recalculationCounter
122
+ }, () => {
123
+ for (const [handle, value] of Object.entries(resolved)) rCtx.runCallback(handle, value);
124
+ const renderedResult = rCtx.importObjectUniversal(result);
125
+ if (require_index.getDebugFlags().logJsExecStat) console.log(`[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`);
126
+ logOutputStatus(fh.handle, renderedResult, stable, recalculationCounter, unstableMarker);
127
+ return renderedResult;
128
+ });
129
+ }
130
+ };
131
+ });
114
132
  } catch (e) {
115
133
  keepVmAlive = false;
116
134
  throw e;
@@ -124,7 +142,7 @@ function executeSingleLambda(quickJs, fh, codeWithInfo, ...args) {
124
142
  const scope = new quickjs_emscripten.Scope();
125
143
  try {
126
144
  const runtime = scope.manage(quickJs.newRuntime());
127
- runtime.setMemoryLimit(1024 * 1024 * 8);
145
+ runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);
128
146
  runtime.setMaxStackSize(1024 * 320);
129
147
  let deadlineSettings;
130
148
  runtime.setInterruptHandler(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["getDebugFlags","Computable","Scope","JsExecutionContext"],"sources":["../../src/js_render/index.ts"],"sourcesContent":["import type { MiddleLayerEnvironment } from \"../middle_layer/middle_layer\";\nimport type { BlockCodeWithInfo, ConfigRenderLambda } from \"@platforma-sdk/model\";\nimport type { ComputableRenderingOps } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { Scope } from \"quickjs-emscripten\";\nimport type { DeadlineSettings } from \"./context\";\nimport { JsExecutionContext } from \"./context\";\nimport type { BlockContextAny } from \"../middle_layer/block_ctx\";\nimport { getDebugFlags } from \"../debug\";\n\nfunction logOutputStatus(\n handle: string,\n renderedResult: unknown,\n stable: boolean,\n recalculationCounter: number,\n unstableMarker?: string,\n) {\n if (getDebugFlags().logOutputStatus && (getDebugFlags().logOutputStatus === \"any\" || !stable)) {\n if (stable)\n console.log(\n `Stable output ${handle} calculated ${renderedResult !== undefined ? \"defined\" : \"undefined\"}; (#${recalculationCounter})`,\n );\n else\n console.log(\n `Unstable output ${handle}; marker = ${unstableMarker}; ${renderedResult !== undefined ? \"defined\" : \"undefined\"} (#${recalculationCounter})`,\n );\n }\n}\n\n/**\n * Creates a Computable that executes a render function (`fh`) from a block's code in a QuickJS virtual machine.\n * This function handles both synchronous and asynchronous execution patterns of the sandboxed JS code.\n *\n * The overall data flow is as follows:\n * 1. A QuickJS VM is initialized.\n * 2. A `JsExecutionContext` is created to bridge the host (TypeScript) and guest (QuickJS) environments. It injects a\n * context object (`cfgRenderCtx`) into the VM, providing helper methods for the sandboxed code to interact with the\n * platform (e.g., to request data).\n * 3. The block's Javascript bundle is evaluated in the VM.\n * 4. The specified render function (`fh.handle`) is executed.\n *\n * Two execution paths are possible depending on the behavior of the render function:\n *\n * ### Synchronous Path\n * If the render function computes its result without requesting any external data requiring asynchronous calculations,\n * it executes synchronously.\n * - The `computablesToResolve` map in `JsExecutionContext` remains empty.\n * - The function returns an object with an `ir` field holding the result (`{ ir: importedResult }`).\n * Since `postprocessValue` is not specified, `ir` is treated as the final resolved value of the Computable.\n * - The QuickJS VM is disposed of immediately as it's no longer needed.\n *\n * ### Asynchronous Path (with `postprocessValue`)\n * If the render function needs external data requiring asynchronous calculations (e.g., fetching a file), it calls\n * one of the injected helper methods. These methods don't return data directly. Instead, they:\n * a. Create a new `Computable` for the data request (e.g., to fetch a blob).\n * b. Register this new `Computable` in the `computablesToResolve` map.\n * c. Return a handle (string) to the sandboxed code.\n *\n * In this case:\n * - The initial execution of the render function returns a scaffold of the final result, which depends on the pending\n * computables.\n * - The `computablesToResolve` map is passed as the `ir` (initial result) to `Computable.makeRaw`.\n * - The `postprocessValue` function is provided to handle the results once the computables are resolved.\n * - The QuickJS VM is kept alive (`keepVmAlive = true`) because its state is needed in `postprocessValue`.\n * - Once the `computable` framework resolves all dependencies, it calls `postprocessValue` with the resolved data.\n * - `postprocessValue` feeds the resolved data back into the VM, allowing the sandboxed code to compute the final\n * result.\n * - The VM is eventually disposed of when the host Computable is destroyed.\n *\n * @param env The middle layer environment.\n * @param ctx The block context.\n * @param fh The config render lambda to execute.\n * @param codeWithInfo The block's code and feature flags.\n * @param configKey A key for the configuration, used for cache busting.\n * @param ops Options for the computable.\n * @returns A `Computable` that will resolve to the result of the lambda execution.\n */\nexport function computableFromRF(\n env: MiddleLayerEnvironment,\n ctx: BlockContextAny,\n fh: ConfigRenderLambda,\n codeWithInfo: BlockCodeWithInfo,\n configKey: string,\n ops: Partial<ComputableRenderingOps> = {},\n): Computable<unknown> {\n // adding configKey to reload all outputs on block-pack update\n const key = `${ctx.blockId}#lambda#${configKey}#${fh.handle}`;\n ops = { ...ops, key };\n if (ops.mode === undefined && fh.retentive === true) ops.mode = \"StableOnlyRetentive\";\n return Computable.makeRaw((cCtx) => {\n const { code, featureFlags } = codeWithInfo;\n\n if (getDebugFlags().logOutputRecalculations)\n console.log(\n `Block lambda recalculation : ${key} (${cCtx.changeSourceMarker}; ${cCtx.bodyInvocations} invocations)`,\n );\n\n const scope = new Scope();\n let keepVmAlive = false;\n cCtx.addOnDestroy(() => {\n // If keepVmAlive is false, the scope will be disposed by the finally block,\n // no need to dispose it here.\n if (keepVmAlive) scope.dispose();\n });\n\n try {\n const runtime = scope.manage(env.quickJs.newRuntime());\n runtime.setMemoryLimit(1024 * 1024 * 8);\n runtime.setMaxStackSize(1024 * 320);\n\n let deadlineSettings: DeadlineSettings | undefined;\n runtime.setInterruptHandler(() => {\n if (deadlineSettings === undefined) return false;\n if (Date.now() > deadlineSettings.deadline) return true;\n return false;\n });\n const vm = scope.manage(runtime.newContext());\n const rCtx = new JsExecutionContext(\n scope,\n vm,\n (s) => {\n deadlineSettings = s;\n },\n featureFlags,\n { computableCtx: cCtx, blockCtx: ctx, mlEnv: env },\n );\n\n rCtx.evaluateBundle(code.content);\n const result = rCtx.runCallback(fh.handle);\n\n rCtx.resetComputableCtx();\n\n const toBeResolved = rCtx.computableHelper!.computablesToResolve;\n\n if (Object.keys(toBeResolved).length === 0) {\n const importedResult = rCtx.importObjectUniversal(result);\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);\n logOutputStatus(\n fh.handle,\n importedResult,\n cCtx.unstableMarker === undefined,\n -1,\n cCtx.unstableMarker,\n );\n return { ir: importedResult };\n }\n\n let recalculationCounter = 0;\n if (getDebugFlags().logOutputStatus)\n console.log(`Output ${fh.handle} scaffold calculated (not all computables resolved yet).`);\n keepVmAlive = true;\n\n return {\n ir: toBeResolved,\n postprocessValue: (resolved: Record<string, unknown>, { unstableMarker, stable }) => {\n // resolving futures\n for (const [handle, value] of Object.entries(resolved)) rCtx.runCallback(handle, value);\n\n // rendering result\n const renderedResult = rCtx.importObjectUniversal(result);\n\n // logging\n recalculationCounter++;\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`);\n logOutputStatus(fh.handle, renderedResult, stable, recalculationCounter, unstableMarker);\n\n return renderedResult;\n },\n };\n } catch (e) {\n keepVmAlive = false;\n throw e;\n } finally {\n if (!keepVmAlive) scope.dispose();\n }\n }, ops);\n}\n\nexport function executeSingleLambda(\n quickJs: QuickJSWASMModule,\n fh: ConfigRenderLambda,\n codeWithInfo: BlockCodeWithInfo,\n ...args: unknown[]\n): unknown {\n const { code, featureFlags } = codeWithInfo;\n const scope = new Scope();\n try {\n const runtime = scope.manage(quickJs.newRuntime());\n runtime.setMemoryLimit(1024 * 1024 * 8);\n runtime.setMaxStackSize(1024 * 320);\n\n let deadlineSettings: DeadlineSettings | undefined;\n runtime.setInterruptHandler(() => {\n if (deadlineSettings === undefined) return false;\n if (Date.now() > deadlineSettings.deadline) return true;\n return false;\n });\n const vm = scope.manage(runtime.newContext());\n const rCtx = new JsExecutionContext(\n scope,\n vm,\n (s) => {\n deadlineSettings = s;\n },\n featureFlags,\n );\n\n // Initializing the model\n rCtx.evaluateBundle(code.content);\n\n // Running the lambda with arguments (e.g., state for args(), args for enrichmentTargets())\n const importedResult = rCtx.importObjectUniversal(rCtx.runCallback(fh.handle, ...args));\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${fh.handle}: ${JSON.stringify(rCtx.stats)}`);\n return importedResult;\n } finally {\n scope.dispose();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["getDebugFlags","Computable","Scope","JsExecutionContext"],"sources":["../../src/js_render/index.ts"],"sourcesContent":["import type { MiddleLayerEnvironment } from \"../middle_layer/middle_layer\";\nimport type { BlockCodeWithInfo, ConfigRenderLambda } from \"@platforma-sdk/model\";\nimport type { ComputableRenderingOps } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { Scope } from \"quickjs-emscripten\";\nimport type { DeadlineSettings } from \"./context\";\nimport { JsExecutionContext } from \"./context\";\nimport type { BlockContextAny } from \"../middle_layer/block_ctx\";\nimport { getDebugFlags } from \"../debug\";\nimport { recordModelRenderSync } from \"@milaboratories/pl-flight-recorder\";\n\n/** Memory ceiling applied to every QuickJS runtime that evaluates model code. */\nconst QUICK_JS_MEMORY_LIMIT = 1024 * 1024 * 8;\n\nfunction logOutputStatus(\n handle: string,\n renderedResult: unknown,\n stable: boolean,\n recalculationCounter: number,\n unstableMarker?: string,\n) {\n if (getDebugFlags().logOutputStatus && (getDebugFlags().logOutputStatus === \"any\" || !stable)) {\n if (stable)\n console.log(\n `Stable output ${handle} calculated ${renderedResult !== undefined ? \"defined\" : \"undefined\"}; (#${recalculationCounter})`,\n );\n else\n console.log(\n `Unstable output ${handle}; marker = ${unstableMarker}; ${renderedResult !== undefined ? \"defined\" : \"undefined\"} (#${recalculationCounter})`,\n );\n }\n}\n\n/**\n * Creates a Computable that executes a render function (`fh`) from a block's code in a QuickJS virtual machine.\n * This function handles both synchronous and asynchronous execution patterns of the sandboxed JS code.\n *\n * The overall data flow is as follows:\n * 1. A QuickJS VM is initialized.\n * 2. A `JsExecutionContext` is created to bridge the host (TypeScript) and guest (QuickJS) environments. It injects a\n * context object (`cfgRenderCtx`) into the VM, providing helper methods for the sandboxed code to interact with the\n * platform (e.g., to request data).\n * 3. The block's Javascript bundle is evaluated in the VM.\n * 4. The specified render function (`fh.handle`) is executed.\n *\n * Two execution paths are possible depending on the behavior of the render function:\n *\n * ### Synchronous Path\n * If the render function computes its result without requesting any external data requiring asynchronous calculations,\n * it executes synchronously.\n * - The `computablesToResolve` map in `JsExecutionContext` remains empty.\n * - The function returns an object with an `ir` field holding the result (`{ ir: importedResult }`).\n * Since `postprocessValue` is not specified, `ir` is treated as the final resolved value of the Computable.\n * - The QuickJS VM is disposed of immediately as it's no longer needed.\n *\n * ### Asynchronous Path (with `postprocessValue`)\n * If the render function needs external data requiring asynchronous calculations (e.g., fetching a file), it calls\n * one of the injected helper methods. These methods don't return data directly. Instead, they:\n * a. Create a new `Computable` for the data request (e.g., to fetch a blob).\n * b. Register this new `Computable` in the `computablesToResolve` map.\n * c. Return a handle (string) to the sandboxed code.\n *\n * In this case:\n * - The initial execution of the render function returns a scaffold of the final result, which depends on the pending\n * computables.\n * - The `computablesToResolve` map is passed as the `ir` (initial result) to `Computable.makeRaw`.\n * - The `postprocessValue` function is provided to handle the results once the computables are resolved.\n * - The QuickJS VM is kept alive (`keepVmAlive = true`) because its state is needed in `postprocessValue`.\n * - Once the `computable` framework resolves all dependencies, it calls `postprocessValue` with the resolved data.\n * - `postprocessValue` feeds the resolved data back into the VM, allowing the sandboxed code to compute the final\n * result.\n * - The VM is eventually disposed of when the host Computable is destroyed.\n *\n * @param env The middle layer environment.\n * @param ctx The block context.\n * @param fh The config render lambda to execute.\n * @param codeWithInfo The block's code and feature flags.\n * @param configKey A key for the configuration, used for cache busting.\n * @param ops Options for the computable.\n * @returns A `Computable` that will resolve to the result of the lambda execution.\n */\nexport function computableFromRF(\n env: MiddleLayerEnvironment,\n ctx: BlockContextAny,\n fh: ConfigRenderLambda,\n codeWithInfo: BlockCodeWithInfo,\n configKey: string,\n ops: Partial<ComputableRenderingOps> = {},\n): Computable<unknown> {\n // adding configKey to reload all outputs on block-pack update\n const key = `${ctx.blockId}#lambda#${configKey}#${fh.handle}`;\n ops = { ...ops, key };\n if (ops.mode === undefined && fh.retentive === true) ops.mode = \"StableOnlyRetentive\";\n return Computable.makeRaw((cCtx) => {\n const { code, featureFlags } = codeWithInfo;\n\n if (getDebugFlags().logOutputRecalculations)\n console.log(\n `Block lambda recalculation : ${key} (${cCtx.changeSourceMarker}; ${cCtx.bodyInvocations} invocations)`,\n );\n\n const scope = new Scope();\n let keepVmAlive = false;\n cCtx.addOnDestroy(() => {\n // If keepVmAlive is false, the scope will be disposed by the finally block,\n // no need to dispose it here.\n if (keepVmAlive) scope.dispose();\n });\n\n try {\n const runtime = scope.manage(env.quickJs.newRuntime());\n runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);\n runtime.setMaxStackSize(1024 * 320);\n\n let deadlineSettings: DeadlineSettings | undefined;\n runtime.setInterruptHandler(() => {\n if (deadlineSettings === undefined) return false;\n if (Date.now() > deadlineSettings.deadline) return true;\n return false;\n });\n const vm = scope.manage(runtime.newContext());\n const rCtx = new JsExecutionContext(\n scope,\n vm,\n (s) => {\n deadlineSettings = s;\n },\n featureFlags,\n { computableCtx: cCtx, blockCtx: ctx, mlEnv: env },\n );\n\n const flightRecorder = env.driverKit.flightRecorder;\n const renderInfo = {\n blockId: ctx.blockId,\n key,\n lambda: fh.handle,\n // The sandbox has its own 8 MB ceiling, so the model's own objects can\n // never exhaust the host heap. What can is the volume crossing out of\n // it, which `stats` counts.\n sandboxMemoryLimit: QUICK_JS_MEMORY_LIMIT,\n getStats: () => ({ ...rCtx.stats }),\n };\n\n return recordModelRenderSync(flightRecorder, renderInfo, () => {\n rCtx.evaluateBundle(code.content);\n const result = rCtx.runCallback(fh.handle);\n\n rCtx.resetComputableCtx();\n\n const toBeResolved = rCtx.computableHelper!.computablesToResolve;\n\n if (Object.keys(toBeResolved).length === 0) {\n const importedResult = rCtx.importObjectUniversal(result);\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);\n logOutputStatus(\n fh.handle,\n importedResult,\n cCtx.unstableMarker === undefined,\n -1,\n cCtx.unstableMarker,\n );\n return { ir: importedResult };\n }\n\n let recalculationCounter = 0;\n if (getDebugFlags().logOutputStatus)\n console.log(\n `Output ${fh.handle} scaffold calculated (not all computables resolved yet).`,\n );\n keepVmAlive = true;\n\n return {\n ir: toBeResolved,\n postprocessValue: (resolved: Record<string, unknown>, { unstableMarker, stable }) => {\n recalculationCounter++;\n // A deferred render resumes here, potentially many times, and each\n // resumption can build joins of its own, so each gets its own span.\n return recordModelRenderSync(\n flightRecorder,\n { ...renderInfo, recalculation: recalculationCounter },\n () => {\n // resolving futures\n for (const [handle, value] of Object.entries(resolved))\n rCtx.runCallback(handle, value);\n\n // rendering result\n const renderedResult = rCtx.importObjectUniversal(result);\n\n // logging\n if (getDebugFlags().logJsExecStat)\n console.log(\n `[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`,\n );\n logOutputStatus(\n fh.handle,\n renderedResult,\n stable,\n recalculationCounter,\n unstableMarker,\n );\n\n return renderedResult;\n },\n );\n },\n };\n });\n } catch (e) {\n keepVmAlive = false;\n throw e;\n } finally {\n if (!keepVmAlive) scope.dispose();\n }\n }, ops);\n}\n\nexport function executeSingleLambda(\n quickJs: QuickJSWASMModule,\n fh: ConfigRenderLambda,\n codeWithInfo: BlockCodeWithInfo,\n ...args: unknown[]\n): unknown {\n const { code, featureFlags } = codeWithInfo;\n const scope = new Scope();\n try {\n const runtime = scope.manage(quickJs.newRuntime());\n runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);\n runtime.setMaxStackSize(1024 * 320);\n\n let deadlineSettings: DeadlineSettings | undefined;\n runtime.setInterruptHandler(() => {\n if (deadlineSettings === undefined) return false;\n if (Date.now() > deadlineSettings.deadline) return true;\n return false;\n });\n const vm = scope.manage(runtime.newContext());\n const rCtx = new JsExecutionContext(\n scope,\n vm,\n (s) => {\n deadlineSettings = s;\n },\n featureFlags,\n );\n\n // Initializing the model\n rCtx.evaluateBundle(code.content);\n\n // Running the lambda with arguments (e.g., state for args(), args for enrichmentTargets())\n const importedResult = rCtx.importObjectUniversal(rCtx.runCallback(fh.handle, ...args));\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${fh.handle}: ${JSON.stringify(rCtx.stats)}`);\n return importedResult;\n } finally {\n scope.dispose();\n }\n}\n"],"mappings":"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"}
@@ -2,7 +2,10 @@ import { getDebugFlags } from "../debug/index.js";
2
2
  import { JsExecutionContext } from "./context.js";
3
3
  import { Computable } from "@milaboratories/computable";
4
4
  import { Scope } from "quickjs-emscripten";
5
+ import { recordModelRenderSync } from "@milaboratories/pl-flight-recorder";
5
6
  //#region src/js_render/index.ts
7
+ /** Memory ceiling applied to every QuickJS runtime that evaluates model code. */
8
+ const QUICK_JS_MEMORY_LIMIT = 1024 * 1024 * 8;
6
9
  function logOutputStatus(handle, renderedResult, stable, recalculationCounter, unstableMarker) {
7
10
  if (getDebugFlags().logOutputStatus && (getDebugFlags().logOutputStatus === "any" || !stable)) if (stable) console.log(`Stable output ${handle} calculated ${renderedResult !== void 0 ? "defined" : "undefined"}; (#${recalculationCounter})`);
8
11
  else console.log(`Unstable output ${handle}; marker = ${unstableMarker}; ${renderedResult !== void 0 ? "defined" : "undefined"} (#${recalculationCounter})`);
@@ -72,7 +75,7 @@ function computableFromRF(env, ctx, fh, codeWithInfo, configKey, ops = {}) {
72
75
  });
73
76
  try {
74
77
  const runtime = scope.manage(env.quickJs.newRuntime());
75
- runtime.setMemoryLimit(1024 * 1024 * 8);
78
+ runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);
76
79
  runtime.setMaxStackSize(1024 * 320);
77
80
  let deadlineSettings;
78
81
  runtime.setInterruptHandler(() => {
@@ -87,30 +90,45 @@ function computableFromRF(env, ctx, fh, codeWithInfo, configKey, ops = {}) {
87
90
  blockCtx: ctx,
88
91
  mlEnv: env
89
92
  });
90
- rCtx.evaluateBundle(code.content);
91
- const result = rCtx.runCallback(fh.handle);
92
- rCtx.resetComputableCtx();
93
- const toBeResolved = rCtx.computableHelper.computablesToResolve;
94
- if (Object.keys(toBeResolved).length === 0) {
95
- const importedResult = rCtx.importObjectUniversal(result);
96
- if (getDebugFlags().logJsExecStat) console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);
97
- logOutputStatus(fh.handle, importedResult, cCtx.unstableMarker === void 0, -1, cCtx.unstableMarker);
98
- return { ir: importedResult };
99
- }
100
- let recalculationCounter = 0;
101
- if (getDebugFlags().logOutputStatus) console.log(`Output ${fh.handle} scaffold calculated (not all computables resolved yet).`);
102
- keepVmAlive = true;
103
- return {
104
- ir: toBeResolved,
105
- postprocessValue: (resolved, { unstableMarker, stable }) => {
106
- for (const [handle, value] of Object.entries(resolved)) rCtx.runCallback(handle, value);
107
- const renderedResult = rCtx.importObjectUniversal(result);
108
- recalculationCounter++;
109
- if (getDebugFlags().logJsExecStat) console.log(`[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`);
110
- logOutputStatus(fh.handle, renderedResult, stable, recalculationCounter, unstableMarker);
111
- return renderedResult;
112
- }
93
+ const flightRecorder = env.driverKit.flightRecorder;
94
+ const renderInfo = {
95
+ blockId: ctx.blockId,
96
+ key,
97
+ lambda: fh.handle,
98
+ sandboxMemoryLimit: QUICK_JS_MEMORY_LIMIT,
99
+ getStats: () => ({ ...rCtx.stats })
113
100
  };
101
+ return recordModelRenderSync(flightRecorder, renderInfo, () => {
102
+ rCtx.evaluateBundle(code.content);
103
+ const result = rCtx.runCallback(fh.handle);
104
+ rCtx.resetComputableCtx();
105
+ const toBeResolved = rCtx.computableHelper.computablesToResolve;
106
+ if (Object.keys(toBeResolved).length === 0) {
107
+ const importedResult = rCtx.importObjectUniversal(result);
108
+ if (getDebugFlags().logJsExecStat) console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);
109
+ logOutputStatus(fh.handle, importedResult, cCtx.unstableMarker === void 0, -1, cCtx.unstableMarker);
110
+ return { ir: importedResult };
111
+ }
112
+ let recalculationCounter = 0;
113
+ if (getDebugFlags().logOutputStatus) console.log(`Output ${fh.handle} scaffold calculated (not all computables resolved yet).`);
114
+ keepVmAlive = true;
115
+ return {
116
+ ir: toBeResolved,
117
+ postprocessValue: (resolved, { unstableMarker, stable }) => {
118
+ recalculationCounter++;
119
+ return recordModelRenderSync(flightRecorder, {
120
+ ...renderInfo,
121
+ recalculation: recalculationCounter
122
+ }, () => {
123
+ for (const [handle, value] of Object.entries(resolved)) rCtx.runCallback(handle, value);
124
+ const renderedResult = rCtx.importObjectUniversal(result);
125
+ if (getDebugFlags().logJsExecStat) console.log(`[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`);
126
+ logOutputStatus(fh.handle, renderedResult, stable, recalculationCounter, unstableMarker);
127
+ return renderedResult;
128
+ });
129
+ }
130
+ };
131
+ });
114
132
  } catch (e) {
115
133
  keepVmAlive = false;
116
134
  throw e;
@@ -124,7 +142,7 @@ function executeSingleLambda(quickJs, fh, codeWithInfo, ...args) {
124
142
  const scope = new Scope();
125
143
  try {
126
144
  const runtime = scope.manage(quickJs.newRuntime());
127
- runtime.setMemoryLimit(1024 * 1024 * 8);
145
+ runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);
128
146
  runtime.setMaxStackSize(1024 * 320);
129
147
  let deadlineSettings;
130
148
  runtime.setInterruptHandler(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/js_render/index.ts"],"sourcesContent":["import type { MiddleLayerEnvironment } from \"../middle_layer/middle_layer\";\nimport type { BlockCodeWithInfo, ConfigRenderLambda } from \"@platforma-sdk/model\";\nimport type { ComputableRenderingOps } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { Scope } from \"quickjs-emscripten\";\nimport type { DeadlineSettings } from \"./context\";\nimport { JsExecutionContext } from \"./context\";\nimport type { BlockContextAny } from \"../middle_layer/block_ctx\";\nimport { getDebugFlags } from \"../debug\";\n\nfunction logOutputStatus(\n handle: string,\n renderedResult: unknown,\n stable: boolean,\n recalculationCounter: number,\n unstableMarker?: string,\n) {\n if (getDebugFlags().logOutputStatus && (getDebugFlags().logOutputStatus === \"any\" || !stable)) {\n if (stable)\n console.log(\n `Stable output ${handle} calculated ${renderedResult !== undefined ? \"defined\" : \"undefined\"}; (#${recalculationCounter})`,\n );\n else\n console.log(\n `Unstable output ${handle}; marker = ${unstableMarker}; ${renderedResult !== undefined ? \"defined\" : \"undefined\"} (#${recalculationCounter})`,\n );\n }\n}\n\n/**\n * Creates a Computable that executes a render function (`fh`) from a block's code in a QuickJS virtual machine.\n * This function handles both synchronous and asynchronous execution patterns of the sandboxed JS code.\n *\n * The overall data flow is as follows:\n * 1. A QuickJS VM is initialized.\n * 2. A `JsExecutionContext` is created to bridge the host (TypeScript) and guest (QuickJS) environments. It injects a\n * context object (`cfgRenderCtx`) into the VM, providing helper methods for the sandboxed code to interact with the\n * platform (e.g., to request data).\n * 3. The block's Javascript bundle is evaluated in the VM.\n * 4. The specified render function (`fh.handle`) is executed.\n *\n * Two execution paths are possible depending on the behavior of the render function:\n *\n * ### Synchronous Path\n * If the render function computes its result without requesting any external data requiring asynchronous calculations,\n * it executes synchronously.\n * - The `computablesToResolve` map in `JsExecutionContext` remains empty.\n * - The function returns an object with an `ir` field holding the result (`{ ir: importedResult }`).\n * Since `postprocessValue` is not specified, `ir` is treated as the final resolved value of the Computable.\n * - The QuickJS VM is disposed of immediately as it's no longer needed.\n *\n * ### Asynchronous Path (with `postprocessValue`)\n * If the render function needs external data requiring asynchronous calculations (e.g., fetching a file), it calls\n * one of the injected helper methods. These methods don't return data directly. Instead, they:\n * a. Create a new `Computable` for the data request (e.g., to fetch a blob).\n * b. Register this new `Computable` in the `computablesToResolve` map.\n * c. Return a handle (string) to the sandboxed code.\n *\n * In this case:\n * - The initial execution of the render function returns a scaffold of the final result, which depends on the pending\n * computables.\n * - The `computablesToResolve` map is passed as the `ir` (initial result) to `Computable.makeRaw`.\n * - The `postprocessValue` function is provided to handle the results once the computables are resolved.\n * - The QuickJS VM is kept alive (`keepVmAlive = true`) because its state is needed in `postprocessValue`.\n * - Once the `computable` framework resolves all dependencies, it calls `postprocessValue` with the resolved data.\n * - `postprocessValue` feeds the resolved data back into the VM, allowing the sandboxed code to compute the final\n * result.\n * - The VM is eventually disposed of when the host Computable is destroyed.\n *\n * @param env The middle layer environment.\n * @param ctx The block context.\n * @param fh The config render lambda to execute.\n * @param codeWithInfo The block's code and feature flags.\n * @param configKey A key for the configuration, used for cache busting.\n * @param ops Options for the computable.\n * @returns A `Computable` that will resolve to the result of the lambda execution.\n */\nexport function computableFromRF(\n env: MiddleLayerEnvironment,\n ctx: BlockContextAny,\n fh: ConfigRenderLambda,\n codeWithInfo: BlockCodeWithInfo,\n configKey: string,\n ops: Partial<ComputableRenderingOps> = {},\n): Computable<unknown> {\n // adding configKey to reload all outputs on block-pack update\n const key = `${ctx.blockId}#lambda#${configKey}#${fh.handle}`;\n ops = { ...ops, key };\n if (ops.mode === undefined && fh.retentive === true) ops.mode = \"StableOnlyRetentive\";\n return Computable.makeRaw((cCtx) => {\n const { code, featureFlags } = codeWithInfo;\n\n if (getDebugFlags().logOutputRecalculations)\n console.log(\n `Block lambda recalculation : ${key} (${cCtx.changeSourceMarker}; ${cCtx.bodyInvocations} invocations)`,\n );\n\n const scope = new Scope();\n let keepVmAlive = false;\n cCtx.addOnDestroy(() => {\n // If keepVmAlive is false, the scope will be disposed by the finally block,\n // no need to dispose it here.\n if (keepVmAlive) scope.dispose();\n });\n\n try {\n const runtime = scope.manage(env.quickJs.newRuntime());\n runtime.setMemoryLimit(1024 * 1024 * 8);\n runtime.setMaxStackSize(1024 * 320);\n\n let deadlineSettings: DeadlineSettings | undefined;\n runtime.setInterruptHandler(() => {\n if (deadlineSettings === undefined) return false;\n if (Date.now() > deadlineSettings.deadline) return true;\n return false;\n });\n const vm = scope.manage(runtime.newContext());\n const rCtx = new JsExecutionContext(\n scope,\n vm,\n (s) => {\n deadlineSettings = s;\n },\n featureFlags,\n { computableCtx: cCtx, blockCtx: ctx, mlEnv: env },\n );\n\n rCtx.evaluateBundle(code.content);\n const result = rCtx.runCallback(fh.handle);\n\n rCtx.resetComputableCtx();\n\n const toBeResolved = rCtx.computableHelper!.computablesToResolve;\n\n if (Object.keys(toBeResolved).length === 0) {\n const importedResult = rCtx.importObjectUniversal(result);\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);\n logOutputStatus(\n fh.handle,\n importedResult,\n cCtx.unstableMarker === undefined,\n -1,\n cCtx.unstableMarker,\n );\n return { ir: importedResult };\n }\n\n let recalculationCounter = 0;\n if (getDebugFlags().logOutputStatus)\n console.log(`Output ${fh.handle} scaffold calculated (not all computables resolved yet).`);\n keepVmAlive = true;\n\n return {\n ir: toBeResolved,\n postprocessValue: (resolved: Record<string, unknown>, { unstableMarker, stable }) => {\n // resolving futures\n for (const [handle, value] of Object.entries(resolved)) rCtx.runCallback(handle, value);\n\n // rendering result\n const renderedResult = rCtx.importObjectUniversal(result);\n\n // logging\n recalculationCounter++;\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`);\n logOutputStatus(fh.handle, renderedResult, stable, recalculationCounter, unstableMarker);\n\n return renderedResult;\n },\n };\n } catch (e) {\n keepVmAlive = false;\n throw e;\n } finally {\n if (!keepVmAlive) scope.dispose();\n }\n }, ops);\n}\n\nexport function executeSingleLambda(\n quickJs: QuickJSWASMModule,\n fh: ConfigRenderLambda,\n codeWithInfo: BlockCodeWithInfo,\n ...args: unknown[]\n): unknown {\n const { code, featureFlags } = codeWithInfo;\n const scope = new Scope();\n try {\n const runtime = scope.manage(quickJs.newRuntime());\n runtime.setMemoryLimit(1024 * 1024 * 8);\n runtime.setMaxStackSize(1024 * 320);\n\n let deadlineSettings: DeadlineSettings | undefined;\n runtime.setInterruptHandler(() => {\n if (deadlineSettings === undefined) return false;\n if (Date.now() > deadlineSettings.deadline) return true;\n return false;\n });\n const vm = scope.manage(runtime.newContext());\n const rCtx = new JsExecutionContext(\n scope,\n vm,\n (s) => {\n deadlineSettings = s;\n },\n featureFlags,\n );\n\n // Initializing the model\n rCtx.evaluateBundle(code.content);\n\n // Running the lambda with arguments (e.g., state for args(), args for enrichmentTargets())\n const importedResult = rCtx.importObjectUniversal(rCtx.runCallback(fh.handle, ...args));\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${fh.handle}: ${JSON.stringify(rCtx.stats)}`);\n return importedResult;\n } finally {\n scope.dispose();\n }\n}\n"],"mappings":";;;;;AAWA,SAAS,gBACP,QACA,gBACA,QACA,sBACA,gBACA;CACA,IAAI,cAAc,CAAC,CAAC,oBAAoB,cAAc,CAAC,CAAC,oBAAoB,SAAS,CAAC,SACpF,IAAI,QACF,QAAQ,IACN,iBAAiB,OAAO,cAAc,mBAAmB,KAAA,IAAY,YAAY,YAAY,MAAM,qBAAqB,EAC1H;MAEA,QAAQ,IACN,mBAAmB,OAAO,aAAa,eAAe,IAAI,mBAAmB,KAAA,IAAY,YAAY,YAAY,KAAK,qBAAqB,EAC7I;AAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAgB,iBACd,KACA,KACA,IACA,cACA,WACA,MAAuC,CAAC,GACnB;CAErB,MAAM,MAAM,GAAG,IAAI,QAAQ,UAAU,UAAU,GAAG,GAAG;CACrD,MAAM;EAAE,GAAG;EAAK;CAAI;CACpB,IAAI,IAAI,SAAS,KAAA,KAAa,GAAG,cAAc,MAAM,IAAI,OAAO;CAChE,OAAO,WAAW,SAAS,SAAS;EAClC,MAAM,EAAE,MAAM,iBAAiB;EAE/B,IAAI,cAAc,CAAC,CAAC,yBAClB,QAAQ,IACN,gCAAgC,IAAI,IAAI,KAAK,mBAAmB,IAAI,KAAK,gBAAgB,cAC3F;EAEF,MAAM,QAAQ,IAAI,MAAM;EACxB,IAAI,cAAc;EAClB,KAAK,mBAAmB;GAGtB,IAAI,aAAa,MAAM,QAAQ;EACjC,CAAC;EAED,IAAI;GACF,MAAM,UAAU,MAAM,OAAO,IAAI,QAAQ,WAAW,CAAC;GACrD,QAAQ,eAAe,OAAO,OAAO,CAAC;GACtC,QAAQ,gBAAgB,OAAO,GAAG;GAElC,IAAI;GACJ,QAAQ,0BAA0B;IAChC,IAAI,qBAAqB,KAAA,GAAW,OAAO;IAC3C,IAAI,KAAK,IAAI,IAAI,iBAAiB,UAAU,OAAO;IACnD,OAAO;GACT,CAAC;GAED,MAAM,OAAO,IAAI,mBACf,OAFS,MAAM,OAAO,QAAQ,WAAW,CAGxC,IACA,MAAM;IACL,mBAAmB;GACrB,GACA,cACA;IAAE,eAAe;IAAM,UAAU;IAAK,OAAO;GAAI,CACnD;GAEA,KAAK,eAAe,KAAK,OAAO;GAChC,MAAM,SAAS,KAAK,YAAY,GAAG,MAAM;GAEzC,KAAK,mBAAmB;GAExB,MAAM,eAAe,KAAK,iBAAkB;GAE5C,IAAI,OAAO,KAAK,YAAY,CAAC,CAAC,WAAW,GAAG;IAC1C,MAAM,iBAAiB,KAAK,sBAAsB,MAAM;IACxD,IAAI,cAAc,CAAC,CAAC,eAClB,QAAQ,IAAI,YAAY,IAAI,IAAI,KAAK,UAAU,KAAK,KAAK,GAAG;IAC9D,gBACE,GAAG,QACH,gBACA,KAAK,mBAAmB,KAAA,GACxB,IACA,KAAK,cACP;IACA,OAAO,EAAE,IAAI,eAAe;GAC9B;GAEA,IAAI,uBAAuB;GAC3B,IAAI,cAAc,CAAC,CAAC,iBAClB,QAAQ,IAAI,UAAU,GAAG,OAAO,yDAAyD;GAC3F,cAAc;GAEd,OAAO;IACL,IAAI;IACJ,mBAAmB,UAAmC,EAAE,gBAAgB,aAAa;KAEnF,KAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,QAAQ,GAAG,KAAK,YAAY,QAAQ,KAAK;KAGtF,MAAM,iBAAiB,KAAK,sBAAsB,MAAM;KAGxD;KACA,IAAI,cAAc,CAAC,CAAC,eAClB,QAAQ,IAAI,YAAY,IAAI,IAAI,qBAAqB,IAAI,KAAK,UAAU,KAAK,KAAK,GAAG;KACvF,gBAAgB,GAAG,QAAQ,gBAAgB,QAAQ,sBAAsB,cAAc;KAEvF,OAAO;IACT;GACF;EACF,SAAS,GAAG;GACV,cAAc;GACd,MAAM;EACR,UAAU;GACR,IAAI,CAAC,aAAa,MAAM,QAAQ;EAClC;CACF,GAAG,GAAG;AACR;AAEA,SAAgB,oBACd,SACA,IACA,cACA,GAAG,MACM;CACT,MAAM,EAAE,MAAM,iBAAiB;CAC/B,MAAM,QAAQ,IAAI,MAAM;CACxB,IAAI;EACF,MAAM,UAAU,MAAM,OAAO,QAAQ,WAAW,CAAC;EACjD,QAAQ,eAAe,OAAO,OAAO,CAAC;EACtC,QAAQ,gBAAgB,OAAO,GAAG;EAElC,IAAI;EACJ,QAAQ,0BAA0B;GAChC,IAAI,qBAAqB,KAAA,GAAW,OAAO;GAC3C,IAAI,KAAK,IAAI,IAAI,iBAAiB,UAAU,OAAO;GACnD,OAAO;EACT,CAAC;EAED,MAAM,OAAO,IAAI,mBACf,OAFS,MAAM,OAAO,QAAQ,WAAW,CAGxC,IACA,MAAM;GACL,mBAAmB;EACrB,GACA,YACF;EAGA,KAAK,eAAe,KAAK,OAAO;EAGhC,MAAM,iBAAiB,KAAK,sBAAsB,KAAK,YAAY,GAAG,QAAQ,GAAG,IAAI,CAAC;EACtF,IAAI,cAAc,CAAC,CAAC,eAClB,QAAQ,IAAI,YAAY,GAAG,OAAO,IAAI,KAAK,UAAU,KAAK,KAAK,GAAG;EACpE,OAAO;CACT,UAAU;EACR,MAAM,QAAQ;CAChB;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/js_render/index.ts"],"sourcesContent":["import type { MiddleLayerEnvironment } from \"../middle_layer/middle_layer\";\nimport type { BlockCodeWithInfo, ConfigRenderLambda } from \"@platforma-sdk/model\";\nimport type { ComputableRenderingOps } from \"@milaboratories/computable\";\nimport { Computable } from \"@milaboratories/computable\";\nimport type { QuickJSWASMModule } from \"quickjs-emscripten\";\nimport { Scope } from \"quickjs-emscripten\";\nimport type { DeadlineSettings } from \"./context\";\nimport { JsExecutionContext } from \"./context\";\nimport type { BlockContextAny } from \"../middle_layer/block_ctx\";\nimport { getDebugFlags } from \"../debug\";\nimport { recordModelRenderSync } from \"@milaboratories/pl-flight-recorder\";\n\n/** Memory ceiling applied to every QuickJS runtime that evaluates model code. */\nconst QUICK_JS_MEMORY_LIMIT = 1024 * 1024 * 8;\n\nfunction logOutputStatus(\n handle: string,\n renderedResult: unknown,\n stable: boolean,\n recalculationCounter: number,\n unstableMarker?: string,\n) {\n if (getDebugFlags().logOutputStatus && (getDebugFlags().logOutputStatus === \"any\" || !stable)) {\n if (stable)\n console.log(\n `Stable output ${handle} calculated ${renderedResult !== undefined ? \"defined\" : \"undefined\"}; (#${recalculationCounter})`,\n );\n else\n console.log(\n `Unstable output ${handle}; marker = ${unstableMarker}; ${renderedResult !== undefined ? \"defined\" : \"undefined\"} (#${recalculationCounter})`,\n );\n }\n}\n\n/**\n * Creates a Computable that executes a render function (`fh`) from a block's code in a QuickJS virtual machine.\n * This function handles both synchronous and asynchronous execution patterns of the sandboxed JS code.\n *\n * The overall data flow is as follows:\n * 1. A QuickJS VM is initialized.\n * 2. A `JsExecutionContext` is created to bridge the host (TypeScript) and guest (QuickJS) environments. It injects a\n * context object (`cfgRenderCtx`) into the VM, providing helper methods for the sandboxed code to interact with the\n * platform (e.g., to request data).\n * 3. The block's Javascript bundle is evaluated in the VM.\n * 4. The specified render function (`fh.handle`) is executed.\n *\n * Two execution paths are possible depending on the behavior of the render function:\n *\n * ### Synchronous Path\n * If the render function computes its result without requesting any external data requiring asynchronous calculations,\n * it executes synchronously.\n * - The `computablesToResolve` map in `JsExecutionContext` remains empty.\n * - The function returns an object with an `ir` field holding the result (`{ ir: importedResult }`).\n * Since `postprocessValue` is not specified, `ir` is treated as the final resolved value of the Computable.\n * - The QuickJS VM is disposed of immediately as it's no longer needed.\n *\n * ### Asynchronous Path (with `postprocessValue`)\n * If the render function needs external data requiring asynchronous calculations (e.g., fetching a file), it calls\n * one of the injected helper methods. These methods don't return data directly. Instead, they:\n * a. Create a new `Computable` for the data request (e.g., to fetch a blob).\n * b. Register this new `Computable` in the `computablesToResolve` map.\n * c. Return a handle (string) to the sandboxed code.\n *\n * In this case:\n * - The initial execution of the render function returns a scaffold of the final result, which depends on the pending\n * computables.\n * - The `computablesToResolve` map is passed as the `ir` (initial result) to `Computable.makeRaw`.\n * - The `postprocessValue` function is provided to handle the results once the computables are resolved.\n * - The QuickJS VM is kept alive (`keepVmAlive = true`) because its state is needed in `postprocessValue`.\n * - Once the `computable` framework resolves all dependencies, it calls `postprocessValue` with the resolved data.\n * - `postprocessValue` feeds the resolved data back into the VM, allowing the sandboxed code to compute the final\n * result.\n * - The VM is eventually disposed of when the host Computable is destroyed.\n *\n * @param env The middle layer environment.\n * @param ctx The block context.\n * @param fh The config render lambda to execute.\n * @param codeWithInfo The block's code and feature flags.\n * @param configKey A key for the configuration, used for cache busting.\n * @param ops Options for the computable.\n * @returns A `Computable` that will resolve to the result of the lambda execution.\n */\nexport function computableFromRF(\n env: MiddleLayerEnvironment,\n ctx: BlockContextAny,\n fh: ConfigRenderLambda,\n codeWithInfo: BlockCodeWithInfo,\n configKey: string,\n ops: Partial<ComputableRenderingOps> = {},\n): Computable<unknown> {\n // adding configKey to reload all outputs on block-pack update\n const key = `${ctx.blockId}#lambda#${configKey}#${fh.handle}`;\n ops = { ...ops, key };\n if (ops.mode === undefined && fh.retentive === true) ops.mode = \"StableOnlyRetentive\";\n return Computable.makeRaw((cCtx) => {\n const { code, featureFlags } = codeWithInfo;\n\n if (getDebugFlags().logOutputRecalculations)\n console.log(\n `Block lambda recalculation : ${key} (${cCtx.changeSourceMarker}; ${cCtx.bodyInvocations} invocations)`,\n );\n\n const scope = new Scope();\n let keepVmAlive = false;\n cCtx.addOnDestroy(() => {\n // If keepVmAlive is false, the scope will be disposed by the finally block,\n // no need to dispose it here.\n if (keepVmAlive) scope.dispose();\n });\n\n try {\n const runtime = scope.manage(env.quickJs.newRuntime());\n runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);\n runtime.setMaxStackSize(1024 * 320);\n\n let deadlineSettings: DeadlineSettings | undefined;\n runtime.setInterruptHandler(() => {\n if (deadlineSettings === undefined) return false;\n if (Date.now() > deadlineSettings.deadline) return true;\n return false;\n });\n const vm = scope.manage(runtime.newContext());\n const rCtx = new JsExecutionContext(\n scope,\n vm,\n (s) => {\n deadlineSettings = s;\n },\n featureFlags,\n { computableCtx: cCtx, blockCtx: ctx, mlEnv: env },\n );\n\n const flightRecorder = env.driverKit.flightRecorder;\n const renderInfo = {\n blockId: ctx.blockId,\n key,\n lambda: fh.handle,\n // The sandbox has its own 8 MB ceiling, so the model's own objects can\n // never exhaust the host heap. What can is the volume crossing out of\n // it, which `stats` counts.\n sandboxMemoryLimit: QUICK_JS_MEMORY_LIMIT,\n getStats: () => ({ ...rCtx.stats }),\n };\n\n return recordModelRenderSync(flightRecorder, renderInfo, () => {\n rCtx.evaluateBundle(code.content);\n const result = rCtx.runCallback(fh.handle);\n\n rCtx.resetComputableCtx();\n\n const toBeResolved = rCtx.computableHelper!.computablesToResolve;\n\n if (Object.keys(toBeResolved).length === 0) {\n const importedResult = rCtx.importObjectUniversal(result);\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);\n logOutputStatus(\n fh.handle,\n importedResult,\n cCtx.unstableMarker === undefined,\n -1,\n cCtx.unstableMarker,\n );\n return { ir: importedResult };\n }\n\n let recalculationCounter = 0;\n if (getDebugFlags().logOutputStatus)\n console.log(\n `Output ${fh.handle} scaffold calculated (not all computables resolved yet).`,\n );\n keepVmAlive = true;\n\n return {\n ir: toBeResolved,\n postprocessValue: (resolved: Record<string, unknown>, { unstableMarker, stable }) => {\n recalculationCounter++;\n // A deferred render resumes here, potentially many times, and each\n // resumption can build joins of its own, so each gets its own span.\n return recordModelRenderSync(\n flightRecorder,\n { ...renderInfo, recalculation: recalculationCounter },\n () => {\n // resolving futures\n for (const [handle, value] of Object.entries(resolved))\n rCtx.runCallback(handle, value);\n\n // rendering result\n const renderedResult = rCtx.importObjectUniversal(result);\n\n // logging\n if (getDebugFlags().logJsExecStat)\n console.log(\n `[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`,\n );\n logOutputStatus(\n fh.handle,\n renderedResult,\n stable,\n recalculationCounter,\n unstableMarker,\n );\n\n return renderedResult;\n },\n );\n },\n };\n });\n } catch (e) {\n keepVmAlive = false;\n throw e;\n } finally {\n if (!keepVmAlive) scope.dispose();\n }\n }, ops);\n}\n\nexport function executeSingleLambda(\n quickJs: QuickJSWASMModule,\n fh: ConfigRenderLambda,\n codeWithInfo: BlockCodeWithInfo,\n ...args: unknown[]\n): unknown {\n const { code, featureFlags } = codeWithInfo;\n const scope = new Scope();\n try {\n const runtime = scope.manage(quickJs.newRuntime());\n runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);\n runtime.setMaxStackSize(1024 * 320);\n\n let deadlineSettings: DeadlineSettings | undefined;\n runtime.setInterruptHandler(() => {\n if (deadlineSettings === undefined) return false;\n if (Date.now() > deadlineSettings.deadline) return true;\n return false;\n });\n const vm = scope.manage(runtime.newContext());\n const rCtx = new JsExecutionContext(\n scope,\n vm,\n (s) => {\n deadlineSettings = s;\n },\n featureFlags,\n );\n\n // Initializing the model\n rCtx.evaluateBundle(code.content);\n\n // Running the lambda with arguments (e.g., state for args(), args for enrichmentTargets())\n const importedResult = rCtx.importObjectUniversal(rCtx.runCallback(fh.handle, ...args));\n if (getDebugFlags().logJsExecStat)\n console.log(`[jsExec] ${fh.handle}: ${JSON.stringify(rCtx.stats)}`);\n return importedResult;\n } finally {\n scope.dispose();\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  //#region src/middle_layer/build_stamp.ts
2
2
  function injectedStamp() {
3
3
  try {
4
- return "b3993db0f49b-dirty-1788940902447";
4
+ return "30d1c9606a29-dirty-1788964938933";
5
5
  } catch {
6
6
  return;
7
7
  }
@@ -1,7 +1,7 @@
1
1
  //#region src/middle_layer/build_stamp.ts
2
2
  function injectedStamp() {
3
3
  try {
4
- return "b3993db0f49b-dirty-1788940902418";
4
+ return "30d1c9606a29-dirty-1788964938904";
5
5
  } catch {
6
6
  return;
7
7
  }
@@ -3,6 +3,7 @@ require("../pool/index.cjs");
3
3
  const require_ops = require("./ops.cjs");
4
4
  let _milaboratories_ts_helpers = require("@milaboratories/ts-helpers");
5
5
  let _milaboratories_pl_drivers = require("@milaboratories/pl-drivers");
6
+ let _milaboratories_pl_flight_recorder = require("@milaboratories/pl-flight-recorder");
6
7
  let _milaboratories_helpers = require("@milaboratories/helpers");
7
8
  //#region src/middle_layer/driver_kit.ts
8
9
  async function initDriverKit(pl, workdir, frontendDownloadPath, _ops) {
@@ -20,26 +21,32 @@ async function initDriverKit(pl, workdir, frontendDownloadPath, _ops) {
20
21
  const blobToURLDriver = new _milaboratories_pl_drivers.DownloadBlobToURLDriver(ops.logger, signer, downloadClient, ops.downloadBlobToURLPath, ops.downloadBlobToURLDriverOps);
21
22
  const uploadDriver = new _milaboratories_pl_drivers.UploadDriver(ops.logger, signer, uploadBlobClient, uploadProgressClient, ops.uploadDriverOps);
22
23
  const logsStreamDriver = new _milaboratories_pl_drivers.LogsStreamDriver(ops.logger, logsClient, ops.logStreamDriverOps);
24
+ const logDriver = new _milaboratories_pl_drivers.LogsDriver(ops.logger, logsStreamDriver, blobDriver);
25
+ const lsDriver = await _milaboratories_pl_drivers.LsDriver.init(ops.logger, pl, signer, ops.localProjections, ops.openFileDialogCallback, ops.virtualLocalStoragesOverride);
26
+ const rawPFrameDriver = await require_driver.createPFrameDriver({
27
+ blobDriver,
28
+ logger: ops.logger,
29
+ spillPath: ops.pframesSpillPath,
30
+ cachePath: ops.parquetCachePath,
31
+ options: ops.pFrameDriverOps,
32
+ cacheOps: ops.parquetCacheOps
33
+ });
34
+ const flightSession = (0, _milaboratories_pl_flight_recorder.openFlightSession)({ role: "middle-layer" });
23
35
  const driverKit = {
24
36
  blobDriver,
25
37
  blobToURLDriver,
26
- logDriver: new _milaboratories_pl_drivers.LogsDriver(ops.logger, logsStreamDriver, blobDriver),
27
- lsDriver: await _milaboratories_pl_drivers.LsDriver.init(ops.logger, pl, signer, ops.localProjections, ops.openFileDialogCallback, ops.virtualLocalStoragesOverride),
38
+ logDriver,
39
+ lsDriver,
28
40
  signer,
29
41
  uploadDriver,
30
- pFrameDriver: await require_driver.createPFrameDriver({
31
- blobDriver,
32
- logger: ops.logger,
33
- spillPath: ops.pframesSpillPath,
34
- cachePath: ops.parquetCachePath,
35
- options: ops.pFrameDriverOps,
36
- cacheOps: ops.parquetCacheOps
37
- }),
38
- frontendDriver: new _milaboratories_pl_drivers.DownloadUrlDriver(ops.logger, pl.httpDispatcher, frontendDownloadPath, signer, ops.frontendDownloadDriverOps)
42
+ pFrameDriver: flightSession ? (0, _milaboratories_pl_flight_recorder.wrapDataDriver)((0, _milaboratories_pl_flight_recorder.wrapModelDriver)(rawPFrameDriver, flightSession.recorder, flightSession.registry), flightSession.recorder, flightSession.registry) : rawPFrameDriver,
43
+ frontendDriver: new _milaboratories_pl_drivers.DownloadUrlDriver(ops.logger, pl.httpDispatcher, frontendDownloadPath, signer, ops.frontendDownloadDriverOps),
44
+ flightRecorder: flightSession?.recorder
39
45
  };
40
46
  const dispose = async () => {
41
47
  const disposePromises = Object.values(driverKit).flatMap((driver) => (0, _milaboratories_helpers.isAsyncDisposable)(driver) ? [driver[Symbol.asyncDispose]()] : []);
42
48
  await Promise.all(disposePromises);
49
+ flightSession?.close("driver-kit-disposed");
43
50
  };
44
51
  return {
45
52
  ...driverKit,
@@ -1 +1 @@
1
- {"version":3,"file":"driver_kit.cjs","names":["DefaultDriverKitOpsSettings","DefaultDriverKitOpsPaths","HmacSha256Signer","DownloadDriver","DownloadBlobToURLDriver","UploadDriver","LogsStreamDriver","LogsDriver","LsDriver","createPFrameDriver","DownloadUrlDriver"],"sources":["../../src/middle_layer/driver_kit.ts"],"sourcesContent":["import type { PlClient } from \"@milaboratories/pl-client\";\nimport type { InternalLsDriver } from \"@milaboratories/pl-drivers\";\nimport {\n createDownloadClient,\n createLogsClient,\n createUploadBlobClient,\n createUploadProgressClient,\n DownloadDriver,\n DownloadBlobToURLDriver,\n LogsDriver,\n LogsStreamDriver,\n LsDriver,\n UploadDriver,\n DownloadUrlDriver,\n} from \"@milaboratories/pl-drivers\";\nimport type * as Sdk from \"@milaboratories/pl-model-common\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { isAsyncDisposable } from \"@milaboratories/helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { InternalPFrameDriver } from \"../pool\";\nimport { createPFrameDriver } from \"../pool\";\nimport type { DriverKitOps, DriverKitOpsConstructor } from \"./ops\";\nimport { DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings } from \"./ops\";\n\n/**\n * Drivers offered by the middle-layer for internal consumers,\n * like configuration rendering routines.\n *\n * This intertface is basically a version of the DriverKit from\n * UI SDK with extended API.\n * */\nexport interface MiddleLayerDriverKit extends Sdk.DriverKit, AsyncDisposable {\n /** Dispose the driver kit and all its resources. */\n dispose(): Promise<void>;\n\n // override with wider interface\n readonly blobDriver: DownloadDriver;\n // override with wider interface\n readonly blobToURLDriver: DownloadBlobToURLDriver;\n // override with wider interface\n readonly logDriver: LogsDriver;\n // override with wider interface\n readonly lsDriver: InternalLsDriver;\n // override with wider interface\n readonly pFrameDriver: InternalPFrameDriver;\n // override with wider interface\n readonly frontendDriver: DownloadUrlDriver;\n\n /**\n * Signer is initialized from local secret in drivers initialization routine,\n * so constitutes a part of the driver kit\n * */\n readonly signer: Signer;\n\n /**\n * Used to retrieve upload progress, and initiate upload porecesses driven by\n * upload requests from block outputs.\n * */\n readonly uploadDriver: UploadDriver;\n}\n\nexport async function initDriverKit(\n pl: PlClient,\n workdir: string,\n frontendDownloadPath: string,\n _ops: DriverKitOpsConstructor,\n): Promise<MiddleLayerDriverKit> {\n const ops: DriverKitOps = {\n ...DefaultDriverKitOpsSettings,\n ...DefaultDriverKitOpsPaths(workdir),\n ..._ops,\n };\n\n const signer = new HmacSha256Signer(ops.localSecret);\n\n const downloadClient = createDownloadClient(ops.logger, pl, ops.localProjections);\n const logsClient = createLogsClient(pl, ops.logger);\n const uploadBlobClient = createUploadBlobClient(pl, ops.logger);\n const uploadProgressClient = createUploadProgressClient(pl, ops.logger);\n\n const blobDriver = await DownloadDriver.init(\n ops.logger,\n downloadClient,\n logsClient,\n ops.blobDownloadPath,\n ops.blobDownloadRangesCachePath,\n signer,\n ops.blobDriverOps,\n );\n\n const blobToURLDriver = new DownloadBlobToURLDriver(\n ops.logger,\n signer,\n downloadClient,\n ops.downloadBlobToURLPath,\n ops.downloadBlobToURLDriverOps,\n );\n\n const uploadDriver = new UploadDriver(\n ops.logger,\n signer,\n uploadBlobClient,\n uploadProgressClient,\n ops.uploadDriverOps,\n );\n const logsStreamDriver = new LogsStreamDriver(ops.logger, logsClient, ops.logStreamDriverOps);\n const logDriver = new LogsDriver(ops.logger, logsStreamDriver, blobDriver);\n const lsDriver = await LsDriver.init(\n ops.logger,\n pl,\n signer,\n ops.localProjections,\n ops.openFileDialogCallback,\n ops.virtualLocalStoragesOverride,\n );\n\n const pFrameDriver = await createPFrameDriver({\n blobDriver,\n logger: ops.logger,\n spillPath: ops.pframesSpillPath,\n cachePath: ops.parquetCachePath,\n options: ops.pFrameDriverOps,\n cacheOps: ops.parquetCacheOps,\n });\n\n const frontendDownloadDriver = new DownloadUrlDriver(\n ops.logger,\n pl.httpDispatcher,\n frontendDownloadPath,\n signer,\n ops.frontendDownloadDriverOps,\n );\n\n const driverKit = {\n blobDriver,\n blobToURLDriver: blobToURLDriver,\n logDriver,\n lsDriver,\n signer,\n uploadDriver,\n pFrameDriver,\n frontendDriver: frontendDownloadDriver,\n };\n\n const dispose = async () => {\n const disposePromises = Object.values(driverKit).flatMap((driver) =>\n isAsyncDisposable(driver) ? [driver[Symbol.asyncDispose]()] : [],\n );\n await Promise.all(disposePromises);\n };\n\n return {\n ...driverKit,\n dispose,\n [Symbol.asyncDispose]: dispose,\n };\n}\n"],"mappings":";;;;;;;AA6DA,eAAsB,cACpB,IACA,SACA,sBACA,MAC+B;CAC/B,MAAM,MAAoB;EACxB,GAAGA,YAAAA;EACH,GAAGC,YAAAA,yBAAyB,OAAO;EACnC,GAAG;CACL;CAEA,MAAM,SAAS,IAAIC,2BAAAA,iBAAiB,IAAI,WAAW;CAEnD,MAAM,kBAAA,GAAA,2BAAA,qBAAA,CAAsC,IAAI,QAAQ,IAAI,IAAI,gBAAgB;CAChF,MAAM,cAAA,GAAA,2BAAA,iBAAA,CAA8B,IAAI,IAAI,MAAM;CAClD,MAAM,oBAAA,GAAA,2BAAA,uBAAA,CAA0C,IAAI,IAAI,MAAM;CAC9D,MAAM,wBAAA,GAAA,2BAAA,2BAAA,CAAkD,IAAI,IAAI,MAAM;CAEtE,MAAM,aAAa,MAAMC,2BAAAA,eAAe,KACtC,IAAI,QACJ,gBACA,YACA,IAAI,kBACJ,IAAI,6BACJ,QACA,IAAI,aACN;CAEA,MAAM,kBAAkB,IAAIC,2BAAAA,wBAC1B,IAAI,QACJ,QACA,gBACA,IAAI,uBACJ,IAAI,0BACN;CAEA,MAAM,eAAe,IAAIC,2BAAAA,aACvB,IAAI,QACJ,QACA,kBACA,sBACA,IAAI,eACN;CACA,MAAM,mBAAmB,IAAIC,2BAAAA,iBAAiB,IAAI,QAAQ,YAAY,IAAI,kBAAkB;CA4B5F,MAAM,YAAY;EAChB;EACiB;EACjB,WAAA,IA9BoBC,2BAAAA,WAAW,IAAI,QAAQ,kBAAkB,UA8BrD;EACR,UAAA,MA9BqBC,2BAAAA,SAAS,KAC9B,IAAI,QACJ,IACA,QACA,IAAI,kBACJ,IAAI,wBACJ,IAAI,4BACN;EAwBE;EACA;EACA,cAAA,MAxByBC,eAAAA,mBAAmB;GAC5C;GACA,QAAQ,IAAI;GACZ,WAAW,IAAI;GACf,WAAW,IAAI;GACf,SAAS,IAAI;GACb,UAAU,IAAI;EAChB,CAAC;EAkBC,gBAAgB,IAhBiBC,2BAAAA,kBACjC,IAAI,QACJ,GAAG,gBACH,sBACA,QACA,IAAI,yBAWiC;CACvC;CAEA,MAAM,UAAU,YAAY;EAC1B,MAAM,kBAAkB,OAAO,OAAO,SAAS,CAAC,CAAC,SAAS,YAAA,GAAA,wBAAA,kBAAA,CACtC,MAAM,IAAI,CAAC,OAAO,OAAO,aAAa,CAAC,CAAC,IAAI,CAAC,CACjE;EACA,MAAM,QAAQ,IAAI,eAAe;CACnC;CAEA,OAAO;EACL,GAAG;EACH;GACC,OAAO,eAAe;CACzB;AACF"}
1
+ {"version":3,"file":"driver_kit.cjs","names":["DefaultDriverKitOpsSettings","DefaultDriverKitOpsPaths","HmacSha256Signer","DownloadDriver","DownloadBlobToURLDriver","UploadDriver","LogsStreamDriver","LogsDriver","LsDriver","createPFrameDriver","DownloadUrlDriver"],"sources":["../../src/middle_layer/driver_kit.ts"],"sourcesContent":["import type { PlClient } from \"@milaboratories/pl-client\";\nimport type { InternalLsDriver } from \"@milaboratories/pl-drivers\";\nimport {\n createDownloadClient,\n createLogsClient,\n createUploadBlobClient,\n createUploadProgressClient,\n DownloadDriver,\n DownloadBlobToURLDriver,\n LogsDriver,\n LogsStreamDriver,\n LsDriver,\n UploadDriver,\n DownloadUrlDriver,\n} from \"@milaboratories/pl-drivers\";\nimport type * as Sdk from \"@milaboratories/pl-model-common\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { isAsyncDisposable } from \"@milaboratories/helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { InternalPFrameDriver } from \"../pool\";\nimport { createPFrameDriver } from \"../pool\";\nimport type { Recorder } from \"@milaboratories/pl-flight-recorder\";\nimport {\n openFlightSession,\n wrapDataDriver,\n wrapModelDriver,\n} from \"@milaboratories/pl-flight-recorder\";\nimport type { DriverKitOps, DriverKitOpsConstructor } from \"./ops\";\nimport { DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings } from \"./ops\";\n\n/**\n * Drivers offered by the middle-layer for internal consumers,\n * like configuration rendering routines.\n *\n * This intertface is basically a version of the DriverKit from\n * UI SDK with extended API.\n * */\nexport interface MiddleLayerDriverKit extends Sdk.DriverKit, AsyncDisposable {\n /** Dispose the driver kit and all its resources. */\n dispose(): Promise<void>;\n\n // override with wider interface\n readonly blobDriver: DownloadDriver;\n // override with wider interface\n readonly blobToURLDriver: DownloadBlobToURLDriver;\n // override with wider interface\n readonly logDriver: LogsDriver;\n // override with wider interface\n readonly lsDriver: InternalLsDriver;\n // override with wider interface\n readonly pFrameDriver: InternalPFrameDriver;\n // override with wider interface\n readonly frontendDriver: DownloadUrlDriver;\n\n /**\n * Diagnostics log for the model layer, present only when flight recording is\n * switched on. Render spans are written through it so that a join recorded by\n * the instrumented pFrame driver can be attributed to the block that built it.\n * */\n readonly flightRecorder?: Recorder;\n\n /**\n * Signer is initialized from local secret in drivers initialization routine,\n * so constitutes a part of the driver kit\n * */\n readonly signer: Signer;\n\n /**\n * Used to retrieve upload progress, and initiate upload porecesses driven by\n * upload requests from block outputs.\n * */\n readonly uploadDriver: UploadDriver;\n}\n\nexport async function initDriverKit(\n pl: PlClient,\n workdir: string,\n frontendDownloadPath: string,\n _ops: DriverKitOpsConstructor,\n): Promise<MiddleLayerDriverKit> {\n const ops: DriverKitOps = {\n ...DefaultDriverKitOpsSettings,\n ...DefaultDriverKitOpsPaths(workdir),\n ..._ops,\n };\n\n const signer = new HmacSha256Signer(ops.localSecret);\n\n const downloadClient = createDownloadClient(ops.logger, pl, ops.localProjections);\n const logsClient = createLogsClient(pl, ops.logger);\n const uploadBlobClient = createUploadBlobClient(pl, ops.logger);\n const uploadProgressClient = createUploadProgressClient(pl, ops.logger);\n\n const blobDriver = await DownloadDriver.init(\n ops.logger,\n downloadClient,\n logsClient,\n ops.blobDownloadPath,\n ops.blobDownloadRangesCachePath,\n signer,\n ops.blobDriverOps,\n );\n\n const blobToURLDriver = new DownloadBlobToURLDriver(\n ops.logger,\n signer,\n downloadClient,\n ops.downloadBlobToURLPath,\n ops.downloadBlobToURLDriverOps,\n );\n\n const uploadDriver = new UploadDriver(\n ops.logger,\n signer,\n uploadBlobClient,\n uploadProgressClient,\n ops.uploadDriverOps,\n );\n const logsStreamDriver = new LogsStreamDriver(ops.logger, logsClient, ops.logStreamDriverOps);\n const logDriver = new LogsDriver(ops.logger, logsStreamDriver, blobDriver);\n const lsDriver = await LsDriver.init(\n ops.logger,\n pl,\n signer,\n ops.localProjections,\n ops.openFileDialogCallback,\n ops.virtualLocalStoragesOverride,\n );\n\n const rawPFrameDriver = await createPFrameDriver({\n blobDriver,\n logger: ops.logger,\n spillPath: ops.pframesSpillPath,\n cachePath: ops.parquetCachePath,\n options: ops.pFrameDriverOps,\n cacheOps: ops.parquetCacheOps,\n });\n\n // Every join a block model builds and every row it reads back passes through\n // this one driver, so instrumenting it here covers the whole model layer\n // without touching the call sites. The session is undefined unless recording\n // is switched on, in which case the driver is used exactly as before.\n const flightSession = openFlightSession({ role: \"middle-layer\" });\n const pFrameDriver: InternalPFrameDriver = flightSession\n ? wrapDataDriver(\n wrapModelDriver(rawPFrameDriver, flightSession.recorder, flightSession.registry),\n flightSession.recorder,\n flightSession.registry,\n )\n : rawPFrameDriver;\n\n const frontendDownloadDriver = new DownloadUrlDriver(\n ops.logger,\n pl.httpDispatcher,\n frontendDownloadPath,\n signer,\n ops.frontendDownloadDriverOps,\n );\n\n const driverKit = {\n blobDriver,\n blobToURLDriver: blobToURLDriver,\n logDriver,\n lsDriver,\n signer,\n uploadDriver,\n pFrameDriver,\n frontendDriver: frontendDownloadDriver,\n flightRecorder: flightSession?.recorder,\n };\n\n const dispose = async () => {\n const disposePromises = Object.values(driverKit).flatMap((driver) =>\n isAsyncDisposable(driver) ? [driver[Symbol.asyncDispose]()] : [],\n );\n await Promise.all(disposePromises);\n flightSession?.close(\"driver-kit-disposed\");\n };\n\n return {\n ...driverKit,\n dispose,\n [Symbol.asyncDispose]: dispose,\n };\n}\n"],"mappings":";;;;;;;;AA0EA,eAAsB,cACpB,IACA,SACA,sBACA,MAC+B;CAC/B,MAAM,MAAoB;EACxB,GAAGA,YAAAA;EACH,GAAGC,YAAAA,yBAAyB,OAAO;EACnC,GAAG;CACL;CAEA,MAAM,SAAS,IAAIC,2BAAAA,iBAAiB,IAAI,WAAW;CAEnD,MAAM,kBAAA,GAAA,2BAAA,qBAAA,CAAsC,IAAI,QAAQ,IAAI,IAAI,gBAAgB;CAChF,MAAM,cAAA,GAAA,2BAAA,iBAAA,CAA8B,IAAI,IAAI,MAAM;CAClD,MAAM,oBAAA,GAAA,2BAAA,uBAAA,CAA0C,IAAI,IAAI,MAAM;CAC9D,MAAM,wBAAA,GAAA,2BAAA,2BAAA,CAAkD,IAAI,IAAI,MAAM;CAEtE,MAAM,aAAa,MAAMC,2BAAAA,eAAe,KACtC,IAAI,QACJ,gBACA,YACA,IAAI,kBACJ,IAAI,6BACJ,QACA,IAAI,aACN;CAEA,MAAM,kBAAkB,IAAIC,2BAAAA,wBAC1B,IAAI,QACJ,QACA,gBACA,IAAI,uBACJ,IAAI,0BACN;CAEA,MAAM,eAAe,IAAIC,2BAAAA,aACvB,IAAI,QACJ,QACA,kBACA,sBACA,IAAI,eACN;CACA,MAAM,mBAAmB,IAAIC,2BAAAA,iBAAiB,IAAI,QAAQ,YAAY,IAAI,kBAAkB;CAC5F,MAAM,YAAY,IAAIC,2BAAAA,WAAW,IAAI,QAAQ,kBAAkB,UAAU;CACzE,MAAM,WAAW,MAAMC,2BAAAA,SAAS,KAC9B,IAAI,QACJ,IACA,QACA,IAAI,kBACJ,IAAI,wBACJ,IAAI,4BACN;CAEA,MAAM,kBAAkB,MAAMC,eAAAA,mBAAmB;EAC/C;EACA,QAAQ,IAAI;EACZ,WAAW,IAAI;EACf,WAAW,IAAI;EACf,SAAS,IAAI;EACb,UAAU,IAAI;CAChB,CAAC;CAMD,MAAM,iBAAA,GAAA,mCAAA,kBAAA,CAAkC,EAAE,MAAM,eAAe,CAAC;CAiBhE,MAAM,YAAY;EAChB;EACiB;EACjB;EACA;EACA;EACA;EACA,cAvByC,iBAAA,GAAA,mCAAA,eAAA,EAAA,GAAA,mCAAA,gBAAA,CAErB,iBAAiB,cAAc,UAAU,cAAc,QAAQ,GAC/E,cAAc,UACd,cAAc,QAChB,IACA;EAkBF,gBAAgB,IAhBiBC,2BAAAA,kBACjC,IAAI,QACJ,GAAG,gBACH,sBACA,QACA,IAAI,yBAWiC;EACrC,gBAAgB,eAAe;CACjC;CAEA,MAAM,UAAU,YAAY;EAC1B,MAAM,kBAAkB,OAAO,OAAO,SAAS,CAAC,CAAC,SAAS,YAAA,GAAA,wBAAA,kBAAA,CACtC,MAAM,IAAI,CAAC,OAAO,OAAO,aAAa,CAAC,CAAC,IAAI,CAAC,CACjE;EACA,MAAM,QAAQ,IAAI,eAAe;EACjC,eAAe,MAAM,qBAAqB;CAC5C;CAEA,OAAO;EACL,GAAG;EACH;GACC,OAAO,eAAe;CACzB;AACF"}
@@ -5,6 +5,7 @@ import { Signer } from "@milaboratories/ts-helpers";
5
5
  import * as Sdk from "@milaboratories/pl-model-common";
6
6
  import { PlClient } from "@milaboratories/pl-client";
7
7
  import { DownloadBlobToURLDriver, DownloadDriver, DownloadUrlDriver, InternalLsDriver, LogsDriver, UploadDriver } from "@milaboratories/pl-drivers";
8
+ import { Recorder } from "@milaboratories/pl-flight-recorder";
8
9
  //#region src/middle_layer/driver_kit.d.ts
9
10
  /**
10
11
  * Drivers offered by the middle-layer for internal consumers,
@@ -22,6 +23,12 @@ export interface MiddleLayerDriverKit extends Sdk.DriverKit, AsyncDisposable {
22
23
  readonly lsDriver: InternalLsDriver;
23
24
  readonly pFrameDriver: InternalPFrameDriver;
24
25
  readonly frontendDriver: DownloadUrlDriver;
26
+ /**
27
+ * Diagnostics log for the model layer, present only when flight recording is
28
+ * switched on. Render spans are written through it so that a join recorded by
29
+ * the instrumented pFrame driver can be attributed to the block that built it.
30
+ * */
31
+ readonly flightRecorder?: Recorder;
25
32
  /**
26
33
  * Signer is initialized from local secret in drivers initialization routine,
27
34
  * so constitutes a part of the driver kit
@@ -1 +1 @@
1
- {"version":3,"file":"driver_kit.d.ts","names":[],"sources":["../../src/middle_layer/driver_kit.ts"],"mappings":";;;;;;;;;;;;;;;iBA+BiB,6BAA6B,IAAI,WAAW;;EAE3D,WAAW;WAGF,YAAY;WAEZ,iBAAiB;WAEjB,WAAW;WAEX,UAAU;WAEV,cAAc;WAEd,gBAAgB;;;;;WAMhB,QAAQ;;;;;WAMR,cAAc;;wBAGH,cACpB,IAAI,UACJ,iBACA,8BACA,MAAM,0BACL,QAAQ"}
1
+ {"version":3,"file":"driver_kit.d.ts","names":[],"sources":["../../src/middle_layer/driver_kit.ts"],"mappings":";;;;;;;;;;;;;;;;iBAqCiB,6BAA6B,IAAI,WAAW;;EAE3D,WAAW;WAGF,YAAY;WAEZ,iBAAiB;WAEjB,WAAW;WAEX,UAAU;WAEV,cAAc;WAEd,gBAAgB;;;;;;WAOhB,iBAAiB;;;;;WAMjB,QAAQ;;;;;WAMR,cAAc;;wBAGH,cACpB,IAAI,UACJ,iBACA,8BACA,MAAM,0BACL,QAAQ"}
@@ -3,6 +3,7 @@ import "../pool/index.js";
3
3
  import { DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings } from "./ops.js";
4
4
  import { HmacSha256Signer } from "@milaboratories/ts-helpers";
5
5
  import { DownloadBlobToURLDriver, DownloadDriver, DownloadUrlDriver, LogsDriver, LogsStreamDriver, LsDriver, UploadDriver, createDownloadClient, createLogsClient, createUploadBlobClient, createUploadProgressClient } from "@milaboratories/pl-drivers";
6
+ import { openFlightSession, wrapDataDriver, wrapModelDriver } from "@milaboratories/pl-flight-recorder";
6
7
  import { isAsyncDisposable } from "@milaboratories/helpers";
7
8
  //#region src/middle_layer/driver_kit.ts
8
9
  async function initDriverKit(pl, workdir, frontendDownloadPath, _ops) {
@@ -20,26 +21,32 @@ async function initDriverKit(pl, workdir, frontendDownloadPath, _ops) {
20
21
  const blobToURLDriver = new DownloadBlobToURLDriver(ops.logger, signer, downloadClient, ops.downloadBlobToURLPath, ops.downloadBlobToURLDriverOps);
21
22
  const uploadDriver = new UploadDriver(ops.logger, signer, uploadBlobClient, uploadProgressClient, ops.uploadDriverOps);
22
23
  const logsStreamDriver = new LogsStreamDriver(ops.logger, logsClient, ops.logStreamDriverOps);
24
+ const logDriver = new LogsDriver(ops.logger, logsStreamDriver, blobDriver);
25
+ const lsDriver = await LsDriver.init(ops.logger, pl, signer, ops.localProjections, ops.openFileDialogCallback, ops.virtualLocalStoragesOverride);
26
+ const rawPFrameDriver = await createPFrameDriver({
27
+ blobDriver,
28
+ logger: ops.logger,
29
+ spillPath: ops.pframesSpillPath,
30
+ cachePath: ops.parquetCachePath,
31
+ options: ops.pFrameDriverOps,
32
+ cacheOps: ops.parquetCacheOps
33
+ });
34
+ const flightSession = openFlightSession({ role: "middle-layer" });
23
35
  const driverKit = {
24
36
  blobDriver,
25
37
  blobToURLDriver,
26
- logDriver: new LogsDriver(ops.logger, logsStreamDriver, blobDriver),
27
- lsDriver: await LsDriver.init(ops.logger, pl, signer, ops.localProjections, ops.openFileDialogCallback, ops.virtualLocalStoragesOverride),
38
+ logDriver,
39
+ lsDriver,
28
40
  signer,
29
41
  uploadDriver,
30
- pFrameDriver: await createPFrameDriver({
31
- blobDriver,
32
- logger: ops.logger,
33
- spillPath: ops.pframesSpillPath,
34
- cachePath: ops.parquetCachePath,
35
- options: ops.pFrameDriverOps,
36
- cacheOps: ops.parquetCacheOps
37
- }),
38
- frontendDriver: new DownloadUrlDriver(ops.logger, pl.httpDispatcher, frontendDownloadPath, signer, ops.frontendDownloadDriverOps)
42
+ pFrameDriver: flightSession ? wrapDataDriver(wrapModelDriver(rawPFrameDriver, flightSession.recorder, flightSession.registry), flightSession.recorder, flightSession.registry) : rawPFrameDriver,
43
+ frontendDriver: new DownloadUrlDriver(ops.logger, pl.httpDispatcher, frontendDownloadPath, signer, ops.frontendDownloadDriverOps),
44
+ flightRecorder: flightSession?.recorder
39
45
  };
40
46
  const dispose = async () => {
41
47
  const disposePromises = Object.values(driverKit).flatMap((driver) => isAsyncDisposable(driver) ? [driver[Symbol.asyncDispose]()] : []);
42
48
  await Promise.all(disposePromises);
49
+ flightSession?.close("driver-kit-disposed");
43
50
  };
44
51
  return {
45
52
  ...driverKit,
@@ -1 +1 @@
1
- {"version":3,"file":"driver_kit.js","names":[],"sources":["../../src/middle_layer/driver_kit.ts"],"sourcesContent":["import type { PlClient } from \"@milaboratories/pl-client\";\nimport type { InternalLsDriver } from \"@milaboratories/pl-drivers\";\nimport {\n createDownloadClient,\n createLogsClient,\n createUploadBlobClient,\n createUploadProgressClient,\n DownloadDriver,\n DownloadBlobToURLDriver,\n LogsDriver,\n LogsStreamDriver,\n LsDriver,\n UploadDriver,\n DownloadUrlDriver,\n} from \"@milaboratories/pl-drivers\";\nimport type * as Sdk from \"@milaboratories/pl-model-common\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { isAsyncDisposable } from \"@milaboratories/helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { InternalPFrameDriver } from \"../pool\";\nimport { createPFrameDriver } from \"../pool\";\nimport type { DriverKitOps, DriverKitOpsConstructor } from \"./ops\";\nimport { DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings } from \"./ops\";\n\n/**\n * Drivers offered by the middle-layer for internal consumers,\n * like configuration rendering routines.\n *\n * This intertface is basically a version of the DriverKit from\n * UI SDK with extended API.\n * */\nexport interface MiddleLayerDriverKit extends Sdk.DriverKit, AsyncDisposable {\n /** Dispose the driver kit and all its resources. */\n dispose(): Promise<void>;\n\n // override with wider interface\n readonly blobDriver: DownloadDriver;\n // override with wider interface\n readonly blobToURLDriver: DownloadBlobToURLDriver;\n // override with wider interface\n readonly logDriver: LogsDriver;\n // override with wider interface\n readonly lsDriver: InternalLsDriver;\n // override with wider interface\n readonly pFrameDriver: InternalPFrameDriver;\n // override with wider interface\n readonly frontendDriver: DownloadUrlDriver;\n\n /**\n * Signer is initialized from local secret in drivers initialization routine,\n * so constitutes a part of the driver kit\n * */\n readonly signer: Signer;\n\n /**\n * Used to retrieve upload progress, and initiate upload porecesses driven by\n * upload requests from block outputs.\n * */\n readonly uploadDriver: UploadDriver;\n}\n\nexport async function initDriverKit(\n pl: PlClient,\n workdir: string,\n frontendDownloadPath: string,\n _ops: DriverKitOpsConstructor,\n): Promise<MiddleLayerDriverKit> {\n const ops: DriverKitOps = {\n ...DefaultDriverKitOpsSettings,\n ...DefaultDriverKitOpsPaths(workdir),\n ..._ops,\n };\n\n const signer = new HmacSha256Signer(ops.localSecret);\n\n const downloadClient = createDownloadClient(ops.logger, pl, ops.localProjections);\n const logsClient = createLogsClient(pl, ops.logger);\n const uploadBlobClient = createUploadBlobClient(pl, ops.logger);\n const uploadProgressClient = createUploadProgressClient(pl, ops.logger);\n\n const blobDriver = await DownloadDriver.init(\n ops.logger,\n downloadClient,\n logsClient,\n ops.blobDownloadPath,\n ops.blobDownloadRangesCachePath,\n signer,\n ops.blobDriverOps,\n );\n\n const blobToURLDriver = new DownloadBlobToURLDriver(\n ops.logger,\n signer,\n downloadClient,\n ops.downloadBlobToURLPath,\n ops.downloadBlobToURLDriverOps,\n );\n\n const uploadDriver = new UploadDriver(\n ops.logger,\n signer,\n uploadBlobClient,\n uploadProgressClient,\n ops.uploadDriverOps,\n );\n const logsStreamDriver = new LogsStreamDriver(ops.logger, logsClient, ops.logStreamDriverOps);\n const logDriver = new LogsDriver(ops.logger, logsStreamDriver, blobDriver);\n const lsDriver = await LsDriver.init(\n ops.logger,\n pl,\n signer,\n ops.localProjections,\n ops.openFileDialogCallback,\n ops.virtualLocalStoragesOverride,\n );\n\n const pFrameDriver = await createPFrameDriver({\n blobDriver,\n logger: ops.logger,\n spillPath: ops.pframesSpillPath,\n cachePath: ops.parquetCachePath,\n options: ops.pFrameDriverOps,\n cacheOps: ops.parquetCacheOps,\n });\n\n const frontendDownloadDriver = new DownloadUrlDriver(\n ops.logger,\n pl.httpDispatcher,\n frontendDownloadPath,\n signer,\n ops.frontendDownloadDriverOps,\n );\n\n const driverKit = {\n blobDriver,\n blobToURLDriver: blobToURLDriver,\n logDriver,\n lsDriver,\n signer,\n uploadDriver,\n pFrameDriver,\n frontendDriver: frontendDownloadDriver,\n };\n\n const dispose = async () => {\n const disposePromises = Object.values(driverKit).flatMap((driver) =>\n isAsyncDisposable(driver) ? [driver[Symbol.asyncDispose]()] : [],\n );\n await Promise.all(disposePromises);\n };\n\n return {\n ...driverKit,\n dispose,\n [Symbol.asyncDispose]: dispose,\n };\n}\n"],"mappings":";;;;;;;AA6DA,eAAsB,cACpB,IACA,SACA,sBACA,MAC+B;CAC/B,MAAM,MAAoB;EACxB,GAAG;EACH,GAAG,yBAAyB,OAAO;EACnC,GAAG;CACL;CAEA,MAAM,SAAS,IAAI,iBAAiB,IAAI,WAAW;CAEnD,MAAM,iBAAiB,qBAAqB,IAAI,QAAQ,IAAI,IAAI,gBAAgB;CAChF,MAAM,aAAa,iBAAiB,IAAI,IAAI,MAAM;CAClD,MAAM,mBAAmB,uBAAuB,IAAI,IAAI,MAAM;CAC9D,MAAM,uBAAuB,2BAA2B,IAAI,IAAI,MAAM;CAEtE,MAAM,aAAa,MAAM,eAAe,KACtC,IAAI,QACJ,gBACA,YACA,IAAI,kBACJ,IAAI,6BACJ,QACA,IAAI,aACN;CAEA,MAAM,kBAAkB,IAAI,wBAC1B,IAAI,QACJ,QACA,gBACA,IAAI,uBACJ,IAAI,0BACN;CAEA,MAAM,eAAe,IAAI,aACvB,IAAI,QACJ,QACA,kBACA,sBACA,IAAI,eACN;CACA,MAAM,mBAAmB,IAAI,iBAAiB,IAAI,QAAQ,YAAY,IAAI,kBAAkB;CA4B5F,MAAM,YAAY;EAChB;EACiB;EACjB,WAAA,IA9BoB,WAAW,IAAI,QAAQ,kBAAkB,UA8BrD;EACR,UAAA,MA9BqB,SAAS,KAC9B,IAAI,QACJ,IACA,QACA,IAAI,kBACJ,IAAI,wBACJ,IAAI,4BACN;EAwBE;EACA;EACA,cAAA,MAxByB,mBAAmB;GAC5C;GACA,QAAQ,IAAI;GACZ,WAAW,IAAI;GACf,WAAW,IAAI;GACf,SAAS,IAAI;GACb,UAAU,IAAI;EAChB,CAAC;EAkBC,gBAAgB,IAhBiB,kBACjC,IAAI,QACJ,GAAG,gBACH,sBACA,QACA,IAAI,yBAWiC;CACvC;CAEA,MAAM,UAAU,YAAY;EAC1B,MAAM,kBAAkB,OAAO,OAAO,SAAS,CAAC,CAAC,SAAS,WACxD,kBAAkB,MAAM,IAAI,CAAC,OAAO,OAAO,aAAa,CAAC,CAAC,IAAI,CAAC,CACjE;EACA,MAAM,QAAQ,IAAI,eAAe;CACnC;CAEA,OAAO;EACL,GAAG;EACH;GACC,OAAO,eAAe;CACzB;AACF"}
1
+ {"version":3,"file":"driver_kit.js","names":[],"sources":["../../src/middle_layer/driver_kit.ts"],"sourcesContent":["import type { PlClient } from \"@milaboratories/pl-client\";\nimport type { InternalLsDriver } from \"@milaboratories/pl-drivers\";\nimport {\n createDownloadClient,\n createLogsClient,\n createUploadBlobClient,\n createUploadProgressClient,\n DownloadDriver,\n DownloadBlobToURLDriver,\n LogsDriver,\n LogsStreamDriver,\n LsDriver,\n UploadDriver,\n DownloadUrlDriver,\n} from \"@milaboratories/pl-drivers\";\nimport type * as Sdk from \"@milaboratories/pl-model-common\";\nimport type { Signer } from \"@milaboratories/ts-helpers\";\nimport { isAsyncDisposable } from \"@milaboratories/helpers\";\nimport { HmacSha256Signer } from \"@milaboratories/ts-helpers\";\nimport type { InternalPFrameDriver } from \"../pool\";\nimport { createPFrameDriver } from \"../pool\";\nimport type { Recorder } from \"@milaboratories/pl-flight-recorder\";\nimport {\n openFlightSession,\n wrapDataDriver,\n wrapModelDriver,\n} from \"@milaboratories/pl-flight-recorder\";\nimport type { DriverKitOps, DriverKitOpsConstructor } from \"./ops\";\nimport { DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings } from \"./ops\";\n\n/**\n * Drivers offered by the middle-layer for internal consumers,\n * like configuration rendering routines.\n *\n * This intertface is basically a version of the DriverKit from\n * UI SDK with extended API.\n * */\nexport interface MiddleLayerDriverKit extends Sdk.DriverKit, AsyncDisposable {\n /** Dispose the driver kit and all its resources. */\n dispose(): Promise<void>;\n\n // override with wider interface\n readonly blobDriver: DownloadDriver;\n // override with wider interface\n readonly blobToURLDriver: DownloadBlobToURLDriver;\n // override with wider interface\n readonly logDriver: LogsDriver;\n // override with wider interface\n readonly lsDriver: InternalLsDriver;\n // override with wider interface\n readonly pFrameDriver: InternalPFrameDriver;\n // override with wider interface\n readonly frontendDriver: DownloadUrlDriver;\n\n /**\n * Diagnostics log for the model layer, present only when flight recording is\n * switched on. Render spans are written through it so that a join recorded by\n * the instrumented pFrame driver can be attributed to the block that built it.\n * */\n readonly flightRecorder?: Recorder;\n\n /**\n * Signer is initialized from local secret in drivers initialization routine,\n * so constitutes a part of the driver kit\n * */\n readonly signer: Signer;\n\n /**\n * Used to retrieve upload progress, and initiate upload porecesses driven by\n * upload requests from block outputs.\n * */\n readonly uploadDriver: UploadDriver;\n}\n\nexport async function initDriverKit(\n pl: PlClient,\n workdir: string,\n frontendDownloadPath: string,\n _ops: DriverKitOpsConstructor,\n): Promise<MiddleLayerDriverKit> {\n const ops: DriverKitOps = {\n ...DefaultDriverKitOpsSettings,\n ...DefaultDriverKitOpsPaths(workdir),\n ..._ops,\n };\n\n const signer = new HmacSha256Signer(ops.localSecret);\n\n const downloadClient = createDownloadClient(ops.logger, pl, ops.localProjections);\n const logsClient = createLogsClient(pl, ops.logger);\n const uploadBlobClient = createUploadBlobClient(pl, ops.logger);\n const uploadProgressClient = createUploadProgressClient(pl, ops.logger);\n\n const blobDriver = await DownloadDriver.init(\n ops.logger,\n downloadClient,\n logsClient,\n ops.blobDownloadPath,\n ops.blobDownloadRangesCachePath,\n signer,\n ops.blobDriverOps,\n );\n\n const blobToURLDriver = new DownloadBlobToURLDriver(\n ops.logger,\n signer,\n downloadClient,\n ops.downloadBlobToURLPath,\n ops.downloadBlobToURLDriverOps,\n );\n\n const uploadDriver = new UploadDriver(\n ops.logger,\n signer,\n uploadBlobClient,\n uploadProgressClient,\n ops.uploadDriverOps,\n );\n const logsStreamDriver = new LogsStreamDriver(ops.logger, logsClient, ops.logStreamDriverOps);\n const logDriver = new LogsDriver(ops.logger, logsStreamDriver, blobDriver);\n const lsDriver = await LsDriver.init(\n ops.logger,\n pl,\n signer,\n ops.localProjections,\n ops.openFileDialogCallback,\n ops.virtualLocalStoragesOverride,\n );\n\n const rawPFrameDriver = await createPFrameDriver({\n blobDriver,\n logger: ops.logger,\n spillPath: ops.pframesSpillPath,\n cachePath: ops.parquetCachePath,\n options: ops.pFrameDriverOps,\n cacheOps: ops.parquetCacheOps,\n });\n\n // Every join a block model builds and every row it reads back passes through\n // this one driver, so instrumenting it here covers the whole model layer\n // without touching the call sites. The session is undefined unless recording\n // is switched on, in which case the driver is used exactly as before.\n const flightSession = openFlightSession({ role: \"middle-layer\" });\n const pFrameDriver: InternalPFrameDriver = flightSession\n ? wrapDataDriver(\n wrapModelDriver(rawPFrameDriver, flightSession.recorder, flightSession.registry),\n flightSession.recorder,\n flightSession.registry,\n )\n : rawPFrameDriver;\n\n const frontendDownloadDriver = new DownloadUrlDriver(\n ops.logger,\n pl.httpDispatcher,\n frontendDownloadPath,\n signer,\n ops.frontendDownloadDriverOps,\n );\n\n const driverKit = {\n blobDriver,\n blobToURLDriver: blobToURLDriver,\n logDriver,\n lsDriver,\n signer,\n uploadDriver,\n pFrameDriver,\n frontendDriver: frontendDownloadDriver,\n flightRecorder: flightSession?.recorder,\n };\n\n const dispose = async () => {\n const disposePromises = Object.values(driverKit).flatMap((driver) =>\n isAsyncDisposable(driver) ? [driver[Symbol.asyncDispose]()] : [],\n );\n await Promise.all(disposePromises);\n flightSession?.close(\"driver-kit-disposed\");\n };\n\n return {\n ...driverKit,\n dispose,\n [Symbol.asyncDispose]: dispose,\n };\n}\n"],"mappings":";;;;;;;;AA0EA,eAAsB,cACpB,IACA,SACA,sBACA,MAC+B;CAC/B,MAAM,MAAoB;EACxB,GAAG;EACH,GAAG,yBAAyB,OAAO;EACnC,GAAG;CACL;CAEA,MAAM,SAAS,IAAI,iBAAiB,IAAI,WAAW;CAEnD,MAAM,iBAAiB,qBAAqB,IAAI,QAAQ,IAAI,IAAI,gBAAgB;CAChF,MAAM,aAAa,iBAAiB,IAAI,IAAI,MAAM;CAClD,MAAM,mBAAmB,uBAAuB,IAAI,IAAI,MAAM;CAC9D,MAAM,uBAAuB,2BAA2B,IAAI,IAAI,MAAM;CAEtE,MAAM,aAAa,MAAM,eAAe,KACtC,IAAI,QACJ,gBACA,YACA,IAAI,kBACJ,IAAI,6BACJ,QACA,IAAI,aACN;CAEA,MAAM,kBAAkB,IAAI,wBAC1B,IAAI,QACJ,QACA,gBACA,IAAI,uBACJ,IAAI,0BACN;CAEA,MAAM,eAAe,IAAI,aACvB,IAAI,QACJ,QACA,kBACA,sBACA,IAAI,eACN;CACA,MAAM,mBAAmB,IAAI,iBAAiB,IAAI,QAAQ,YAAY,IAAI,kBAAkB;CAC5F,MAAM,YAAY,IAAI,WAAW,IAAI,QAAQ,kBAAkB,UAAU;CACzE,MAAM,WAAW,MAAM,SAAS,KAC9B,IAAI,QACJ,IACA,QACA,IAAI,kBACJ,IAAI,wBACJ,IAAI,4BACN;CAEA,MAAM,kBAAkB,MAAM,mBAAmB;EAC/C;EACA,QAAQ,IAAI;EACZ,WAAW,IAAI;EACf,WAAW,IAAI;EACf,SAAS,IAAI;EACb,UAAU,IAAI;CAChB,CAAC;CAMD,MAAM,gBAAgB,kBAAkB,EAAE,MAAM,eAAe,CAAC;CAiBhE,MAAM,YAAY;EAChB;EACiB;EACjB;EACA;EACA;EACA;EACA,cAvByC,gBACvC,eACE,gBAAgB,iBAAiB,cAAc,UAAU,cAAc,QAAQ,GAC/E,cAAc,UACd,cAAc,QAChB,IACA;EAkBF,gBAAgB,IAhBiB,kBACjC,IAAI,QACJ,GAAG,gBACH,sBACA,QACA,IAAI,yBAWiC;EACrC,gBAAgB,eAAe;CACjC;CAEA,MAAM,UAAU,YAAY;EAC1B,MAAM,kBAAkB,OAAO,OAAO,SAAS,CAAC,CAAC,SAAS,WACxD,kBAAkB,MAAM,IAAI,CAAC,OAAO,OAAO,aAAa,CAAC,CAAC,IAAI,CAAC,CACjE;EACA,MAAM,QAAQ,IAAI,eAAe;EACjC,eAAe,MAAM,qBAAqB;CAC5C;CAEA,OAAO;EACL,GAAG;EACH;GACC,OAAO,eAAe;CACzB;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/pl-middle-layer",
3
- "version": "1.71.1",
3
+ "version": "1.71.2",
4
4
  "description": "Pl Middle Layer",
5
5
  "keywords": [],
6
6
  "license": "UNLICENSED",
@@ -33,21 +33,22 @@
33
33
  "@milaboratories/columns-collection-driver": "0.2.4",
34
34
  "@milaboratories/computable": "2.9.8",
35
35
  "@milaboratories/pf-driver": "1.9.1",
36
- "@milaboratories/helpers": "1.14.5",
37
36
  "@milaboratories/pf-spec-driver": "1.5.1",
38
- "@milaboratories/pl-drivers": "1.16.19",
39
37
  "@milaboratories/pl-client": "3.16.0",
40
38
  "@milaboratories/pl-deployments": "3.0.16",
41
- "@milaboratories/pl-model-common": "1.48.0",
39
+ "@milaboratories/helpers": "1.14.5",
42
40
  "@milaboratories/pl-errors": "1.4.38",
43
- "@milaboratories/pl-http": "1.2.4",
41
+ "@milaboratories/pl-drivers": "1.16.19",
42
+ "@milaboratories/pl-flight-recorder": "0.2.0",
44
43
  "@milaboratories/pl-model-backend": "1.4.23",
44
+ "@milaboratories/pl-http": "1.2.4",
45
+ "@milaboratories/pl-model-common": "1.48.0",
45
46
  "@milaboratories/pl-tree": "1.14.2",
46
- "@milaboratories/pl-model-middle-layer": "1.32.0",
47
- "@milaboratories/ts-helpers": "1.8.6",
48
- "@platforma-sdk/block-tools": "2.15.0",
49
47
  "@milaboratories/resolve-helper": "1.1.3",
48
+ "@milaboratories/ts-helpers": "1.8.6",
49
+ "@milaboratories/pl-model-middle-layer": "1.32.0",
50
50
  "@platforma-sdk/model": "1.83.9",
51
+ "@platforma-sdk/block-tools": "2.15.0",
51
52
  "@platforma-sdk/workflow-tengo": "6.9.0"
52
53
  },
53
54
  "devDependencies": {
@@ -57,8 +58,8 @@
57
58
  "typescript": "7.0.2",
58
59
  "vitest": "^4.1.3",
59
60
  "@milaboratories/build-configs": "2.0.1",
60
- "@milaboratories/ts-builder": "1.7.2",
61
- "@milaboratories/ts-configs": "1.4.0"
61
+ "@milaboratories/ts-configs": "1.4.0",
62
+ "@milaboratories/ts-builder": "1.7.2"
62
63
  },
63
64
  "engines": {
64
65
  "node": ">=22.19.0"
@@ -8,6 +8,10 @@ import type { DeadlineSettings } from "./context";
8
8
  import { JsExecutionContext } from "./context";
9
9
  import type { BlockContextAny } from "../middle_layer/block_ctx";
10
10
  import { getDebugFlags } from "../debug";
11
+ import { recordModelRenderSync } from "@milaboratories/pl-flight-recorder";
12
+
13
+ /** Memory ceiling applied to every QuickJS runtime that evaluates model code. */
14
+ const QUICK_JS_MEMORY_LIMIT = 1024 * 1024 * 8;
11
15
 
12
16
  function logOutputStatus(
13
17
  handle: string,
@@ -106,7 +110,7 @@ export function computableFromRF(
106
110
 
107
111
  try {
108
112
  const runtime = scope.manage(env.quickJs.newRuntime());
109
- runtime.setMemoryLimit(1024 * 1024 * 8);
113
+ runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);
110
114
  runtime.setMaxStackSize(1024 * 320);
111
115
 
112
116
  let deadlineSettings: DeadlineSettings | undefined;
@@ -126,50 +130,83 @@ export function computableFromRF(
126
130
  { computableCtx: cCtx, blockCtx: ctx, mlEnv: env },
127
131
  );
128
132
 
129
- rCtx.evaluateBundle(code.content);
130
- const result = rCtx.runCallback(fh.handle);
131
-
132
- rCtx.resetComputableCtx();
133
-
134
- const toBeResolved = rCtx.computableHelper!.computablesToResolve;
135
-
136
- if (Object.keys(toBeResolved).length === 0) {
137
- const importedResult = rCtx.importObjectUniversal(result);
138
- if (getDebugFlags().logJsExecStat)
139
- console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);
140
- logOutputStatus(
141
- fh.handle,
142
- importedResult,
143
- cCtx.unstableMarker === undefined,
144
- -1,
145
- cCtx.unstableMarker,
146
- );
147
- return { ir: importedResult };
148
- }
149
-
150
- let recalculationCounter = 0;
151
- if (getDebugFlags().logOutputStatus)
152
- console.log(`Output ${fh.handle} scaffold calculated (not all computables resolved yet).`);
153
- keepVmAlive = true;
154
-
155
- return {
156
- ir: toBeResolved,
157
- postprocessValue: (resolved: Record<string, unknown>, { unstableMarker, stable }) => {
158
- // resolving futures
159
- for (const [handle, value] of Object.entries(resolved)) rCtx.runCallback(handle, value);
160
-
161
- // rendering result
162
- const renderedResult = rCtx.importObjectUniversal(result);
163
-
164
- // logging
165
- recalculationCounter++;
133
+ const flightRecorder = env.driverKit.flightRecorder;
134
+ const renderInfo = {
135
+ blockId: ctx.blockId,
136
+ key,
137
+ lambda: fh.handle,
138
+ // The sandbox has its own 8 MB ceiling, so the model's own objects can
139
+ // never exhaust the host heap. What can is the volume crossing out of
140
+ // it, which `stats` counts.
141
+ sandboxMemoryLimit: QUICK_JS_MEMORY_LIMIT,
142
+ getStats: () => ({ ...rCtx.stats }),
143
+ };
144
+
145
+ return recordModelRenderSync(flightRecorder, renderInfo, () => {
146
+ rCtx.evaluateBundle(code.content);
147
+ const result = rCtx.runCallback(fh.handle);
148
+
149
+ rCtx.resetComputableCtx();
150
+
151
+ const toBeResolved = rCtx.computableHelper!.computablesToResolve;
152
+
153
+ if (Object.keys(toBeResolved).length === 0) {
154
+ const importedResult = rCtx.importObjectUniversal(result);
166
155
  if (getDebugFlags().logJsExecStat)
167
- console.log(`[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`);
168
- logOutputStatus(fh.handle, renderedResult, stable, recalculationCounter, unstableMarker);
156
+ console.log(`[jsExec] ${key}: ${JSON.stringify(rCtx.stats)}`);
157
+ logOutputStatus(
158
+ fh.handle,
159
+ importedResult,
160
+ cCtx.unstableMarker === undefined,
161
+ -1,
162
+ cCtx.unstableMarker,
163
+ );
164
+ return { ir: importedResult };
165
+ }
169
166
 
170
- return renderedResult;
171
- },
172
- };
167
+ let recalculationCounter = 0;
168
+ if (getDebugFlags().logOutputStatus)
169
+ console.log(
170
+ `Output ${fh.handle} scaffold calculated (not all computables resolved yet).`,
171
+ );
172
+ keepVmAlive = true;
173
+
174
+ return {
175
+ ir: toBeResolved,
176
+ postprocessValue: (resolved: Record<string, unknown>, { unstableMarker, stable }) => {
177
+ recalculationCounter++;
178
+ // A deferred render resumes here, potentially many times, and each
179
+ // resumption can build joins of its own, so each gets its own span.
180
+ return recordModelRenderSync(
181
+ flightRecorder,
182
+ { ...renderInfo, recalculation: recalculationCounter },
183
+ () => {
184
+ // resolving futures
185
+ for (const [handle, value] of Object.entries(resolved))
186
+ rCtx.runCallback(handle, value);
187
+
188
+ // rendering result
189
+ const renderedResult = rCtx.importObjectUniversal(result);
190
+
191
+ // logging
192
+ if (getDebugFlags().logJsExecStat)
193
+ console.log(
194
+ `[jsExec] ${key} #${recalculationCounter}: ${JSON.stringify(rCtx.stats)}`,
195
+ );
196
+ logOutputStatus(
197
+ fh.handle,
198
+ renderedResult,
199
+ stable,
200
+ recalculationCounter,
201
+ unstableMarker,
202
+ );
203
+
204
+ return renderedResult;
205
+ },
206
+ );
207
+ },
208
+ };
209
+ });
173
210
  } catch (e) {
174
211
  keepVmAlive = false;
175
212
  throw e;
@@ -189,7 +226,7 @@ export function executeSingleLambda(
189
226
  const scope = new Scope();
190
227
  try {
191
228
  const runtime = scope.manage(quickJs.newRuntime());
192
- runtime.setMemoryLimit(1024 * 1024 * 8);
229
+ runtime.setMemoryLimit(QUICK_JS_MEMORY_LIMIT);
193
230
  runtime.setMaxStackSize(1024 * 320);
194
231
 
195
232
  let deadlineSettings: DeadlineSettings | undefined;
@@ -19,6 +19,12 @@ import { isAsyncDisposable } from "@milaboratories/helpers";
19
19
  import { HmacSha256Signer } from "@milaboratories/ts-helpers";
20
20
  import type { InternalPFrameDriver } from "../pool";
21
21
  import { createPFrameDriver } from "../pool";
22
+ import type { Recorder } from "@milaboratories/pl-flight-recorder";
23
+ import {
24
+ openFlightSession,
25
+ wrapDataDriver,
26
+ wrapModelDriver,
27
+ } from "@milaboratories/pl-flight-recorder";
22
28
  import type { DriverKitOps, DriverKitOpsConstructor } from "./ops";
23
29
  import { DefaultDriverKitOpsPaths, DefaultDriverKitOpsSettings } from "./ops";
24
30
 
@@ -46,6 +52,13 @@ export interface MiddleLayerDriverKit extends Sdk.DriverKit, AsyncDisposable {
46
52
  // override with wider interface
47
53
  readonly frontendDriver: DownloadUrlDriver;
48
54
 
55
+ /**
56
+ * Diagnostics log for the model layer, present only when flight recording is
57
+ * switched on. Render spans are written through it so that a join recorded by
58
+ * the instrumented pFrame driver can be attributed to the block that built it.
59
+ * */
60
+ readonly flightRecorder?: Recorder;
61
+
49
62
  /**
50
63
  * Signer is initialized from local secret in drivers initialization routine,
51
64
  * so constitutes a part of the driver kit
@@ -114,7 +127,7 @@ export async function initDriverKit(
114
127
  ops.virtualLocalStoragesOverride,
115
128
  );
116
129
 
117
- const pFrameDriver = await createPFrameDriver({
130
+ const rawPFrameDriver = await createPFrameDriver({
118
131
  blobDriver,
119
132
  logger: ops.logger,
120
133
  spillPath: ops.pframesSpillPath,
@@ -123,6 +136,19 @@ export async function initDriverKit(
123
136
  cacheOps: ops.parquetCacheOps,
124
137
  });
125
138
 
139
+ // Every join a block model builds and every row it reads back passes through
140
+ // this one driver, so instrumenting it here covers the whole model layer
141
+ // without touching the call sites. The session is undefined unless recording
142
+ // is switched on, in which case the driver is used exactly as before.
143
+ const flightSession = openFlightSession({ role: "middle-layer" });
144
+ const pFrameDriver: InternalPFrameDriver = flightSession
145
+ ? wrapDataDriver(
146
+ wrapModelDriver(rawPFrameDriver, flightSession.recorder, flightSession.registry),
147
+ flightSession.recorder,
148
+ flightSession.registry,
149
+ )
150
+ : rawPFrameDriver;
151
+
126
152
  const frontendDownloadDriver = new DownloadUrlDriver(
127
153
  ops.logger,
128
154
  pl.httpDispatcher,
@@ -140,6 +166,7 @@ export async function initDriverKit(
140
166
  uploadDriver,
141
167
  pFrameDriver,
142
168
  frontendDriver: frontendDownloadDriver,
169
+ flightRecorder: flightSession?.recorder,
143
170
  };
144
171
 
145
172
  const dispose = async () => {
@@ -147,6 +174,7 @@ export async function initDriverKit(
147
174
  isAsyncDisposable(driver) ? [driver[Symbol.asyncDispose]()] : [],
148
175
  );
149
176
  await Promise.all(disposePromises);
177
+ flightSession?.close("driver-kit-disposed");
150
178
  };
151
179
 
152
180
  return {