@milaboratories/pl-middle-layer 1.45.4 → 1.46.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.cjs +58 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/js_render/computable_context.cjs +37 -7
- package/dist/js_render/computable_context.cjs.map +1 -1
- package/dist/js_render/computable_context.d.ts.map +1 -1
- package/dist/js_render/computable_context.js +37 -7
- package/dist/js_render/computable_context.js.map +1 -1
- package/dist/js_render/context.cjs +12 -4
- package/dist/js_render/context.cjs.map +1 -1
- package/dist/js_render/context.d.ts +9 -0
- package/dist/js_render/context.d.ts.map +1 -1
- package/dist/js_render/context.js +12 -4
- package/dist/js_render/context.js.map +1 -1
- package/dist/js_render/index.cjs +1 -1
- package/dist/js_render/index.cjs.map +1 -1
- package/dist/js_render/index.js +1 -1
- package/dist/js_render/index.js.map +1 -1
- package/dist/middle_layer/block.cjs +7 -8
- package/dist/middle_layer/block.cjs.map +1 -1
- package/dist/middle_layer/block.d.ts +4 -4
- package/dist/middle_layer/block.d.ts.map +1 -1
- package/dist/middle_layer/block.js +7 -8
- package/dist/middle_layer/block.js.map +1 -1
- package/dist/middle_layer/block_ctx.cjs +67 -13
- package/dist/middle_layer/block_ctx.cjs.map +1 -1
- package/dist/middle_layer/block_ctx.d.ts +4 -7
- package/dist/middle_layer/block_ctx.d.ts.map +1 -1
- package/dist/middle_layer/block_ctx.js +68 -14
- package/dist/middle_layer/block_ctx.js.map +1 -1
- package/dist/middle_layer/block_ctx_unsafe.cjs +10 -3
- package/dist/middle_layer/block_ctx_unsafe.cjs.map +1 -1
- package/dist/middle_layer/block_ctx_unsafe.d.ts +1 -1
- package/dist/middle_layer/block_ctx_unsafe.d.ts.map +1 -1
- package/dist/middle_layer/block_ctx_unsafe.js +10 -3
- package/dist/middle_layer/block_ctx_unsafe.js.map +1 -1
- package/dist/middle_layer/frontend_path.cjs +1 -0
- package/dist/middle_layer/frontend_path.cjs.map +1 -1
- package/dist/middle_layer/frontend_path.js +1 -0
- package/dist/middle_layer/frontend_path.js.map +1 -1
- package/dist/middle_layer/middle_layer.cjs +1 -0
- package/dist/middle_layer/middle_layer.cjs.map +1 -1
- package/dist/middle_layer/middle_layer.d.ts +1 -1
- package/dist/middle_layer/middle_layer.d.ts.map +1 -1
- package/dist/middle_layer/middle_layer.js +1 -0
- package/dist/middle_layer/middle_layer.js.map +1 -1
- package/dist/middle_layer/project.cjs +75 -28
- package/dist/middle_layer/project.cjs.map +1 -1
- package/dist/middle_layer/project.d.ts +34 -7
- package/dist/middle_layer/project.d.ts.map +1 -1
- package/dist/middle_layer/project.js +76 -29
- package/dist/middle_layer/project.js.map +1 -1
- package/dist/middle_layer/project_overview.cjs +32 -11
- package/dist/middle_layer/project_overview.cjs.map +1 -1
- package/dist/middle_layer/project_overview.d.ts.map +1 -1
- package/dist/middle_layer/project_overview.js +32 -11
- package/dist/middle_layer/project_overview.js.map +1 -1
- package/dist/middle_layer/render.cjs +1 -1
- package/dist/middle_layer/render.cjs.map +1 -1
- package/dist/middle_layer/render.js +1 -1
- package/dist/middle_layer/render.js.map +1 -1
- package/dist/middle_layer/render.test.d.ts.map +1 -1
- package/dist/model/block_storage_helper.cjs +210 -0
- package/dist/model/block_storage_helper.cjs.map +1 -0
- package/dist/model/block_storage_helper.d.ts +98 -0
- package/dist/model/block_storage_helper.d.ts.map +1 -0
- package/dist/model/block_storage_helper.js +153 -0
- package/dist/model/block_storage_helper.js.map +1 -0
- package/dist/model/index.d.ts +2 -1
- package/dist/model/index.d.ts.map +1 -1
- package/dist/model/project_helper.cjs +177 -0
- package/dist/model/project_helper.cjs.map +1 -1
- package/dist/model/project_helper.d.ts +110 -1
- package/dist/model/project_helper.d.ts.map +1 -1
- package/dist/model/project_helper.js +178 -1
- package/dist/model/project_helper.js.map +1 -1
- package/dist/model/project_model.cjs +6 -3
- package/dist/model/project_model.cjs.map +1 -1
- package/dist/model/project_model.d.ts +3 -2
- package/dist/model/project_model.d.ts.map +1 -1
- package/dist/model/project_model.js +6 -4
- package/dist/model/project_model.js.map +1 -1
- package/dist/mutator/block-pack/block_pack.cjs +1 -2
- package/dist/mutator/block-pack/block_pack.cjs.map +1 -1
- package/dist/mutator/block-pack/block_pack.d.ts.map +1 -1
- package/dist/mutator/block-pack/block_pack.js +1 -2
- package/dist/mutator/block-pack/block_pack.js.map +1 -1
- package/dist/mutator/block-pack/frontend.cjs +1 -0
- package/dist/mutator/block-pack/frontend.cjs.map +1 -1
- package/dist/mutator/block-pack/frontend.js +1 -0
- package/dist/mutator/block-pack/frontend.js.map +1 -1
- package/dist/mutator/migration.cjs +64 -3
- package/dist/mutator/migration.cjs.map +1 -1
- package/dist/mutator/migration.d.ts.map +1 -1
- package/dist/mutator/migration.js +66 -5
- package/dist/mutator/migration.js.map +1 -1
- package/dist/mutator/project-v3.test.d.ts +2 -0
- package/dist/mutator/project-v3.test.d.ts.map +1 -0
- package/dist/mutator/project.cjs +282 -41
- package/dist/mutator/project.cjs.map +1 -1
- package/dist/mutator/project.d.ts +77 -12
- package/dist/mutator/project.d.ts.map +1 -1
- package/dist/mutator/project.js +283 -42
- package/dist/mutator/project.js.map +1 -1
- package/dist/pool/result_pool.cjs +9 -6
- package/dist/pool/result_pool.cjs.map +1 -1
- package/dist/pool/result_pool.d.ts.map +1 -1
- package/dist/pool/result_pool.js +9 -6
- package/dist/pool/result_pool.js.map +1 -1
- package/package.json +16 -16
- package/src/js_render/computable_context.ts +37 -7
- package/src/js_render/context.ts +12 -5
- package/src/js_render/index.ts +1 -1
- package/src/middle_layer/block.ts +13 -14
- package/src/middle_layer/block_ctx.ts +70 -23
- package/src/middle_layer/block_ctx_unsafe.ts +11 -4
- package/src/middle_layer/middle_layer.ts +2 -1
- package/src/middle_layer/project.ts +86 -40
- package/src/middle_layer/project_overview.ts +44 -20
- package/src/middle_layer/render.test.ts +1 -1
- package/src/middle_layer/render.ts +1 -1
- package/src/model/block_storage_helper.ts +213 -0
- package/src/model/index.ts +2 -1
- package/src/model/project_helper.ts +249 -1
- package/src/model/project_model.ts +9 -5
- package/src/mutator/block-pack/block_pack.ts +1 -2
- package/src/mutator/migration.ts +79 -6
- package/src/mutator/project-v3.test.ts +280 -0
- package/src/mutator/project.test.ts +27 -27
- package/src/mutator/project.ts +351 -68
- package/src/pool/result_pool.ts +11 -4
|
@@ -24,7 +24,7 @@ import { SynchronizedTreeState, treeDumpStats } from '@milaboratories/pl-tree';
|
|
|
24
24
|
import { setTimeout } from 'node:timers/promises';
|
|
25
25
|
import { frontendData } from './frontend_path';
|
|
26
26
|
import type { NavigationState } from '@milaboratories/pl-model-common';
|
|
27
|
-
import {
|
|
27
|
+
import { getBlockParameters, blockOutputs } from './block';
|
|
28
28
|
import type { FrontendData } from '../model/frontend';
|
|
29
29
|
import type { ProjectStructure } from '../model/project_model';
|
|
30
30
|
import { projectFieldName } from '../model/project_model';
|
|
@@ -33,8 +33,8 @@ import type { BlockPackInfo } from '../model/block_pack';
|
|
|
33
33
|
import type {
|
|
34
34
|
ProjectOverview,
|
|
35
35
|
AuthorMarker,
|
|
36
|
-
BlockStateInternal,
|
|
37
36
|
BlockSettings,
|
|
37
|
+
BlockStateInternalV3,
|
|
38
38
|
} from '@milaboratories/pl-model-middle-layer';
|
|
39
39
|
import { activeConfigs } from './active_cfg';
|
|
40
40
|
import { NavigationStates } from './navigation_states';
|
|
@@ -46,7 +46,7 @@ import { projectOverviewLight } from './project_overview_light';
|
|
|
46
46
|
import { applyProjectMigrations } from '../mutator/migration';
|
|
47
47
|
|
|
48
48
|
type BlockStateComputables = {
|
|
49
|
-
readonly fullState: Computable<
|
|
49
|
+
readonly fullState: Computable<BlockStateInternalV3>;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
function stringifyForDump(object: unknown): string {
|
|
@@ -131,7 +131,7 @@ export class Project {
|
|
|
131
131
|
await this.activeConfigs.getValue();
|
|
132
132
|
await setTimeout(this.env.ops.projectRefreshInterval, this.abortController.signal);
|
|
133
133
|
|
|
134
|
-
// Block computables
|
|
134
|
+
// Block computables housekeeping
|
|
135
135
|
const overviewLight = await this.overviewLight.getValue();
|
|
136
136
|
const existingBlocks = new Set(overviewLight.listOfBlocks);
|
|
137
137
|
// Doing cleanup for deleted blocks
|
|
@@ -187,6 +187,12 @@ export class Project {
|
|
|
187
187
|
const preparedBp = await this.env.bpPreparer.prepare(blockPackSpec);
|
|
188
188
|
const blockCfgContainer = await this.env.bpPreparer.getBlockConfigContainer(blockPackSpec);
|
|
189
189
|
const blockCfg = extractConfig(blockCfgContainer); // full content of this var should never be persisted
|
|
190
|
+
|
|
191
|
+
// Build NewBlockSpec based on model API version
|
|
192
|
+
const newBlockSpec = blockCfg.modelAPIVersion === 2
|
|
193
|
+
? { storageMode: 'fromModel' as const, blockPack: preparedBp }
|
|
194
|
+
: { storageMode: 'legacy' as const, blockPack: preparedBp, legacyState: canonicalize({ args: blockCfg.initialArgs, uiState: blockCfg.initialUiState })! };
|
|
195
|
+
|
|
190
196
|
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) => {
|
|
191
197
|
return mut.addBlock(
|
|
192
198
|
{
|
|
@@ -194,11 +200,7 @@ export class Project {
|
|
|
194
200
|
label: blockLabel,
|
|
195
201
|
renderingMode: blockCfg.renderingMode,
|
|
196
202
|
},
|
|
197
|
-
|
|
198
|
-
args: canonicalize(blockCfg.initialArgs)!,
|
|
199
|
-
uiState: canonicalize(blockCfg.initialUiState)!,
|
|
200
|
-
blockPack: preparedBp,
|
|
201
|
-
},
|
|
203
|
+
newBlockSpec,
|
|
202
204
|
before,
|
|
203
205
|
);
|
|
204
206
|
},
|
|
@@ -255,11 +257,17 @@ export class Project {
|
|
|
255
257
|
): Promise<void> {
|
|
256
258
|
const preparedBp = await this.env.bpPreparer.prepare(blockPackSpec);
|
|
257
259
|
const blockCfg = extractConfig(await this.env.bpPreparer.getBlockConfigContainer(blockPackSpec));
|
|
260
|
+
// resetState signals to mutator to reset storage
|
|
261
|
+
// For v2+ blocks: mutator gets initial storage directly via getInitialStorageInVM
|
|
262
|
+
// For v1 blocks: we pass the legacy state format
|
|
263
|
+
const resetState = resetArgs
|
|
264
|
+
? { state: blockCfg.modelAPIVersion === 2 ? {} : { args: blockCfg.initialArgs, uiState: blockCfg.initialUiState } }
|
|
265
|
+
: undefined;
|
|
258
266
|
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) =>
|
|
259
267
|
mut.migrateBlockPack(
|
|
260
268
|
blockId,
|
|
261
269
|
preparedBp,
|
|
262
|
-
|
|
270
|
+
resetState,
|
|
263
271
|
),
|
|
264
272
|
{ name: 'updateBlockPack', lockId: this.projectLockId },
|
|
265
273
|
);
|
|
@@ -326,49 +334,38 @@ export class Project {
|
|
|
326
334
|
await this.projectTree.refreshState();
|
|
327
335
|
}
|
|
328
336
|
|
|
329
|
-
// /** Update block label. */
|
|
330
|
-
// public async setBlockLabel(blockId: string, label: string, author?: AuthorMarker) {
|
|
331
|
-
// await withProjectAuthored(this.env.pl, this.rid, author, (mut) => {
|
|
332
|
-
// mut.setBlockLabel(blockId, label);
|
|
333
|
-
// });
|
|
334
|
-
// await this.projectTree.refreshState();
|
|
335
|
-
// }
|
|
336
|
-
|
|
337
337
|
/**
|
|
338
|
+
* @deprecated Use mutateBlockStorage() for V3 blocks.
|
|
338
339
|
* Sets block args, and changes whole project state accordingly.
|
|
339
340
|
* Along with setting arguments one can specify author marker, that will be
|
|
340
341
|
* transactionally associated with the block, to facilitate conflict resolution
|
|
341
342
|
* in collaborative editing scenario.
|
|
342
343
|
* */
|
|
343
344
|
public async setBlockArgs(blockId: string, args: unknown, author?: AuthorMarker) {
|
|
344
|
-
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) =>
|
|
345
|
-
mut.
|
|
346
|
-
|
|
345
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) => {
|
|
346
|
+
const state = mut.mergeBlockState(blockId, { args });
|
|
347
|
+
mut.setStates([{ modelAPIVersion: 1, blockId, state }]);
|
|
348
|
+
}, { name: 'setBlockArgs', lockId: this.projectLockId });
|
|
347
349
|
await this.projectTree.refreshState();
|
|
348
350
|
}
|
|
349
351
|
|
|
350
352
|
/**
|
|
353
|
+
* @deprecated Use mutateBlockStorage() for V3 blocks.
|
|
351
354
|
* Sets ui block state associated with the block.
|
|
352
355
|
* Along with setting arguments one can specify author marker, that will be
|
|
353
356
|
* transactionally associated with the block, to facilitate conflict resolution
|
|
354
357
|
* in collaborative editing scenario.
|
|
355
358
|
* */
|
|
356
359
|
public async setUiState(blockId: string, uiState: unknown, author?: AuthorMarker) {
|
|
357
|
-
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) =>
|
|
358
|
-
mut.
|
|
359
|
-
|
|
360
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) => {
|
|
361
|
+
const state = mut.mergeBlockState(blockId, { uiState });
|
|
362
|
+
mut.setStates([{ modelAPIVersion: 1, blockId, state }]);
|
|
363
|
+
}, { name: 'setUiState', lockId: this.projectLockId });
|
|
360
364
|
await this.projectTree.refreshState();
|
|
361
365
|
}
|
|
362
366
|
|
|
363
367
|
/**
|
|
364
|
-
*
|
|
365
|
-
* */
|
|
366
|
-
// eslint-disable-next-line @typescript-eslint/require-await
|
|
367
|
-
public async setNavigationState(blockId: string, state: NavigationState): Promise<void> {
|
|
368
|
-
this.navigationStates.setState(blockId, state);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
/**
|
|
368
|
+
* @deprecated Use mutateBlockStorage() for V3 blocks.
|
|
372
369
|
* Sets block args and ui state, and changes the whole project state accordingly.
|
|
373
370
|
* Along with setting arguments one can specify author marker, that will be
|
|
374
371
|
* transactionally associated with the block, to facilitate conflict resolution
|
|
@@ -376,16 +373,43 @@ export class Project {
|
|
|
376
373
|
* */
|
|
377
374
|
public async setBlockArgsAndUiState(
|
|
378
375
|
blockId: string,
|
|
379
|
-
args: unknown,
|
|
380
|
-
uiState: unknown,
|
|
376
|
+
args: unknown, // keep for v1/v2 compatibility
|
|
377
|
+
uiState: unknown, // keep for v1/v2 compatibility
|
|
381
378
|
author?: AuthorMarker,
|
|
382
379
|
) {
|
|
380
|
+
// Normalize to unified state format { args, uiState } for v1/v2 blocks
|
|
381
|
+
const state = { args, uiState };
|
|
383
382
|
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) => {
|
|
384
|
-
mut.setStates([{
|
|
383
|
+
mut.setStates([{ modelAPIVersion: 1, blockId, state }]);
|
|
385
384
|
}, { name: 'setBlockArgsAndUiState', lockId: this.projectLockId });
|
|
386
385
|
await this.projectTree.refreshState();
|
|
387
386
|
}
|
|
388
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Sets navigation state.
|
|
390
|
+
* */
|
|
391
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
392
|
+
public async setNavigationState(blockId: string, state: NavigationState): Promise<void> {
|
|
393
|
+
this.navigationStates.setState(blockId, state);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Mutates block storage for Model API v3 blocks.
|
|
398
|
+
* Applies a storage operation (e.g., 'update-data') which triggers
|
|
399
|
+
* args derivation (args(data) and prerunArgs(data)).
|
|
400
|
+
* The derived args are stored atomically with the data.
|
|
401
|
+
*
|
|
402
|
+
* @param blockId - The block ID
|
|
403
|
+
* @param payload - Storage mutation payload with operation and value
|
|
404
|
+
* @param author - Optional author marker for collaborative editing
|
|
405
|
+
*/
|
|
406
|
+
public async mutateBlockStorage(blockId: string, payload: { operation: string; value: unknown }, author?: AuthorMarker) {
|
|
407
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, author, (mut) =>
|
|
408
|
+
mut.setStates([{ modelAPIVersion: 2, blockId, payload }]),
|
|
409
|
+
{ name: 'mutateBlockStorage', lockId: this.projectLockId });
|
|
410
|
+
await this.projectTree.refreshState();
|
|
411
|
+
}
|
|
412
|
+
|
|
389
413
|
/** Update block settings */
|
|
390
414
|
public async setBlockSettings(blockId: string, newValue: BlockSettings) {
|
|
391
415
|
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, undefined, (mut) => {
|
|
@@ -394,6 +418,20 @@ export class Project {
|
|
|
394
418
|
await this.projectTree.refreshState();
|
|
395
419
|
}
|
|
396
420
|
|
|
421
|
+
/**
|
|
422
|
+
* Sets raw block storage content directly.
|
|
423
|
+
* This bypasses all normalization and VM transformations.
|
|
424
|
+
*
|
|
425
|
+
* @param blockId The block to set storage for
|
|
426
|
+
* @param rawStorageJson Raw storage as JSON string
|
|
427
|
+
*/
|
|
428
|
+
public async setBlockStorageRaw(blockId: string, rawStorageJson: string): Promise<void> {
|
|
429
|
+
await withProjectAuthored(this.env.projectHelper, this.env.pl, this.rid, undefined, (mut) => {
|
|
430
|
+
mut.setBlockStorageRaw(blockId, rawStorageJson);
|
|
431
|
+
}, { name: 'setBlockStorageRaw' });
|
|
432
|
+
await this.projectTree.refreshState();
|
|
433
|
+
}
|
|
434
|
+
|
|
397
435
|
/** Resets arguments and ui state of the block to initial state */
|
|
398
436
|
public async resetBlockArgsAndUiState(blockId: string, author?: AuthorMarker): Promise<void> {
|
|
399
437
|
await this.env.pl.withWriteTx('BlockInputsReset', async (tx) => {
|
|
@@ -406,8 +444,16 @@ export class Project {
|
|
|
406
444
|
);
|
|
407
445
|
const bpData = await tx.getResourceData(bpRid, false);
|
|
408
446
|
const config = extractConfig(cachedDeserialize<BlockPackInfo>(notEmpty(bpData.data)).config);
|
|
447
|
+
|
|
409
448
|
await withProjectAuthored(this.env.projectHelper, tx, this.rid, author, (prj) => {
|
|
410
|
-
|
|
449
|
+
if (config.modelAPIVersion === 2) {
|
|
450
|
+
// V2+: Reset to initial storage via VM
|
|
451
|
+
prj.resetToInitialStorage(blockId);
|
|
452
|
+
} else {
|
|
453
|
+
// V1: Use legacy state format
|
|
454
|
+
const initialState = { args: config.initialArgs, uiState: config.initialUiState };
|
|
455
|
+
prj.setStates([{ modelAPIVersion: 1, blockId, state: initialState }]);
|
|
456
|
+
}
|
|
411
457
|
}, { name: 'resetBlockArgsAndUiState', lockId: this.projectLockId });
|
|
412
458
|
await tx.commit();
|
|
413
459
|
});
|
|
@@ -423,7 +469,7 @@ export class Project {
|
|
|
423
469
|
const fullState = Computable.make(
|
|
424
470
|
(ctx) => {
|
|
425
471
|
return {
|
|
426
|
-
|
|
472
|
+
parameters: getBlockParameters(this.projectTree.entry(), blockId, ctx),
|
|
427
473
|
outputs,
|
|
428
474
|
navigationState: this.navigationStates.getState(blockId),
|
|
429
475
|
overview: this.overview,
|
|
@@ -438,10 +484,10 @@ export class Project {
|
|
|
438
484
|
: v.outputs;
|
|
439
485
|
|
|
440
486
|
return {
|
|
441
|
-
...v.
|
|
487
|
+
...v.parameters,
|
|
442
488
|
outputs: newOutputs,
|
|
443
489
|
navigationState: v.navigationState,
|
|
444
|
-
} as
|
|
490
|
+
} as BlockStateInternalV3;
|
|
445
491
|
},
|
|
446
492
|
},
|
|
447
493
|
);
|
|
@@ -461,7 +507,7 @@ export class Project {
|
|
|
461
507
|
* Returns a computable, that can be used to retrieve and watch full block state,
|
|
462
508
|
* including outputs, arguments, ui state.
|
|
463
509
|
* */
|
|
464
|
-
public getBlockState(blockId: string): Computable<
|
|
510
|
+
public getBlockState(blockId: string): Computable<BlockStateInternalV3> {
|
|
465
511
|
return this.getBlockComputables(blockId).fullState;
|
|
466
512
|
}
|
|
467
513
|
|
|
@@ -34,13 +34,11 @@ import { resourceIdToString, type ResourceId } from '@milaboratories/pl-client';
|
|
|
34
34
|
import * as R from 'remeda';
|
|
35
35
|
|
|
36
36
|
type BlockInfo = {
|
|
37
|
-
argsRid
|
|
37
|
+
argsRid?: ResourceId;
|
|
38
38
|
currentArguments: unknown;
|
|
39
39
|
prod?: ProdState;
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
type _CalculationStatus = 'Running' | 'Done';
|
|
43
|
-
|
|
44
42
|
type ProdState = {
|
|
45
43
|
finished: boolean;
|
|
46
44
|
|
|
@@ -87,9 +85,9 @@ export function projectOverview(
|
|
|
87
85
|
const cInputs = prj.traverse({
|
|
88
86
|
field: projectFieldName(id, 'currentArgs'),
|
|
89
87
|
assertFieldType: 'Dynamic',
|
|
90
|
-
|
|
88
|
+
stableIfNotFound: true,
|
|
91
89
|
});
|
|
92
|
-
const currentArguments = cInputs
|
|
90
|
+
const currentArguments = cInputs?.getDataAsJson<Record<string, unknown>>();
|
|
93
91
|
|
|
94
92
|
let prod: ProdState | undefined = undefined;
|
|
95
93
|
|
|
@@ -129,7 +127,7 @@ export function projectOverview(
|
|
|
129
127
|
};
|
|
130
128
|
}
|
|
131
129
|
|
|
132
|
-
infos.set(id, { currentArguments, prod, argsRid: cInputs
|
|
130
|
+
infos.set(id, { currentArguments, prod, argsRid: cInputs?.resourceInfo.id });
|
|
133
131
|
}
|
|
134
132
|
|
|
135
133
|
const currentGraph = productionGraph(structure, (id) => {
|
|
@@ -138,7 +136,13 @@ export function projectOverview(
|
|
|
138
136
|
const args = bInfo.currentArguments;
|
|
139
137
|
return {
|
|
140
138
|
args,
|
|
141
|
-
enrichmentTargets:
|
|
139
|
+
enrichmentTargets: bInfo.argsRid
|
|
140
|
+
? env.projectHelper.getEnrichmentTargets(
|
|
141
|
+
() => bpInfo.cfg,
|
|
142
|
+
() => args,
|
|
143
|
+
{ argsRid: bInfo.argsRid, blockPackRid: bpInfo.bpResourceId },
|
|
144
|
+
)
|
|
145
|
+
: undefined,
|
|
142
146
|
};
|
|
143
147
|
});
|
|
144
148
|
|
|
@@ -235,19 +239,24 @@ export function projectOverview(
|
|
|
235
239
|
},
|
|
236
240
|
}) as ComputableStableDefined<string[]>,
|
|
237
241
|
),
|
|
238
|
-
inputsValid:
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
242
|
+
// inputsValid: for modelAPIVersion 2, it's true if currentArgs exists (args derivation succeeded)
|
|
243
|
+
// For older blocks, use the inputsValid callback from config
|
|
244
|
+
inputsValid: cfg.modelAPIVersion === 2
|
|
245
|
+
? info.currentArguments !== undefined
|
|
246
|
+
: cfg.inputsValid
|
|
247
|
+
? computableFromCfgOrRF(
|
|
248
|
+
env,
|
|
249
|
+
blockCtxArgsOnly,
|
|
250
|
+
cfg.inputsValid,
|
|
251
|
+
codeWithInfo,
|
|
252
|
+
bpId,
|
|
253
|
+
).wrap({
|
|
254
|
+
recover: (cause) => {
|
|
255
|
+
env.logger.error(new Error('Error in block model inputsValid', { cause }));
|
|
256
|
+
return false;
|
|
257
|
+
},
|
|
258
|
+
}) as ComputableStableDefined<boolean>
|
|
259
|
+
: undefined,
|
|
251
260
|
sdkVersion: codeWithInfo?.sdkVersion,
|
|
252
261
|
featureFlags: codeWithInfo?.featureFlags ?? {},
|
|
253
262
|
isIncompatibleWithRuntime: false,
|
|
@@ -262,6 +271,20 @@ export function projectOverview(
|
|
|
262
271
|
})
|
|
263
272
|
.getDataAsJson() as BlockSettings;
|
|
264
273
|
|
|
274
|
+
// Get block storage info by calling VM function (only for Model API v2 blocks)
|
|
275
|
+
const blockStorageInfo = ifNotUndef(bp, ({ cfg }) => {
|
|
276
|
+
if (cfg.modelAPIVersion !== 2) {
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
const storageNode = prj.traverse({
|
|
280
|
+
field: projectFieldName(id, 'blockStorage'),
|
|
281
|
+
assertFieldType: 'Dynamic',
|
|
282
|
+
stableIfNotFound: true,
|
|
283
|
+
});
|
|
284
|
+
const rawStorageJson = storageNode?.getDataAsString();
|
|
285
|
+
return env.projectHelper.getStorageInfoInVM(cfg, rawStorageJson);
|
|
286
|
+
});
|
|
287
|
+
|
|
265
288
|
const updates = ifNotUndef(bp, ({ info }) =>
|
|
266
289
|
env.blockUpdateWatcher.get({ currentSpec: info.source, settings }),
|
|
267
290
|
);
|
|
@@ -292,6 +315,7 @@ export function projectOverview(
|
|
|
292
315
|
featureFlags,
|
|
293
316
|
isIncompatibleWithRuntime,
|
|
294
317
|
navigationState: navigationStates.getState(id),
|
|
318
|
+
blockStorageInfo,
|
|
295
319
|
};
|
|
296
320
|
});
|
|
297
321
|
|
|
@@ -25,6 +25,7 @@ export async function withMl(
|
|
|
25
25
|
});
|
|
26
26
|
ml.addRuntimeCapability('requiresUIAPIVersion', 1);
|
|
27
27
|
ml.addRuntimeCapability('requiresUIAPIVersion', 2);
|
|
28
|
+
ml.addRuntimeCapability('requiresUIAPIVersion', 3);
|
|
28
29
|
try {
|
|
29
30
|
await cb(ml, workFolder);
|
|
30
31
|
} finally {
|
|
@@ -159,7 +160,6 @@ test.skip('basic quickjs code', async () => {
|
|
|
159
160
|
vm.newFunction('nextId', () => {
|
|
160
161
|
return vm.newNumber(12); // vm.newArrayBuffer(new Uint8Array([1, 2]));
|
|
161
162
|
}).consume((fn) => vm.setProp(vm.global, 'nextId', fn));
|
|
162
|
-
console.log('asdasdas');
|
|
163
163
|
|
|
164
164
|
const nextId = vm.getString(
|
|
165
165
|
scope.manage(
|
|
@@ -21,7 +21,7 @@ export function computableFromCfgOrRF(
|
|
|
21
21
|
ops: Partial<ComputableRenderingOps> = {},
|
|
22
22
|
): Computable<unknown> {
|
|
23
23
|
if (isConfigLambda(cfgOrFh)) {
|
|
24
|
-
if (codeWithInfo === undefined) throw new Error('No code bundle.');
|
|
24
|
+
if (codeWithInfo === undefined) throw new Error('computableFromCfgOrRF: No code bundle.');
|
|
25
25
|
return computableFromRF(env, ctx, cfgOrFh, codeWithInfo, configKey, ops);
|
|
26
26
|
} else return computableFromCfg(env.driverKit, ctx, cfgOrFh, ops);
|
|
27
27
|
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block Storage Helper - Middle layer utilities for BlockStorage operations.
|
|
3
|
+
*
|
|
4
|
+
* This module provides the bridge between the SDK's BlockStorage abstraction
|
|
5
|
+
* and the middle layer's storage operations. Block developers never interact
|
|
6
|
+
* with this - they only see `state`.
|
|
7
|
+
*
|
|
8
|
+
* @module block_storage_helper
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
BlockStorage,
|
|
13
|
+
BlockStorageHandlers,
|
|
14
|
+
} from '@platforma-sdk/model';
|
|
15
|
+
import {
|
|
16
|
+
BLOCK_STORAGE_KEY,
|
|
17
|
+
BLOCK_STORAGE_SCHEMA_VERSION,
|
|
18
|
+
createBlockStorage,
|
|
19
|
+
defaultBlockStorageHandlers,
|
|
20
|
+
getPluginData,
|
|
21
|
+
getStorageData,
|
|
22
|
+
getStorageDataVersion,
|
|
23
|
+
isBlockStorage,
|
|
24
|
+
normalizeBlockStorage,
|
|
25
|
+
setPluginData,
|
|
26
|
+
updateStorageData,
|
|
27
|
+
updateStorageDataVersion,
|
|
28
|
+
} from '@platforma-sdk/model';
|
|
29
|
+
|
|
30
|
+
// Re-export types for convenience
|
|
31
|
+
export type { BlockStorage, BlockStorageHandlers };
|
|
32
|
+
|
|
33
|
+
// Re-export utilities that middle layer needs
|
|
34
|
+
export {
|
|
35
|
+
BLOCK_STORAGE_KEY,
|
|
36
|
+
BLOCK_STORAGE_SCHEMA_VERSION,
|
|
37
|
+
createBlockStorage,
|
|
38
|
+
defaultBlockStorageHandlers,
|
|
39
|
+
getPluginData,
|
|
40
|
+
getStorageData,
|
|
41
|
+
getStorageDataVersion,
|
|
42
|
+
isBlockStorage,
|
|
43
|
+
normalizeBlockStorage,
|
|
44
|
+
setPluginData,
|
|
45
|
+
updateStorageData,
|
|
46
|
+
updateStorageDataVersion,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Parses raw blockStorage data (from resource tree) into BlockStorage.
|
|
51
|
+
* Handles both legacy format (raw state) and new format (BlockStorage).
|
|
52
|
+
*
|
|
53
|
+
* @param rawData - Raw data from blockStorage field (may be string or parsed object)
|
|
54
|
+
* @returns Normalized BlockStorage object
|
|
55
|
+
*/
|
|
56
|
+
export function parseBlockStorage<TState = unknown>(rawData: unknown): BlockStorage<TState> {
|
|
57
|
+
if (rawData === undefined || rawData === null) {
|
|
58
|
+
return createBlockStorage<TState>({} as TState);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// If it's a string, parse it first
|
|
62
|
+
if (typeof rawData === 'string') {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(rawData);
|
|
65
|
+
return normalizeBlockStorage<TState>(parsed);
|
|
66
|
+
} catch {
|
|
67
|
+
// If parsing fails, treat the string as the state itself
|
|
68
|
+
return createBlockStorage(rawData as unknown as TState);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return normalizeBlockStorage<TState>(rawData);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extracts the user-facing data from BlockStorage.
|
|
77
|
+
* This is what block developers see in their .args() callbacks.
|
|
78
|
+
*
|
|
79
|
+
* @param storage - The BlockStorage object
|
|
80
|
+
* @returns The data value that developers work with
|
|
81
|
+
*/
|
|
82
|
+
export function extractDataForDeveloper<TState>(storage: BlockStorage<TState>): TState {
|
|
83
|
+
return getStorageData(storage);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Creates a new BlockStorage with updated state.
|
|
88
|
+
* Used when setState is called from the frontend.
|
|
89
|
+
*
|
|
90
|
+
* @param currentStorage - Current BlockStorage (or undefined for new blocks)
|
|
91
|
+
* @param newState - New state from developer
|
|
92
|
+
* @param handlers - Optional custom handlers (defaults to standard handlers)
|
|
93
|
+
* @returns Updated BlockStorage
|
|
94
|
+
*/
|
|
95
|
+
export function applyStateUpdate<TState>(
|
|
96
|
+
currentStorage: BlockStorage<TState> | undefined,
|
|
97
|
+
newState: TState,
|
|
98
|
+
handlers: BlockStorageHandlers<TState> = defaultBlockStorageHandlers as BlockStorageHandlers<TState>,
|
|
99
|
+
): BlockStorage<TState> {
|
|
100
|
+
const storage = currentStorage ?? createBlockStorage<TState>({} as TState);
|
|
101
|
+
const transform = handlers.transformStateForStorage ?? defaultBlockStorageHandlers.transformStateForStorage;
|
|
102
|
+
return transform(storage, newState) as BlockStorage<TState>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Serializes BlockStorage for writing to the resource tree.
|
|
107
|
+
*
|
|
108
|
+
* @param storage - The BlockStorage to serialize
|
|
109
|
+
* @returns JSON string
|
|
110
|
+
*/
|
|
111
|
+
export function serializeBlockStorage(storage: BlockStorage): string {
|
|
112
|
+
return JSON.stringify(storage);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Checks if the storage needs migration based on version.
|
|
117
|
+
*
|
|
118
|
+
* @param storage - Current BlockStorage
|
|
119
|
+
* @param targetVersion - Target schema version
|
|
120
|
+
* @returns true if migration is needed
|
|
121
|
+
*/
|
|
122
|
+
export function needsMigration(storage: BlockStorage, targetVersion: number): boolean {
|
|
123
|
+
return getStorageDataVersion(storage) < targetVersion;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Migrates BlockStorage from one version to another.
|
|
128
|
+
*
|
|
129
|
+
* @param storage - Current BlockStorage
|
|
130
|
+
* @param fromVersion - Source version
|
|
131
|
+
* @param toVersion - Target version
|
|
132
|
+
* @param handlers - Optional custom handlers
|
|
133
|
+
* @returns Migrated BlockStorage
|
|
134
|
+
*/
|
|
135
|
+
export function migrateBlockStorage<TState>(
|
|
136
|
+
storage: BlockStorage<TState>,
|
|
137
|
+
fromVersion: number,
|
|
138
|
+
toVersion: number,
|
|
139
|
+
handlers: BlockStorageHandlers<TState> = defaultBlockStorageHandlers as BlockStorageHandlers<TState>,
|
|
140
|
+
): BlockStorage<TState> {
|
|
141
|
+
const migrate = handlers.migrateStorage ?? defaultBlockStorageHandlers.migrateStorage;
|
|
142
|
+
return migrate(storage, fromVersion, toVersion) as BlockStorage<TState>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* V1/V2 legacy format - state stored as { args, uiState }
|
|
147
|
+
*/
|
|
148
|
+
export interface LegacyV1V2State {
|
|
149
|
+
args?: unknown;
|
|
150
|
+
uiState?: unknown;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Checks if the raw data is in legacy V1/V2 format.
|
|
155
|
+
* V1/V2 blocks stored { args, uiState } directly, not wrapped in BlockStorage.
|
|
156
|
+
*
|
|
157
|
+
* @param rawData - Raw data from blockStorage field
|
|
158
|
+
* @returns true if data is in legacy format
|
|
159
|
+
*/
|
|
160
|
+
export function isLegacyV1V2Format(rawData: unknown): rawData is LegacyV1V2State {
|
|
161
|
+
if (rawData === null || typeof rawData !== 'object') return false;
|
|
162
|
+
// If it has the discriminator, it's BlockStorage format
|
|
163
|
+
if (isBlockStorage(rawData)) return false;
|
|
164
|
+
|
|
165
|
+
const obj = rawData as Record<string, unknown>;
|
|
166
|
+
// Legacy format has 'args' and/or 'uiState' at top level, no discriminator
|
|
167
|
+
return ('args' in obj || 'uiState' in obj);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Converts legacy V1/V2 format to BlockStorage.
|
|
172
|
+
* For backward compatibility with existing blocks.
|
|
173
|
+
*
|
|
174
|
+
* @param legacyData - Data in { args, uiState } format
|
|
175
|
+
* @returns BlockStorage with legacy data as state
|
|
176
|
+
*/
|
|
177
|
+
export function convertLegacyToBlockStorage(legacyData: LegacyV1V2State): BlockStorage<LegacyV1V2State> {
|
|
178
|
+
return createBlockStorage(legacyData, 1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Smart parser that handles all storage formats:
|
|
183
|
+
* - New BlockStorage format
|
|
184
|
+
* - Legacy V1/V2 format ({ args, uiState })
|
|
185
|
+
* - Raw state (any other format)
|
|
186
|
+
*
|
|
187
|
+
* @param rawData - Raw data from blockStorage field
|
|
188
|
+
* @returns Normalized BlockStorage
|
|
189
|
+
*/
|
|
190
|
+
export function parseBlockStorageSmart<TState = unknown>(rawData: unknown): BlockStorage<TState> {
|
|
191
|
+
if (rawData === undefined || rawData === null) {
|
|
192
|
+
return createBlockStorage<TState>({} as TState);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// If it's a string, parse it first
|
|
196
|
+
let parsed = rawData;
|
|
197
|
+
if (typeof rawData === 'string') {
|
|
198
|
+
try {
|
|
199
|
+
parsed = JSON.parse(rawData);
|
|
200
|
+
} catch {
|
|
201
|
+
// If parsing fails, treat the string as the state itself
|
|
202
|
+
return createBlockStorage(rawData as unknown as TState);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check for legacy V1/V2 format
|
|
207
|
+
if (isLegacyV1V2Format(parsed)) {
|
|
208
|
+
return convertLegacyToBlockStorage(parsed) as unknown as BlockStorage<TState>;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Use standard normalization
|
|
212
|
+
return normalizeBlockStorage<TState>(parsed);
|
|
213
|
+
}
|
package/src/model/index.ts
CHANGED