@milaboratories/pl-middle-layer 1.36.4 → 1.37.1
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 +19 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2031 -1918
- package/dist/index.mjs.map +1 -1
- package/dist/js_render/computable_context.d.ts +68 -0
- package/dist/js_render/computable_context.d.ts.map +1 -0
- package/dist/js_render/context.d.ts +22 -64
- package/dist/js_render/context.d.ts.map +1 -1
- package/dist/js_render/index.d.ts +2 -0
- package/dist/js_render/index.d.ts.map +1 -1
- package/dist/middle_layer/block.d.ts.map +1 -1
- package/dist/middle_layer/block_ctx.d.ts +5 -0
- package/dist/middle_layer/block_ctx.d.ts.map +1 -1
- package/dist/middle_layer/middle_layer.d.ts +2 -0
- package/dist/middle_layer/middle_layer.d.ts.map +1 -1
- package/dist/middle_layer/project.d.ts.map +1 -1
- package/dist/middle_layer/project_overview.d.ts.map +1 -1
- package/dist/middle_layer/util.d.ts +2 -0
- package/dist/middle_layer/util.d.ts.map +1 -1
- package/dist/model/args.d.ts +4 -2
- package/dist/model/args.d.ts.map +1 -1
- package/dist/model/project_helper.d.ts +14 -0
- package/dist/model/project_helper.d.ts.map +1 -0
- package/dist/model/project_model_util.d.ts +14 -4
- package/dist/model/project_model_util.d.ts.map +1 -1
- package/dist/mutator/project.d.ts +16 -10
- package/dist/mutator/project.d.ts.map +1 -1
- package/package.json +14 -14
- package/src/js_render/computable_context.ts +753 -0
- package/src/js_render/context.ts +32 -720
- package/src/js_render/index.ts +37 -3
- package/src/middle_layer/block.ts +2 -0
- package/src/middle_layer/block_ctx.ts +6 -0
- package/src/middle_layer/middle_layer.ts +7 -2
- package/src/middle_layer/project.ts +15 -17
- package/src/middle_layer/project_overview.ts +13 -4
- package/src/middle_layer/util.ts +3 -1
- package/src/model/args.ts +12 -6
- package/src/model/project_helper.ts +41 -0
- package/src/model/project_model_util.test.ts +13 -4
- package/src/model/project_model_util.ts +37 -12
- package/src/mutator/project.test.ts +18 -12
- package/src/mutator/project.ts +159 -61
- package/src/mutator/template/template_render.test.ts +2 -2
package/src/js_render/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { MiddleLayerEnvironment } from '../middle_layer/middle_layer';
|
|
|
2
2
|
import type { Code, ConfigRenderLambda } from '@platforma-sdk/model';
|
|
3
3
|
import type { ComputableRenderingOps } from '@milaboratories/computable';
|
|
4
4
|
import { Computable } from '@milaboratories/computable';
|
|
5
|
+
import type { QuickJSWASMModule } from 'quickjs-emscripten';
|
|
5
6
|
import { Scope } from 'quickjs-emscripten';
|
|
6
7
|
import type { DeadlineSettings } from './context';
|
|
7
8
|
import { JsExecutionContext } from './context';
|
|
@@ -35,8 +36,9 @@ export function computableFromRF(
|
|
|
35
36
|
return false;
|
|
36
37
|
});
|
|
37
38
|
const vm = scope.manage(runtime.newContext());
|
|
38
|
-
const rCtx = new JsExecutionContext(scope, vm,
|
|
39
|
-
(s) => { deadlineSettings = s; },
|
|
39
|
+
const rCtx = new JsExecutionContext(scope, vm,
|
|
40
|
+
(s) => { deadlineSettings = s; },
|
|
41
|
+
{ computableCtx: cCtx, blockCtx: ctx, mlEnv: env });
|
|
40
42
|
|
|
41
43
|
rCtx.evaluateBundle(code.content);
|
|
42
44
|
const result = rCtx.runCallback(fh.handle);
|
|
@@ -49,7 +51,7 @@ export function computableFromRF(
|
|
|
49
51
|
console.log(`Output ${fh.handle} scaffold calculated.`);
|
|
50
52
|
|
|
51
53
|
return {
|
|
52
|
-
ir: rCtx.computablesToResolve,
|
|
54
|
+
ir: rCtx.computableHelper!.computablesToResolve,
|
|
53
55
|
postprocessValue: (resolved: Record<string, unknown>, { unstableMarker, stable }) => {
|
|
54
56
|
// resolving futures
|
|
55
57
|
for (const [handle, value] of Object.entries(resolved)) rCtx.runCallback(handle, value);
|
|
@@ -75,3 +77,35 @@ export function computableFromRF(
|
|
|
75
77
|
};
|
|
76
78
|
}, ops);
|
|
77
79
|
}
|
|
80
|
+
|
|
81
|
+
export function executeSingleLambda(
|
|
82
|
+
quickJs: QuickJSWASMModule,
|
|
83
|
+
fh: ConfigRenderLambda,
|
|
84
|
+
code: Code,
|
|
85
|
+
...args: unknown[]
|
|
86
|
+
): unknown {
|
|
87
|
+
const scope = new Scope();
|
|
88
|
+
try {
|
|
89
|
+
const runtime = scope.manage(quickJs.newRuntime());
|
|
90
|
+
runtime.setMemoryLimit(1024 * 1024 * 8);
|
|
91
|
+
runtime.setMaxStackSize(1024 * 320);
|
|
92
|
+
|
|
93
|
+
let deadlineSettings: DeadlineSettings | undefined;
|
|
94
|
+
runtime.setInterruptHandler(() => {
|
|
95
|
+
if (deadlineSettings === undefined) return false;
|
|
96
|
+
if (Date.now() > deadlineSettings.deadline) return true;
|
|
97
|
+
return false;
|
|
98
|
+
});
|
|
99
|
+
const vm = scope.manage(runtime.newContext());
|
|
100
|
+
const rCtx = new JsExecutionContext(scope, vm,
|
|
101
|
+
(s) => { deadlineSettings = s; });
|
|
102
|
+
|
|
103
|
+
// Initializing the model
|
|
104
|
+
rCtx.evaluateBundle(code.content);
|
|
105
|
+
|
|
106
|
+
// Running the lambda
|
|
107
|
+
return rCtx.importObjectUniversal(rCtx.runCallback(fh.handle, ...args));
|
|
108
|
+
} finally {
|
|
109
|
+
scope.dispose();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -43,7 +43,9 @@ export function blockArgsAndUiState(
|
|
|
43
43
|
const uiState = ctx.uiState(cCtx);
|
|
44
44
|
return {
|
|
45
45
|
author: prj.getKeyValueAsJson<AuthorMarker>(blockArgsAuthorKey(blockId)),
|
|
46
|
+
// @TODO add deserialization caching
|
|
46
47
|
args: deepFreeze(JSON.parse(ctx.args(cCtx))),
|
|
48
|
+
// @TODO add deserialization caching
|
|
47
49
|
ui: uiState !== undefined ? deepFreeze(JSON.parse(uiState)) : undefined,
|
|
48
50
|
};
|
|
49
51
|
}
|
|
@@ -13,6 +13,12 @@ import {
|
|
|
13
13
|
import { allBlocks } from '../model/project_model_util';
|
|
14
14
|
import { ResultPool } from '../pool/result_pool';
|
|
15
15
|
|
|
16
|
+
export type BlockContextMaterialized = {
|
|
17
|
+
readonly blockId: string;
|
|
18
|
+
readonly args: string;
|
|
19
|
+
readonly uiState?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
16
22
|
export type BlockContextArgsOnly = {
|
|
17
23
|
readonly blockId: string;
|
|
18
24
|
readonly args: (cCtx: ComputableCtx) => string;
|
|
@@ -37,6 +37,7 @@ import { V2RegistryProvider } from '../block_registry';
|
|
|
37
37
|
import type { Dispatcher } from 'undici';
|
|
38
38
|
import { RetryAgent } from 'undici';
|
|
39
39
|
import { getDebugFlags } from '../debug';
|
|
40
|
+
import { ProjectHelper } from '../model/project_helper';
|
|
40
41
|
|
|
41
42
|
export interface MiddleLayerEnvironment {
|
|
42
43
|
readonly pl: PlClient;
|
|
@@ -50,6 +51,7 @@ export interface MiddleLayerEnvironment {
|
|
|
50
51
|
readonly blockUpdateWatcher: BlockUpdateWatcher;
|
|
51
52
|
readonly quickJs: QuickJSWASMModule;
|
|
52
53
|
readonly driverKit: MiddleLayerDriverKit;
|
|
54
|
+
readonly projectHelper: ProjectHelper;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
/**
|
|
@@ -111,7 +113,7 @@ export class MiddleLayer {
|
|
|
111
113
|
meta: ProjectMeta,
|
|
112
114
|
author?: AuthorMarker,
|
|
113
115
|
): Promise<void> {
|
|
114
|
-
await withProjectAuthored(this.pl, rid, author, (prj) => {
|
|
116
|
+
await withProjectAuthored(this.env.projectHelper, this.pl, rid, author, (prj) => {
|
|
115
117
|
prj.setMeta(meta);
|
|
116
118
|
});
|
|
117
119
|
await this.projectListTree.refreshState();
|
|
@@ -251,6 +253,8 @@ export class MiddleLayer {
|
|
|
251
253
|
ops.frontendDownloadPath,
|
|
252
254
|
);
|
|
253
255
|
|
|
256
|
+
const quickJs = await getQuickJS();
|
|
257
|
+
|
|
254
258
|
const env: MiddleLayerEnvironment = {
|
|
255
259
|
pl,
|
|
256
260
|
signer: driverKit.signer,
|
|
@@ -266,7 +270,8 @@ export class MiddleLayer {
|
|
|
266
270
|
http: retryHttpDispatcher,
|
|
267
271
|
preferredUpdateChannel: ops.preferredUpdateChannel,
|
|
268
272
|
}),
|
|
269
|
-
quickJs
|
|
273
|
+
quickJs,
|
|
274
|
+
projectHelper: new ProjectHelper(quickJs),
|
|
270
275
|
};
|
|
271
276
|
|
|
272
277
|
const openedProjects = new WatchableValue<ResourceId[]>([]);
|
|
@@ -27,7 +27,7 @@ import { blockArgsAndUiState, blockOutputs } from './block';
|
|
|
27
27
|
import type { FrontendData } from '../model/frontend';
|
|
28
28
|
import type { ProjectStructure } from '../model/project_model';
|
|
29
29
|
import { projectFieldName } from '../model/project_model';
|
|
30
|
-
import { notEmpty } from '@milaboratories/ts-helpers';
|
|
30
|
+
import { cachedDeserialize, notEmpty } from '@milaboratories/ts-helpers';
|
|
31
31
|
import type { BlockPackInfo } from '../model/block_pack';
|
|
32
32
|
import type {
|
|
33
33
|
ProjectOverview,
|
|
@@ -107,7 +107,7 @@ export class Project {
|
|
|
107
107
|
private async refreshLoop(): Promise<void> {
|
|
108
108
|
while (!this.destroyed) {
|
|
109
109
|
try {
|
|
110
|
-
await withProject(this.env.pl, this.rid, (prj) => {
|
|
110
|
+
await withProject(this.env.projectHelper, this.env.pl, this.rid, (prj) => {
|
|
111
111
|
prj.doRefresh(this.env.ops.stagingRenderingRate);
|
|
112
112
|
});
|
|
113
113
|
await this.activeConfigs.getValue();
|
|
@@ -145,7 +145,7 @@ export class Project {
|
|
|
145
145
|
const preparedBp = await this.env.bpPreparer.prepare(blockPackSpec);
|
|
146
146
|
const blockCfgContainer = await this.env.bpPreparer.getBlockConfigContainer(blockPackSpec);
|
|
147
147
|
const blockCfg = extractConfig(blockCfgContainer); // full content of this var should never be persisted
|
|
148
|
-
await withProjectAuthored(this.env.pl, this.rid, author, (mut) =>
|
|
148
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) =>
|
|
149
149
|
mut.addBlock(
|
|
150
150
|
{
|
|
151
151
|
id: blockId,
|
|
@@ -177,7 +177,7 @@ export class Project {
|
|
|
177
177
|
): Promise<void> {
|
|
178
178
|
const preparedBp = await this.env.bpPreparer.prepare(blockPackSpec);
|
|
179
179
|
const blockCfg = extractConfig(await this.env.bpPreparer.getBlockConfigContainer(blockPackSpec));
|
|
180
|
-
await withProjectAuthored(this.env.pl, this.rid, author, (mut) =>
|
|
180
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) =>
|
|
181
181
|
mut.migrateBlockPack(
|
|
182
182
|
blockId,
|
|
183
183
|
preparedBp,
|
|
@@ -189,7 +189,7 @@ export class Project {
|
|
|
189
189
|
|
|
190
190
|
/** Deletes a block with all associated data. */
|
|
191
191
|
public async deleteBlock(blockId: string, author?: AuthorMarker): Promise<void> {
|
|
192
|
-
await withProjectAuthored(this.env.pl, this.rid, author, (mut) => mut.deleteBlock(blockId));
|
|
192
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) => mut.deleteBlock(blockId));
|
|
193
193
|
this.navigationStates.deleteBlock(blockId);
|
|
194
194
|
await this.projectTree.refreshState();
|
|
195
195
|
}
|
|
@@ -201,7 +201,7 @@ export class Project {
|
|
|
201
201
|
* an error will be thrown instead.
|
|
202
202
|
*/
|
|
203
203
|
public async reorderBlocks(blocks: string[], author?: AuthorMarker): Promise<void> {
|
|
204
|
-
await withProjectAuthored(this.env.pl, this.rid, author, (mut) => {
|
|
204
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) => {
|
|
205
205
|
const currentStructure = mut.structure;
|
|
206
206
|
if (currentStructure.groups.length !== 1)
|
|
207
207
|
throw new Error('Unexpected project structure, non-sinular block group');
|
|
@@ -233,7 +233,7 @@ export class Project {
|
|
|
233
233
|
* stale state.
|
|
234
234
|
* */
|
|
235
235
|
public async runBlock(blockId: string): Promise<void> {
|
|
236
|
-
await withProject(this.env.pl, this.rid, (mut) => mut.renderProduction([blockId], true));
|
|
236
|
+
await withProject(this.env.projectHelper, this.env.pl, this.rid, (mut) => mut.renderProduction([blockId], true));
|
|
237
237
|
await this.projectTree.refreshState();
|
|
238
238
|
}
|
|
239
239
|
|
|
@@ -243,7 +243,7 @@ export class Project {
|
|
|
243
243
|
* calculated.
|
|
244
244
|
* */
|
|
245
245
|
public async stopBlock(blockId: string): Promise<void> {
|
|
246
|
-
await withProject(this.env.pl, this.rid, (mut) => mut.stopProduction(blockId));
|
|
246
|
+
await withProject(this.env.projectHelper, this.env.pl, this.rid, (mut) => mut.stopProduction(blockId));
|
|
247
247
|
await this.projectTree.refreshState();
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -262,7 +262,7 @@ export class Project {
|
|
|
262
262
|
* in collaborative editing scenario.
|
|
263
263
|
* */
|
|
264
264
|
public async setBlockArgs(blockId: string, args: unknown, author?: AuthorMarker) {
|
|
265
|
-
await withProjectAuthored(this.env.pl, this.rid, author, (mut) =>
|
|
265
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) =>
|
|
266
266
|
mut.setArgs([{ blockId, args: canonicalize(args)! }]),
|
|
267
267
|
);
|
|
268
268
|
await this.projectTree.refreshState();
|
|
@@ -275,7 +275,7 @@ export class Project {
|
|
|
275
275
|
* in collaborative editing scenario.
|
|
276
276
|
* */
|
|
277
277
|
public async setUiState(blockId: string, uiState: unknown, author?: AuthorMarker) {
|
|
278
|
-
await withProjectAuthored(this.env.pl, this.rid, author, (mut) =>
|
|
278
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) =>
|
|
279
279
|
mut.setUiState(blockId, uiState === undefined ? undefined : canonicalize(uiState)!),
|
|
280
280
|
);
|
|
281
281
|
await this.projectTree.refreshState();
|
|
@@ -301,7 +301,7 @@ export class Project {
|
|
|
301
301
|
uiState: unknown,
|
|
302
302
|
author?: AuthorMarker,
|
|
303
303
|
) {
|
|
304
|
-
await withProjectAuthored(this.env.pl, this.rid, author, (mut) => {
|
|
304
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) => {
|
|
305
305
|
mut.setArgs([{ blockId, args: canonicalize(args)! }]);
|
|
306
306
|
mut.setUiState(blockId, canonicalize(uiState));
|
|
307
307
|
});
|
|
@@ -310,7 +310,7 @@ export class Project {
|
|
|
310
310
|
|
|
311
311
|
/** Update block settings */
|
|
312
312
|
public async setBlockSettings(blockId: string, newValue: BlockSettings) {
|
|
313
|
-
await withProjectAuthored(this.env.pl, this.rid, undefined, (mut) => {
|
|
313
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, undefined, (mut) => {
|
|
314
314
|
mut.setBlockSettings(blockId, newValue);
|
|
315
315
|
});
|
|
316
316
|
await this.projectTree.refreshState();
|
|
@@ -327,10 +327,8 @@ export class Project {
|
|
|
327
327
|
(await tx.getField(field(bpHolderRid, Pl.HolderRefField))).value,
|
|
328
328
|
);
|
|
329
329
|
const bpData = await tx.getResourceData(bpRid, false);
|
|
330
|
-
const config = extractConfig((
|
|
331
|
-
|
|
332
|
-
) as BlockPackInfo).config);
|
|
333
|
-
await withProjectAuthored(tx, this.rid, author, (prj) => {
|
|
330
|
+
const config = extractConfig((cachedDeserialize(notEmpty(bpData.data)) as BlockPackInfo).config);
|
|
331
|
+
await withProjectAuthored(this.env.projectHelper, tx, this.rid, author, (prj) => {
|
|
334
332
|
prj.setArgs([{ blockId, args: canonicalize(config.initialArgs)! }]);
|
|
335
333
|
prj.setUiState(blockId, canonicalize(config.initialUiState));
|
|
336
334
|
});
|
|
@@ -434,7 +432,7 @@ export class Project {
|
|
|
434
432
|
|
|
435
433
|
public static async init(env: MiddleLayerEnvironment, rid: ResourceId): Promise<Project> {
|
|
436
434
|
// Doing a no-op mutation to apply all migration and schema fixes
|
|
437
|
-
await withProject(env.pl, rid, (_) => {});
|
|
435
|
+
await withProject(env.projectHelper, env.pl, rid, (_) => {});
|
|
438
436
|
|
|
439
437
|
// Loading project tree
|
|
440
438
|
const projectTree = await SynchronizedTreeState.init(
|
|
@@ -29,11 +29,12 @@ import type { BlockSection } from '@platforma-sdk/model';
|
|
|
29
29
|
import { computableFromCfgOrRF } from './render';
|
|
30
30
|
import type { NavigationStates } from './navigation_states';
|
|
31
31
|
import { getBlockPackInfo } from './util';
|
|
32
|
-
import { resourceIdToString } from '@milaboratories/pl-client';
|
|
32
|
+
import { resourceIdToString, type ResourceId } from '@milaboratories/pl-client';
|
|
33
33
|
import * as R from 'remeda';
|
|
34
34
|
|
|
35
35
|
type BlockInfo = {
|
|
36
|
-
|
|
36
|
+
argsRid: ResourceId;
|
|
37
|
+
currentArguments: unknown;
|
|
37
38
|
prod?: ProdState;
|
|
38
39
|
};
|
|
39
40
|
|
|
@@ -127,10 +128,18 @@ export function projectOverview(
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
infos.set(id, { currentArguments, prod });
|
|
131
|
+
infos.set(id, { currentArguments, prod, argsRid: cInputs.resourceInfo.id });
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
const currentGraph = productionGraph(structure, (id) =>
|
|
134
|
+
const currentGraph = productionGraph(structure, (id) => {
|
|
135
|
+
const bpInfo = getBlockPackInfo(prj, id)!;
|
|
136
|
+
const bInfo = infos.get(id)!;
|
|
137
|
+
const args = bInfo.currentArguments;
|
|
138
|
+
return {
|
|
139
|
+
args,
|
|
140
|
+
enrichmentTargets: env.projectHelper.getEnrichmentTargets(() => bpInfo.cfg, () => args, { argsRid: bInfo.argsRid, blockPackRid: bpInfo.bpResourceId }),
|
|
141
|
+
};
|
|
142
|
+
});
|
|
134
143
|
|
|
135
144
|
const limbo = new Set(renderingState.blocksInLimbo);
|
|
136
145
|
|
package/src/middle_layer/util.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PlTreeNodeAccessor } from '@milaboratories/pl-tree';
|
|
2
2
|
import { projectFieldName } from '../model/project_model';
|
|
3
|
+
import type { ResourceId } from '@milaboratories/pl-client';
|
|
3
4
|
import { Pl } from '@milaboratories/pl-client';
|
|
4
5
|
import { ifNotUndef } from '../cfg_render/util';
|
|
5
6
|
import type { BlockPackInfo } from '../model/block_pack';
|
|
@@ -7,6 +8,7 @@ import type { BlockConfig } from '@platforma-sdk/model';
|
|
|
7
8
|
import { extractConfig } from '@platforma-sdk/model';
|
|
8
9
|
|
|
9
10
|
export type BlockPackInfoAndId = {
|
|
11
|
+
readonly bpResourceId: ResourceId;
|
|
10
12
|
/** To be added to computable keys, to force reload on config change */
|
|
11
13
|
readonly bpId: string;
|
|
12
14
|
/** Full block-pack info */
|
|
@@ -32,7 +34,7 @@ export function getBlockPackInfo(
|
|
|
32
34
|
(bpAcc) => {
|
|
33
35
|
const info = bpAcc.getDataAsJson<BlockPackInfo>()!;
|
|
34
36
|
const cfg = extractConfig(info.config);
|
|
35
|
-
return { bpId: bpAcc.resourceInfo.id.toString(), info, cfg };
|
|
37
|
+
return { bpResourceId: bpAcc.resourceInfo.id, bpId: bpAcc.resourceInfo.id.toString(), info, cfg };
|
|
36
38
|
},
|
|
37
39
|
);
|
|
38
40
|
}
|
package/src/model/args.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { assertNever } from '@milaboratories/ts-helpers';
|
|
2
2
|
import type { PlRef } from '@platforma-sdk/model';
|
|
3
3
|
|
|
4
|
-
export function outputRef(blockId: string, name: string): PlRef {
|
|
5
|
-
return { __isRef: true, blockId, name };
|
|
4
|
+
export function outputRef(blockId: string, name: string, requireEnrichments?: boolean): PlRef {
|
|
5
|
+
if (requireEnrichments) return { __isRef: true, blockId, name, requireEnrichments };
|
|
6
|
+
else return { __isRef: true, blockId, name };
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export function isBlockOutputReference(obj: unknown): obj is PlRef {
|
|
@@ -32,8 +33,11 @@ function addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?:
|
|
|
32
33
|
if (node === null) return;
|
|
33
34
|
|
|
34
35
|
if (isBlockOutputReference(node)) {
|
|
35
|
-
if (allowed === undefined || allowed.has(node.blockId))
|
|
36
|
-
|
|
36
|
+
if (allowed === undefined || allowed.has(node.blockId)) {
|
|
37
|
+
result.upstreams.add(node.blockId);
|
|
38
|
+
if (node.requireEnrichments)
|
|
39
|
+
result.upstreamsRequiringEnrichments.add(node.blockId);
|
|
40
|
+
} else result.missingReferences = true;
|
|
37
41
|
} else if (Array.isArray(node)) {
|
|
38
42
|
for (const child of node) addAllReferencedBlocks(result, child, allowed);
|
|
39
43
|
} else {
|
|
@@ -49,15 +53,17 @@ function addAllReferencedBlocks(result: BlockUpstreams, node: unknown, allowed?:
|
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
export interface BlockUpstreams {
|
|
52
|
-
/**
|
|
56
|
+
/** All direct block dependencies */
|
|
53
57
|
upstreams: Set<string>;
|
|
58
|
+
/** Direct block dependencies which enrichments are also required by current block */
|
|
59
|
+
upstreamsRequiringEnrichments: Set<string>;
|
|
54
60
|
/** True if not-allowed references was encountered */
|
|
55
61
|
missingReferences: boolean;
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
/** Extracts all resource ids referenced by args object. */
|
|
59
65
|
export function inferAllReferencedBlocks(args: unknown, allowed?: Set<string>): BlockUpstreams {
|
|
60
|
-
const result = { upstreams: new Set<string>(), missingReferences: false };
|
|
66
|
+
const result = { upstreams: new Set<string>(), upstreamsRequiringEnrichments: new Set<string>(), missingReferences: false };
|
|
61
67
|
addAllReferencedBlocks(result, args, allowed);
|
|
62
68
|
return result;
|
|
63
69
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { BlockConfig, PlRef } from '@platforma-sdk/model';
|
|
2
|
+
import { LRUCache } from 'lru-cache';
|
|
3
|
+
import type { QuickJSWASMModule } from 'quickjs-emscripten';
|
|
4
|
+
import { executeSingleLambda } from '../js_render';
|
|
5
|
+
import type { ResourceId } from '@milaboratories/pl-client';
|
|
6
|
+
|
|
7
|
+
type EnrichmentTargetsRequest = {
|
|
8
|
+
blockConfig: () => BlockConfig;
|
|
9
|
+
args: () => unknown;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type EnrichmentTargetsValue = {
|
|
13
|
+
value: PlRef[] | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export class ProjectHelper {
|
|
17
|
+
private readonly enrichmentTargetsCache = new LRUCache<string, EnrichmentTargetsValue, EnrichmentTargetsRequest>({
|
|
18
|
+
max: 256,
|
|
19
|
+
memoMethod: (_key, _value, { context }) => {
|
|
20
|
+
return { value: this.calculateEnrichmentTargets(context) };
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
constructor(private readonly quickJs: QuickJSWASMModule) {}
|
|
25
|
+
|
|
26
|
+
private calculateEnrichmentTargets(req: EnrichmentTargetsRequest): PlRef[] | undefined {
|
|
27
|
+
const blockConfig = req.blockConfig();
|
|
28
|
+
if (blockConfig.enrichmentTargets === undefined) return undefined;
|
|
29
|
+
const args = req.args();
|
|
30
|
+
const result = executeSingleLambda(this.quickJs, blockConfig.enrichmentTargets, blockConfig.code!, args) as PlRef[];
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public getEnrichmentTargets(blockConfig: () => BlockConfig, args: () => unknown, key?: { argsRid: ResourceId; blockPackRid: ResourceId }): PlRef[] | undefined {
|
|
35
|
+
const req = { blockConfig, args };
|
|
36
|
+
if (key === undefined)
|
|
37
|
+
return this.calculateEnrichmentTargets(req);
|
|
38
|
+
const cacheKey = `${key.argsRid}:${key.blockPackRid}`;
|
|
39
|
+
return this.enrichmentTargetsCache.memo(cacheKey, { context: req }).value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -4,7 +4,7 @@ import { outputRef } from './args';
|
|
|
4
4
|
import { PlRef } from '@platforma-sdk/model';
|
|
5
5
|
|
|
6
6
|
function toRefs(...ids: string[]): PlRef[] {
|
|
7
|
-
return ids.map((id) => outputRef(id, ''));
|
|
7
|
+
return ids.map((id) => outputRef(id, '', true));
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function simpleStructure(...ids: string[]): ProjectStructure {
|
|
@@ -26,7 +26,10 @@ describe('simple traverse', () => {
|
|
|
26
26
|
inputs.set('b4', toRefs('b3'));
|
|
27
27
|
inputs.set('b5', toRefs('b4'));
|
|
28
28
|
inputs.set('b6', toRefs('b2', 'b4'));
|
|
29
|
-
const pGraph1 = productionGraph(struct1, (id) =>
|
|
29
|
+
const pGraph1 = productionGraph(struct1, (id) => ({
|
|
30
|
+
args: inputs.get(id) ?? null,
|
|
31
|
+
enrichmentTargets: undefined,
|
|
32
|
+
}));
|
|
30
33
|
|
|
31
34
|
test.each([
|
|
32
35
|
{ roots: ['b2'], expectedDirectUpstreams: ['b1'], expectedDirectDownstreams: ['b6'], expectedUpstreams: ['b1'], expectedDownstreams: ['b6'] },
|
|
@@ -51,7 +54,10 @@ describe('simple diff', () => {
|
|
|
51
54
|
inputs.set('b2', toRefs('b1'));
|
|
52
55
|
inputs.set('b4', toRefs('b3'));
|
|
53
56
|
const sGraph1 = stagingGraph(struct1);
|
|
54
|
-
const pGraph1 = productionGraph(struct1, (id) =>
|
|
57
|
+
const pGraph1 = productionGraph(struct1, (id) => ({
|
|
58
|
+
args: inputs.get(id) ?? null,
|
|
59
|
+
enrichmentTargets: undefined,
|
|
60
|
+
}));
|
|
55
61
|
|
|
56
62
|
test.each([
|
|
57
63
|
{ struct2a: ['b1', 'b2', 'b3', 'b4'], expectedS: [], expectedP: [] },
|
|
@@ -79,7 +85,10 @@ describe('simple diff', () => {
|
|
|
79
85
|
])('$struct2a', ({ struct2a, expectedS, expectedP, onlyA, onlyB }) => {
|
|
80
86
|
const struct2: ProjectStructure = simpleStructure(...struct2a);
|
|
81
87
|
const sGraph2 = stagingGraph(struct2);
|
|
82
|
-
const pGraph2 = productionGraph(struct2, (id) =>
|
|
88
|
+
const pGraph2 = productionGraph(struct2, (id) => ({
|
|
89
|
+
args: inputs.get(id) ?? null,
|
|
90
|
+
enrichmentTargets: undefined,
|
|
91
|
+
}));
|
|
83
92
|
const sDiff = graphDiff(sGraph1, sGraph2);
|
|
84
93
|
const pDiff = graphDiff(pGraph1, pGraph2);
|
|
85
94
|
expect(sDiff.onlyInA).toEqual(new Set(onlyA ?? []));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Block, ProjectStructure } from './project_model';
|
|
2
2
|
import type { Optional, Writable } from 'utility-types';
|
|
3
3
|
import { inferAllReferencedBlocks } from './args';
|
|
4
|
+
import type { PlRef } from '@milaboratories/pl-model-common';
|
|
4
5
|
|
|
5
6
|
export function allBlocks(structure: ProjectStructure): Iterable<Block> {
|
|
6
7
|
return {
|
|
@@ -19,10 +20,16 @@ export interface BlockGraphNode {
|
|
|
19
20
|
readonly directUpstream: Set<string>;
|
|
20
21
|
/** Downstreams, calculated accounting for potential indirect block dependencies */
|
|
21
22
|
readonly downstream: Set<string>;
|
|
23
|
+
/** Downstream blocks enriching current block's outputs */
|
|
24
|
+
readonly enrichments: Set<string>;
|
|
22
25
|
/** Direct downstreams listing our block in it's args */
|
|
23
26
|
readonly directDownstream: Set<string>;
|
|
27
|
+
/** Upstream blocks that current block may enrich with its exports */
|
|
28
|
+
readonly enrichmentTargets: Set<string>;
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
export type BlockGraphDirection = 'upstream' | 'downstream' | 'directUpstream' | 'directDownstream' | 'enrichments' | 'enrichmentTargets';
|
|
32
|
+
|
|
26
33
|
export class BlockGraph {
|
|
27
34
|
/** Nodes are stored in the map in topological order */
|
|
28
35
|
public readonly nodes: Map<string, BlockGraphNode>;
|
|
@@ -32,7 +39,7 @@ export class BlockGraph {
|
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
public traverseIds(
|
|
35
|
-
direction:
|
|
42
|
+
direction: BlockGraphDirection,
|
|
36
43
|
...rootBlockIds: string[]
|
|
37
44
|
): Set<string> {
|
|
38
45
|
const all = new Set<string>();
|
|
@@ -41,7 +48,7 @@ export class BlockGraph {
|
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
public traverseIdsExcludingRoots(
|
|
44
|
-
direction:
|
|
51
|
+
direction: BlockGraphDirection,
|
|
45
52
|
...rootBlockIds: string[]
|
|
46
53
|
): Set<string> {
|
|
47
54
|
const result = this.traverseIds(direction, ...rootBlockIds);
|
|
@@ -50,7 +57,7 @@ export class BlockGraph {
|
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
public traverse(
|
|
53
|
-
direction:
|
|
60
|
+
direction: BlockGraphDirection,
|
|
54
61
|
rootBlockIds: string[],
|
|
55
62
|
cb: (node: BlockGraphNode) => void,
|
|
56
63
|
): void {
|
|
@@ -75,7 +82,7 @@ export class BlockGraph {
|
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
export function stagingGraph(structure: ProjectStructure) {
|
|
78
|
-
type WNode = Optional<Writable<BlockGraphNode>,
|
|
85
|
+
type WNode = Optional<Writable<BlockGraphNode>, BlockGraphDirection>;
|
|
79
86
|
const result = new Map<string, WNode>();
|
|
80
87
|
|
|
81
88
|
// Simple dependency graph from previous to next
|
|
@@ -92,9 +99,11 @@ export function stagingGraph(structure: ProjectStructure) {
|
|
|
92
99
|
result.set(id, current);
|
|
93
100
|
if (previous === undefined) {
|
|
94
101
|
current.directUpstream = current.upstream = new Set<string>();
|
|
102
|
+
current.enrichments = current.enrichmentTargets = new Set<string>();
|
|
95
103
|
} else {
|
|
96
104
|
current.directUpstream = current.upstream = new Set<string>([previous.id]);
|
|
97
105
|
previous.directDownstream = previous.downstream = new Set<string>([current.id]);
|
|
106
|
+
previous.enrichments = previous.enrichmentTargets = new Set<string>();
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
previous = current;
|
|
@@ -104,9 +113,14 @@ export function stagingGraph(structure: ProjectStructure) {
|
|
|
104
113
|
return new BlockGraph(result as Map<string, BlockGraphNode>);
|
|
105
114
|
}
|
|
106
115
|
|
|
116
|
+
export type ProductionGraphBlockInfo = {
|
|
117
|
+
args: unknown;
|
|
118
|
+
enrichmentTargets: PlRef[] | undefined;
|
|
119
|
+
};
|
|
120
|
+
|
|
107
121
|
export function productionGraph(
|
|
108
122
|
structure: ProjectStructure,
|
|
109
|
-
|
|
123
|
+
infoProvider: (blockId: string) => ProductionGraphBlockInfo | undefined,
|
|
110
124
|
): BlockGraph {
|
|
111
125
|
const resultMap = new Map<string, BlockGraphNode>();
|
|
112
126
|
// result graph is constructed to be able to perform traversal on incomplete graph
|
|
@@ -118,16 +132,19 @@ export function productionGraph(
|
|
|
118
132
|
// those dependencies that are possible under current topology
|
|
119
133
|
const allAbove = new Set<string>();
|
|
120
134
|
for (const { id } of allBlocks(structure)) {
|
|
121
|
-
const
|
|
135
|
+
const info = infoProvider(id);
|
|
122
136
|
|
|
123
137
|
// skipping those blocks for which we don't have args
|
|
124
|
-
if (
|
|
138
|
+
if (info === undefined) continue;
|
|
125
139
|
|
|
126
|
-
const
|
|
140
|
+
const references = inferAllReferencedBlocks(info.args, allAbove);
|
|
127
141
|
|
|
128
142
|
// The algorithm here adds all downstream blocks of direct upstreams as potential upstreams.
|
|
129
143
|
// They may produce additional columns, anchored in our direct upstream, those columns might be needed by the workflow.
|
|
130
|
-
const potentialUpstreams =
|
|
144
|
+
const potentialUpstreams = new Set([
|
|
145
|
+
...references.upstreams,
|
|
146
|
+
...resultGraph.traverseIds('enrichments', ...references.upstreamsRequiringEnrichments),
|
|
147
|
+
]);
|
|
131
148
|
|
|
132
149
|
// To minimize complexity of the graph, we leave only the closest upstreams, removing all their transitive dependencies,
|
|
133
150
|
// relying on the traversal mechanisms in BContexts and on UI level to connect our block with all upstreams
|
|
@@ -141,17 +158,25 @@ export function productionGraph(
|
|
|
141
158
|
}
|
|
142
159
|
}
|
|
143
160
|
|
|
161
|
+
// default assumption is that all direct upstreams are enrichment targets
|
|
162
|
+
const enrichmentTargets = info.enrichmentTargets === undefined
|
|
163
|
+
? new Set(references.upstreams)
|
|
164
|
+
: new Set(info.enrichmentTargets.map((t) => t.blockId));
|
|
165
|
+
|
|
144
166
|
const node: BlockGraphNode = {
|
|
145
167
|
id,
|
|
146
|
-
missingReferences:
|
|
168
|
+
missingReferences: references.missingReferences,
|
|
147
169
|
upstream: upstreams,
|
|
148
|
-
directUpstream:
|
|
170
|
+
directUpstream: references.upstreams,
|
|
171
|
+
enrichmentTargets,
|
|
149
172
|
downstream: new Set<string>(), // will be populated from downstream blocks
|
|
150
173
|
directDownstream: new Set<string>(), // will be populated from downstream blocks
|
|
174
|
+
enrichments: new Set<string>(), // will be populated from downstream blocks
|
|
151
175
|
};
|
|
152
176
|
resultMap.set(id, node);
|
|
153
|
-
|
|
177
|
+
references.upstreams.forEach((dep) => resultMap.get(dep)!.directDownstream.add(id));
|
|
154
178
|
upstreams.forEach((dep) => resultMap.get(dep)!.downstream.add(id));
|
|
179
|
+
enrichmentTargets.forEach((dep) => resultMap.get(dep)?.enrichments.add(id));
|
|
155
180
|
allAbove.add(id);
|
|
156
181
|
}
|
|
157
182
|
|