@milaboratories/pl-middle-layer 1.38.1 → 1.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +846 -831
- package/dist/index.mjs.map +1 -1
- package/dist/js_render/index.d.ts +48 -0
- package/dist/js_render/index.d.ts.map +1 -1
- package/package.json +9 -9
- package/src/js_render/index.ts +118 -50
|
@@ -3,6 +3,54 @@ import { BlockCodeWithInfo, ConfigRenderLambda } from '@platforma-sdk/model';
|
|
|
3
3
|
import { ComputableRenderingOps, Computable } from '@milaboratories/computable';
|
|
4
4
|
import { QuickJSWASMModule } from 'quickjs-emscripten';
|
|
5
5
|
import { BlockContextAny } from '../middle_layer/block_ctx';
|
|
6
|
+
/**
|
|
7
|
+
* Creates a Computable that executes a render function (`fh`) from a block's code in a QuickJS virtual machine.
|
|
8
|
+
* This function handles both synchronous and asynchronous execution patterns of the sandboxed JS code.
|
|
9
|
+
*
|
|
10
|
+
* The overall data flow is as follows:
|
|
11
|
+
* 1. A QuickJS VM is initialized.
|
|
12
|
+
* 2. A `JsExecutionContext` is created to bridge the host (TypeScript) and guest (QuickJS) environments. It injects a
|
|
13
|
+
* context object (`cfgRenderCtx`) into the VM, providing helper methods for the sandboxed code to interact with the
|
|
14
|
+
* platform (e.g., to request data).
|
|
15
|
+
* 3. The block's Javascript bundle is evaluated in the VM.
|
|
16
|
+
* 4. The specified render function (`fh.handle`) is executed.
|
|
17
|
+
*
|
|
18
|
+
* Two execution paths are possible depending on the behavior of the render function:
|
|
19
|
+
*
|
|
20
|
+
* ### Synchronous Path
|
|
21
|
+
* If the render function computes its result without requesting any external data requiring asynchronous calculations,
|
|
22
|
+
* it executes synchronously.
|
|
23
|
+
* - The `computablesToResolve` map in `JsExecutionContext` remains empty.
|
|
24
|
+
* - The function returns an object with an `ir` field holding the result (`{ ir: importedResult }`).
|
|
25
|
+
* Since `postprocessValue` is not specified, `ir` is treated as the final resolved value of the Computable.
|
|
26
|
+
* - The QuickJS VM is disposed of immediately as it's no longer needed.
|
|
27
|
+
*
|
|
28
|
+
* ### Asynchronous Path (with `postprocessValue`)
|
|
29
|
+
* If the render function needs external data requiring asynchronous calculations (e.g., fetching a file), it calls
|
|
30
|
+
* one of the injected helper methods. These methods don't return data directly. Instead, they:
|
|
31
|
+
* a. Create a new `Computable` for the data request (e.g., to fetch a blob).
|
|
32
|
+
* b. Register this new `Computable` in the `computablesToResolve` map.
|
|
33
|
+
* c. Return a handle (string) to the sandboxed code.
|
|
34
|
+
*
|
|
35
|
+
* In this case:
|
|
36
|
+
* - The initial execution of the render function returns a scaffold of the final result, which depends on the pending
|
|
37
|
+
* computables.
|
|
38
|
+
* - The `computablesToResolve` map is passed as the `ir` (initial result) to `Computable.makeRaw`.
|
|
39
|
+
* - The `postprocessValue` function is provided to handle the results once the computables are resolved.
|
|
40
|
+
* - The QuickJS VM is kept alive (`keepVmAlive = true`) because its state is needed in `postprocessValue`.
|
|
41
|
+
* - Once the `computable` framework resolves all dependencies, it calls `postprocessValue` with the resolved data.
|
|
42
|
+
* - `postprocessValue` feeds the resolved data back into the VM, allowing the sandboxed code to compute the final
|
|
43
|
+
* result.
|
|
44
|
+
* - The VM is eventually disposed of when the host Computable is destroyed.
|
|
45
|
+
*
|
|
46
|
+
* @param env The middle layer environment.
|
|
47
|
+
* @param ctx The block context.
|
|
48
|
+
* @param fh The config render lambda to execute.
|
|
49
|
+
* @param codeWithInfo The block's code and feature flags.
|
|
50
|
+
* @param configKey A key for the configuration, used for cache busting.
|
|
51
|
+
* @param ops Options for the computable.
|
|
52
|
+
* @returns A `Computable` that will resolve to the result of the lambda execution.
|
|
53
|
+
*/
|
|
6
54
|
export declare function computableFromRF(env: MiddleLayerEnvironment, ctx: BlockContextAny, fh: ConfigRenderLambda, codeWithInfo: BlockCodeWithInfo, configKey: string, ops?: Partial<ComputableRenderingOps>): Computable<unknown>;
|
|
7
55
|
export declare function executeSingleLambda(quickJs: QuickJSWASMModule, fh: ConfigRenderLambda, codeWithInfo: BlockCodeWithInfo, ...args: unknown[]): unknown;
|
|
8
56
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/js_render/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAI5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/js_render/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AAC3E,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAClF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAI5D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAYjE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,sBAAsB,EAC3B,GAAG,EAAE,eAAe,EACpB,EAAE,EAAE,kBAAkB,EACtB,YAAY,EAAE,iBAAiB,EAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,OAAO,CAAC,sBAAsB,CAAM,GACxC,UAAU,CAAC,OAAO,CAAC,CA4ErB;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,iBAAiB,EAC1B,EAAE,EAAE,kBAAkB,EACtB,YAAY,EAAE,iBAAiB,EAC/B,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,OAAO,CA4BT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milaboratories/pl-middle-layer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.39.0",
|
|
4
4
|
"description": "Pl Middle Layer",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.16.0"
|
|
@@ -32,22 +32,22 @@
|
|
|
32
32
|
"yaml": "^2.7.0",
|
|
33
33
|
"zod": "~3.23.8",
|
|
34
34
|
"remeda": "^2.22.6",
|
|
35
|
-
"@milaboratories/computable": "^2.6.0",
|
|
36
35
|
"@milaboratories/pl-http": "^1.1.4",
|
|
37
|
-
"@milaboratories/resolve-helper": "^1.1.0",
|
|
38
36
|
"@platforma-sdk/block-tools": "^2.5.63",
|
|
39
|
-
"@milaboratories/
|
|
40
|
-
"@milaboratories/pl-model-backend": "~1.1.2",
|
|
37
|
+
"@milaboratories/resolve-helper": "^1.1.0",
|
|
41
38
|
"@milaboratories/pl-client": "^2.11.2",
|
|
42
|
-
"@milaboratories/pl-
|
|
39
|
+
"@milaboratories/pl-drivers": "^1.6.0",
|
|
43
40
|
"@platforma-sdk/model": "~1.39.0",
|
|
44
|
-
"@milaboratories/pl-model-middle-layer": "~1.7.45",
|
|
45
|
-
"@milaboratories/ts-helpers": "^1.4.1",
|
|
46
41
|
"@milaboratories/pl-model-common": "~1.16.0",
|
|
42
|
+
"@milaboratories/computable": "^2.6.0",
|
|
43
|
+
"@milaboratories/pl-tree": "~1.7.0",
|
|
47
44
|
"@platforma-sdk/workflow-tengo": "4.9.2",
|
|
45
|
+
"@milaboratories/pl-model-backend": "~1.1.2",
|
|
46
|
+
"@milaboratories/pl-model-middle-layer": "~1.7.45",
|
|
47
|
+
"@milaboratories/pl-errors": "^1.1.9",
|
|
48
48
|
"@milaboratories/pl-config": "^1.6.1",
|
|
49
49
|
"@milaboratories/pl-deployments": "^2.4.3",
|
|
50
|
-
"@milaboratories/
|
|
50
|
+
"@milaboratories/ts-helpers": "^1.4.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"semver": "^7.7.2",
|
package/src/js_render/index.ts
CHANGED
|
@@ -9,6 +9,63 @@ import { JsExecutionContext } from './context';
|
|
|
9
9
|
import type { BlockContextAny } from '../middle_layer/block_ctx';
|
|
10
10
|
import { getDebugFlags } from '../debug';
|
|
11
11
|
|
|
12
|
+
function logOutputStatus(handle: string, renderedResult: unknown, stable: boolean, recalculationCounter: number, unstableMarker?: string) {
|
|
13
|
+
if (getDebugFlags().logOutputStatus && (getDebugFlags().logOutputStatus === 'any' || !stable)) {
|
|
14
|
+
if (stable)
|
|
15
|
+
console.log(`Stable output ${handle} calculated ${renderedResult !== undefined ? 'defined' : 'undefined'}; (#${recalculationCounter})`);
|
|
16
|
+
else
|
|
17
|
+
console.log(`Unstable output ${handle}; marker = ${unstableMarker}; ${renderedResult !== undefined ? 'defined' : 'undefined'} (#${recalculationCounter})`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Creates a Computable that executes a render function (`fh`) from a block's code in a QuickJS virtual machine.
|
|
23
|
+
* This function handles both synchronous and asynchronous execution patterns of the sandboxed JS code.
|
|
24
|
+
*
|
|
25
|
+
* The overall data flow is as follows:
|
|
26
|
+
* 1. A QuickJS VM is initialized.
|
|
27
|
+
* 2. A `JsExecutionContext` is created to bridge the host (TypeScript) and guest (QuickJS) environments. It injects a
|
|
28
|
+
* context object (`cfgRenderCtx`) into the VM, providing helper methods for the sandboxed code to interact with the
|
|
29
|
+
* platform (e.g., to request data).
|
|
30
|
+
* 3. The block's Javascript bundle is evaluated in the VM.
|
|
31
|
+
* 4. The specified render function (`fh.handle`) is executed.
|
|
32
|
+
*
|
|
33
|
+
* Two execution paths are possible depending on the behavior of the render function:
|
|
34
|
+
*
|
|
35
|
+
* ### Synchronous Path
|
|
36
|
+
* If the render function computes its result without requesting any external data requiring asynchronous calculations,
|
|
37
|
+
* it executes synchronously.
|
|
38
|
+
* - The `computablesToResolve` map in `JsExecutionContext` remains empty.
|
|
39
|
+
* - The function returns an object with an `ir` field holding the result (`{ ir: importedResult }`).
|
|
40
|
+
* Since `postprocessValue` is not specified, `ir` is treated as the final resolved value of the Computable.
|
|
41
|
+
* - The QuickJS VM is disposed of immediately as it's no longer needed.
|
|
42
|
+
*
|
|
43
|
+
* ### Asynchronous Path (with `postprocessValue`)
|
|
44
|
+
* If the render function needs external data requiring asynchronous calculations (e.g., fetching a file), it calls
|
|
45
|
+
* one of the injected helper methods. These methods don't return data directly. Instead, they:
|
|
46
|
+
* a. Create a new `Computable` for the data request (e.g., to fetch a blob).
|
|
47
|
+
* b. Register this new `Computable` in the `computablesToResolve` map.
|
|
48
|
+
* c. Return a handle (string) to the sandboxed code.
|
|
49
|
+
*
|
|
50
|
+
* In this case:
|
|
51
|
+
* - The initial execution of the render function returns a scaffold of the final result, which depends on the pending
|
|
52
|
+
* computables.
|
|
53
|
+
* - The `computablesToResolve` map is passed as the `ir` (initial result) to `Computable.makeRaw`.
|
|
54
|
+
* - The `postprocessValue` function is provided to handle the results once the computables are resolved.
|
|
55
|
+
* - The QuickJS VM is kept alive (`keepVmAlive = true`) because its state is needed in `postprocessValue`.
|
|
56
|
+
* - Once the `computable` framework resolves all dependencies, it calls `postprocessValue` with the resolved data.
|
|
57
|
+
* - `postprocessValue` feeds the resolved data back into the VM, allowing the sandboxed code to compute the final
|
|
58
|
+
* result.
|
|
59
|
+
* - The VM is eventually disposed of when the host Computable is destroyed.
|
|
60
|
+
*
|
|
61
|
+
* @param env The middle layer environment.
|
|
62
|
+
* @param ctx The block context.
|
|
63
|
+
* @param fh The config render lambda to execute.
|
|
64
|
+
* @param codeWithInfo The block's code and feature flags.
|
|
65
|
+
* @param configKey A key for the configuration, used for cache busting.
|
|
66
|
+
* @param ops Options for the computable.
|
|
67
|
+
* @returns A `Computable` that will resolve to the result of the lambda execution.
|
|
68
|
+
*/
|
|
12
69
|
export function computableFromRF(
|
|
13
70
|
env: MiddleLayerEnvironment,
|
|
14
71
|
ctx: BlockContextAny,
|
|
@@ -27,59 +84,70 @@ export function computableFromRF(
|
|
|
27
84
|
console.log(`Block lambda recalculation : ${key} (${cCtx.changeSourceMarker}; ${cCtx.bodyInvocations} invocations)`);
|
|
28
85
|
|
|
29
86
|
const scope = new Scope();
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
let deadlineSettings: DeadlineSettings | undefined;
|
|
37
|
-
runtime.setInterruptHandler(() => {
|
|
38
|
-
if (deadlineSettings === undefined) return false;
|
|
39
|
-
if (Date.now() > deadlineSettings.deadline) return true;
|
|
40
|
-
return false;
|
|
87
|
+
let keepVmAlive = false;
|
|
88
|
+
cCtx.addOnDestroy(() => {
|
|
89
|
+
// If keepVmAlive is false, the scope will be disposed by the finally block,
|
|
90
|
+
// no need to dispose it here.
|
|
91
|
+
if (keepVmAlive) scope.dispose();
|
|
41
92
|
});
|
|
42
|
-
const vm = scope.manage(runtime.newContext());
|
|
43
|
-
const rCtx = new JsExecutionContext(scope, vm,
|
|
44
|
-
(s) => { deadlineSettings = s; },
|
|
45
|
-
featureFlags,
|
|
46
|
-
{ computableCtx: cCtx, blockCtx: ctx, mlEnv: env });
|
|
47
|
-
|
|
48
|
-
rCtx.evaluateBundle(code.content);
|
|
49
|
-
const result = rCtx.runCallback(fh.handle);
|
|
50
|
-
|
|
51
|
-
rCtx.resetComputableCtx();
|
|
52
|
-
|
|
53
|
-
let recalculationCounter = 0;
|
|
54
|
-
|
|
55
|
-
if (getDebugFlags().logOutputStatus === 'any')
|
|
56
|
-
console.log(`Output ${fh.handle} scaffold calculated.`);
|
|
57
93
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
94
|
+
try {
|
|
95
|
+
const runtime = scope.manage(env.quickJs.newRuntime());
|
|
96
|
+
runtime.setMemoryLimit(1024 * 1024 * 8);
|
|
97
|
+
runtime.setMaxStackSize(1024 * 320);
|
|
98
|
+
|
|
99
|
+
let deadlineSettings: DeadlineSettings | undefined;
|
|
100
|
+
runtime.setInterruptHandler(() => {
|
|
101
|
+
if (deadlineSettings === undefined) return false;
|
|
102
|
+
if (Date.now() > deadlineSettings.deadline) return true;
|
|
103
|
+
return false;
|
|
104
|
+
});
|
|
105
|
+
const vm = scope.manage(runtime.newContext());
|
|
106
|
+
const rCtx = new JsExecutionContext(scope, vm,
|
|
107
|
+
(s) => { deadlineSettings = s; },
|
|
108
|
+
featureFlags,
|
|
109
|
+
{ computableCtx: cCtx, blockCtx: ctx, mlEnv: env });
|
|
110
|
+
|
|
111
|
+
rCtx.evaluateBundle(code.content);
|
|
112
|
+
const result = rCtx.runCallback(fh.handle);
|
|
113
|
+
|
|
114
|
+
rCtx.resetComputableCtx();
|
|
115
|
+
|
|
116
|
+
const toBeResolved = rCtx.computableHelper!.computablesToResolve;
|
|
117
|
+
|
|
118
|
+
if (Object.keys(toBeResolved).length === 0) {
|
|
119
|
+
const importedResult = rCtx.importObjectUniversal(result);
|
|
120
|
+
logOutputStatus(fh.handle, importedResult, cCtx.unstableMarker === undefined, -1, cCtx.unstableMarker);
|
|
121
|
+
return { ir: importedResult };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let recalculationCounter = 0;
|
|
125
|
+
if (getDebugFlags().logOutputStatus)
|
|
126
|
+
console.log(`Output ${fh.handle} scaffold calculated (not all computables resolved yet).`);
|
|
127
|
+
keepVmAlive = true;
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
ir: toBeResolved,
|
|
131
|
+
postprocessValue: (resolved: Record<string, unknown>, { unstableMarker, stable }) => {
|
|
61
132
|
// resolving futures
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return renderedResult;
|
|
81
|
-
},
|
|
82
|
-
};
|
|
133
|
+
for (const [handle, value] of Object.entries(resolved)) rCtx.runCallback(handle, value);
|
|
134
|
+
|
|
135
|
+
// rendering result
|
|
136
|
+
const renderedResult = rCtx.importObjectUniversal(result);
|
|
137
|
+
|
|
138
|
+
// logging
|
|
139
|
+
recalculationCounter++;
|
|
140
|
+
logOutputStatus(fh.handle, renderedResult, stable, recalculationCounter, unstableMarker);
|
|
141
|
+
|
|
142
|
+
return renderedResult;
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
} catch (e) {
|
|
146
|
+
keepVmAlive = false;
|
|
147
|
+
throw e;
|
|
148
|
+
} finally {
|
|
149
|
+
if (!keepVmAlive) scope.dispose();
|
|
150
|
+
}
|
|
83
151
|
}, ops);
|
|
84
152
|
}
|
|
85
153
|
|