@milaboratories/pl-middle-layer 1.45.5 → 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.
Files changed (132) hide show
  1. package/dist/index.cjs +58 -0
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +2 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/js_render/computable_context.cjs +37 -7
  6. package/dist/js_render/computable_context.cjs.map +1 -1
  7. package/dist/js_render/computable_context.d.ts.map +1 -1
  8. package/dist/js_render/computable_context.js +37 -7
  9. package/dist/js_render/computable_context.js.map +1 -1
  10. package/dist/js_render/context.cjs +12 -4
  11. package/dist/js_render/context.cjs.map +1 -1
  12. package/dist/js_render/context.d.ts +9 -0
  13. package/dist/js_render/context.d.ts.map +1 -1
  14. package/dist/js_render/context.js +12 -4
  15. package/dist/js_render/context.js.map +1 -1
  16. package/dist/js_render/index.cjs +1 -1
  17. package/dist/js_render/index.cjs.map +1 -1
  18. package/dist/js_render/index.js +1 -1
  19. package/dist/js_render/index.js.map +1 -1
  20. package/dist/middle_layer/block.cjs +7 -8
  21. package/dist/middle_layer/block.cjs.map +1 -1
  22. package/dist/middle_layer/block.d.ts +4 -4
  23. package/dist/middle_layer/block.d.ts.map +1 -1
  24. package/dist/middle_layer/block.js +7 -8
  25. package/dist/middle_layer/block.js.map +1 -1
  26. package/dist/middle_layer/block_ctx.cjs +67 -13
  27. package/dist/middle_layer/block_ctx.cjs.map +1 -1
  28. package/dist/middle_layer/block_ctx.d.ts +4 -7
  29. package/dist/middle_layer/block_ctx.d.ts.map +1 -1
  30. package/dist/middle_layer/block_ctx.js +68 -14
  31. package/dist/middle_layer/block_ctx.js.map +1 -1
  32. package/dist/middle_layer/block_ctx_unsafe.cjs +10 -3
  33. package/dist/middle_layer/block_ctx_unsafe.cjs.map +1 -1
  34. package/dist/middle_layer/block_ctx_unsafe.d.ts +1 -1
  35. package/dist/middle_layer/block_ctx_unsafe.d.ts.map +1 -1
  36. package/dist/middle_layer/block_ctx_unsafe.js +10 -3
  37. package/dist/middle_layer/block_ctx_unsafe.js.map +1 -1
  38. package/dist/middle_layer/frontend_path.cjs +1 -0
  39. package/dist/middle_layer/frontend_path.cjs.map +1 -1
  40. package/dist/middle_layer/frontend_path.js +1 -0
  41. package/dist/middle_layer/frontend_path.js.map +1 -1
  42. package/dist/middle_layer/middle_layer.cjs +1 -0
  43. package/dist/middle_layer/middle_layer.cjs.map +1 -1
  44. package/dist/middle_layer/middle_layer.d.ts +1 -1
  45. package/dist/middle_layer/middle_layer.d.ts.map +1 -1
  46. package/dist/middle_layer/middle_layer.js +1 -0
  47. package/dist/middle_layer/middle_layer.js.map +1 -1
  48. package/dist/middle_layer/project.cjs +75 -28
  49. package/dist/middle_layer/project.cjs.map +1 -1
  50. package/dist/middle_layer/project.d.ts +34 -7
  51. package/dist/middle_layer/project.d.ts.map +1 -1
  52. package/dist/middle_layer/project.js +76 -29
  53. package/dist/middle_layer/project.js.map +1 -1
  54. package/dist/middle_layer/project_overview.cjs +32 -11
  55. package/dist/middle_layer/project_overview.cjs.map +1 -1
  56. package/dist/middle_layer/project_overview.d.ts.map +1 -1
  57. package/dist/middle_layer/project_overview.js +32 -11
  58. package/dist/middle_layer/project_overview.js.map +1 -1
  59. package/dist/middle_layer/render.cjs +1 -1
  60. package/dist/middle_layer/render.cjs.map +1 -1
  61. package/dist/middle_layer/render.js +1 -1
  62. package/dist/middle_layer/render.js.map +1 -1
  63. package/dist/middle_layer/render.test.d.ts.map +1 -1
  64. package/dist/model/block_storage_helper.cjs +210 -0
  65. package/dist/model/block_storage_helper.cjs.map +1 -0
  66. package/dist/model/block_storage_helper.d.ts +98 -0
  67. package/dist/model/block_storage_helper.d.ts.map +1 -0
  68. package/dist/model/block_storage_helper.js +153 -0
  69. package/dist/model/block_storage_helper.js.map +1 -0
  70. package/dist/model/index.d.ts +2 -1
  71. package/dist/model/index.d.ts.map +1 -1
  72. package/dist/model/project_helper.cjs +177 -0
  73. package/dist/model/project_helper.cjs.map +1 -1
  74. package/dist/model/project_helper.d.ts +110 -1
  75. package/dist/model/project_helper.d.ts.map +1 -1
  76. package/dist/model/project_helper.js +178 -1
  77. package/dist/model/project_helper.js.map +1 -1
  78. package/dist/model/project_model.cjs +6 -3
  79. package/dist/model/project_model.cjs.map +1 -1
  80. package/dist/model/project_model.d.ts +3 -2
  81. package/dist/model/project_model.d.ts.map +1 -1
  82. package/dist/model/project_model.js +6 -4
  83. package/dist/model/project_model.js.map +1 -1
  84. package/dist/mutator/block-pack/block_pack.cjs +1 -2
  85. package/dist/mutator/block-pack/block_pack.cjs.map +1 -1
  86. package/dist/mutator/block-pack/block_pack.d.ts.map +1 -1
  87. package/dist/mutator/block-pack/block_pack.js +1 -2
  88. package/dist/mutator/block-pack/block_pack.js.map +1 -1
  89. package/dist/mutator/block-pack/frontend.cjs +1 -0
  90. package/dist/mutator/block-pack/frontend.cjs.map +1 -1
  91. package/dist/mutator/block-pack/frontend.js +1 -0
  92. package/dist/mutator/block-pack/frontend.js.map +1 -1
  93. package/dist/mutator/migration.cjs +64 -3
  94. package/dist/mutator/migration.cjs.map +1 -1
  95. package/dist/mutator/migration.d.ts.map +1 -1
  96. package/dist/mutator/migration.js +66 -5
  97. package/dist/mutator/migration.js.map +1 -1
  98. package/dist/mutator/project-v3.test.d.ts +2 -0
  99. package/dist/mutator/project-v3.test.d.ts.map +1 -0
  100. package/dist/mutator/project.cjs +282 -41
  101. package/dist/mutator/project.cjs.map +1 -1
  102. package/dist/mutator/project.d.ts +77 -12
  103. package/dist/mutator/project.d.ts.map +1 -1
  104. package/dist/mutator/project.js +283 -42
  105. package/dist/mutator/project.js.map +1 -1
  106. package/dist/pool/result_pool.cjs +9 -6
  107. package/dist/pool/result_pool.cjs.map +1 -1
  108. package/dist/pool/result_pool.d.ts.map +1 -1
  109. package/dist/pool/result_pool.js +9 -6
  110. package/dist/pool/result_pool.js.map +1 -1
  111. package/package.json +17 -17
  112. package/src/js_render/computable_context.ts +37 -7
  113. package/src/js_render/context.ts +12 -5
  114. package/src/js_render/index.ts +1 -1
  115. package/src/middle_layer/block.ts +13 -14
  116. package/src/middle_layer/block_ctx.ts +70 -23
  117. package/src/middle_layer/block_ctx_unsafe.ts +11 -4
  118. package/src/middle_layer/middle_layer.ts +2 -1
  119. package/src/middle_layer/project.ts +86 -40
  120. package/src/middle_layer/project_overview.ts +44 -20
  121. package/src/middle_layer/render.test.ts +1 -1
  122. package/src/middle_layer/render.ts +1 -1
  123. package/src/model/block_storage_helper.ts +213 -0
  124. package/src/model/index.ts +2 -1
  125. package/src/model/project_helper.ts +249 -1
  126. package/src/model/project_model.ts +9 -5
  127. package/src/mutator/block-pack/block_pack.ts +1 -2
  128. package/src/mutator/migration.ts +79 -6
  129. package/src/mutator/project-v3.test.ts +280 -0
  130. package/src/mutator/project.test.ts +27 -27
  131. package/src/mutator/project.ts +351 -68
  132. package/src/pool/result_pool.ts +11 -4
@@ -7,7 +7,7 @@ import { InitialBlockSettings } from '@milaboratories/pl-model-middle-layer';
7
7
  import Denque from 'denque';
8
8
  import { getPreparedExportTemplateEnvelope, exportContext } from './context_export.js';
9
9
  import { loadTemplate } from './template/template_loading.js';
10
- import { notEmpty, canonicalJsonBytes, canonicalJsonGzBytes, cachedDeserialize } from '@milaboratories/ts-helpers';
10
+ import { notEmpty, canonicalJsonBytes, cachedDeserialize, cachedDecode } from '@milaboratories/ts-helpers';
11
11
  import { extractConfig, UiError } from '@platforma-sdk/model';
12
12
  import { getDebugFlags } from '../debug/index.js';
13
13
 
@@ -55,19 +55,57 @@ class BlockInfo {
55
55
  throw new Error('inconsistent stage cache fields');
56
56
  if (this.fields.blockPack === undefined)
57
57
  throw new Error('no block pack field');
58
- if (this.fields.currentArgs === undefined)
59
- throw new Error('no current args field');
58
+ if (this.fields.blockStorage === undefined)
59
+ throw new Error('no block storage field');
60
60
  }
61
- currentArgsC = cached(() => this.fields.currentArgs.modCount, () => cachedDeserialize(this.fields.currentArgs.value));
61
+ currentArgsC = cached(() => this.fields.currentArgs?.modCount, () => {
62
+ const bin = this.fields.currentArgs?.value;
63
+ if (bin === undefined)
64
+ return undefined;
65
+ return cachedDeserialize(bin);
66
+ });
67
+ blockStorageC = cached(() => this.fields.blockStorage.modCount, () => {
68
+ const bin = this.fields.blockStorage?.value;
69
+ if (bin === undefined)
70
+ return undefined;
71
+ return cachedDeserialize(bin);
72
+ });
73
+ blockStorageJ = cached(() => this.fields.blockStorage.modCount, () => {
74
+ const bin = this.fields.blockStorage?.value;
75
+ if (bin === undefined)
76
+ return undefined;
77
+ return cachedDecode(bin);
78
+ });
62
79
  prodArgsC = cached(() => this.fields.prodArgs?.modCount, () => {
63
80
  const bin = this.fields.prodArgs?.value;
64
81
  if (bin === undefined)
65
82
  return undefined;
66
83
  return cachedDeserialize(bin);
67
84
  });
85
+ currentPrerunArgsC = cached(() => this.fields.currentPrerunArgs?.modCount, () => {
86
+ const bin = this.fields.currentPrerunArgs?.value;
87
+ if (bin === undefined)
88
+ return undefined;
89
+ return cachedDeserialize(bin);
90
+ });
68
91
  get currentArgs() {
69
92
  return this.currentArgsC();
70
93
  }
94
+ get blockStorage() {
95
+ try {
96
+ return this.blockStorageC();
97
+ }
98
+ catch (e) {
99
+ console.error('Error getting blockStorage:', e);
100
+ return undefined;
101
+ }
102
+ }
103
+ get blockStorageJson() {
104
+ return this.blockStorageJ();
105
+ }
106
+ get currentPrerunArgs() {
107
+ return this.currentPrerunArgsC();
108
+ }
71
109
  get stagingRendered() {
72
110
  return this.fields.stagingCtx !== undefined;
73
111
  }
@@ -79,12 +117,16 @@ class BlockInfo {
79
117
  }
80
118
  productionStaleC = cached(() => `${this.fields.currentArgs.modCount}_${this.fields.prodArgs?.modCount}`, () => this.fields.prodArgs === undefined
81
119
  || Buffer.compare(this.fields.currentArgs.value, this.fields.prodArgs.value) !== 0);
82
- // get productionStale(): boolean {
83
- // return this.productionRendered && this.productionStaleC() && ;
84
- // }
85
120
  get requireProductionRendering() {
86
121
  return !this.productionRendered || this.productionStaleC() || this.productionHasErrors;
87
122
  }
123
+ /** Returns true if staging should be re-rendered (stagingCtx is not set) */
124
+ get requireStagingRendering() {
125
+ // No staging needed if currentPrerunArgs is undefined (args derivation failed)
126
+ if (this.fields.currentPrerunArgs === undefined)
127
+ return false;
128
+ return !this.stagingRendered;
129
+ }
88
130
  get prodArgs() {
89
131
  return this.prodArgsC();
90
132
  }
@@ -159,7 +201,6 @@ class ProjectMutator {
159
201
  || this.renderingStateChanged);
160
202
  }
161
203
  get structure() {
162
- // clone
163
204
  return JSON.parse(JSON.stringify(this.struct));
164
205
  }
165
206
  //
@@ -184,9 +225,13 @@ class ProjectMutator {
184
225
  args = bInfo.prodArgs;
185
226
  }
186
227
  else {
187
- argsField = notEmpty(bInfo.fields.currentArgs);
228
+ argsField = bInfo.fields.currentArgs;
188
229
  args = bInfo.currentArgs;
189
230
  }
231
+ // Can't compute enrichment targets without args
232
+ if (argsField === undefined) {
233
+ return { args, enrichmentTargets: undefined };
234
+ }
190
235
  const blockPackField = notEmpty(bInfo.fields.blockPack);
191
236
  if (isResourceId(argsField.ref) && isResourceId(blockPackField.ref))
192
237
  return {
@@ -219,6 +264,8 @@ class ProjectMutator {
219
264
  return info;
220
265
  }
221
266
  createJsonFieldValueByContent(content) {
267
+ if (content === undefined)
268
+ throw new Error('content is undefined');
222
269
  const value = Buffer.from(content);
223
270
  const ref = this.tx.createValue(Pl.JsonObject, value);
224
271
  return { ref, value, status: 'Ready' };
@@ -319,6 +366,62 @@ class ProjectMutator {
319
366
  return this.deleteBlockFields(blockId, 'prodOutput', 'prodCtx', 'prodUiCtx', 'prodArgs', 'prodOutputPrevious', 'prodCtxPrevious', 'prodUiCtxPrevious');
320
367
  }
321
368
  }
369
+ /**
370
+ * Gets current block state and merges with partial updates.
371
+ * Used by legacy v1/v2 methods like setBlockArgs and setUiState.
372
+ *
373
+ * @param blockId The block to get state for
374
+ * @param partialUpdate Partial state to merge (e.g. { args } or { uiState })
375
+ * @returns Merged state in unified format { args, uiState }
376
+ */
377
+ mergeBlockState(blockId, partialUpdate) {
378
+ const info = this.getBlockInfo(blockId);
379
+ const currentState = info.blockStorage;
380
+ return { ...currentState, ...partialUpdate };
381
+ }
382
+ /**
383
+ * Sets raw block storage content directly (for testing purposes).
384
+ * This bypasses all normalization and VM transformations.
385
+ *
386
+ * @param blockId The block to set storage for
387
+ * @param rawStorageJson Raw storage as JSON string
388
+ */
389
+ setBlockStorageRaw(blockId, rawStorageJson) {
390
+ this.setBlockFieldObj(blockId, 'blockStorage', this.createJsonFieldValueByContent(rawStorageJson));
391
+ this.blocksWithChangedInputs.add(blockId);
392
+ this.updateLastModified();
393
+ }
394
+ /**
395
+ * Resets a v2+ block to its initial storage state.
396
+ * Gets initial storage from VM and derives args from it.
397
+ *
398
+ * For v1 blocks, use setStates() instead.
399
+ *
400
+ * @param blockId The block to reset
401
+ */
402
+ resetToInitialStorage(blockId) {
403
+ const info = this.getBlockInfo(blockId);
404
+ const blockConfig = info.config;
405
+ if (blockConfig.modelAPIVersion !== 2) {
406
+ throw new Error('resetToInitialStorage is only supported for model API version 2');
407
+ }
408
+ // Get initial storage from VM
409
+ const initialStorageJson = this.projectHelper.getInitialStorageInVM(blockConfig);
410
+ this.setBlockStorageRaw(blockId, initialStorageJson);
411
+ // Derive args from storage - set or clear currentArgs based on derivation result
412
+ const deriveArgsResult = this.projectHelper.deriveArgsFromStorage(blockConfig, initialStorageJson);
413
+ if (!deriveArgsResult.error) {
414
+ this.setBlockFieldObj(blockId, 'currentArgs', this.createJsonFieldValue(deriveArgsResult.value));
415
+ // Derive prerunArgs from storage
416
+ const prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(blockConfig, initialStorageJson);
417
+ if (prerunArgs !== undefined) {
418
+ this.setBlockFieldObj(blockId, 'currentPrerunArgs', this.createJsonFieldValue(prerunArgs));
419
+ }
420
+ }
421
+ else {
422
+ this.deleteBlockFields(blockId, 'currentArgs');
423
+ }
424
+ }
322
425
  /** Optimally sets inputs for multiple blocks in one go */
323
426
  setStates(requests) {
324
427
  const changedArgs = [];
@@ -326,32 +429,76 @@ class ProjectMutator {
326
429
  for (const req of requests) {
327
430
  const info = this.getBlockInfo(req.blockId);
328
431
  let blockChanged = false;
329
- for (const stateKey of ['args', 'uiState']) {
330
- if (!(stateKey in req))
331
- continue;
332
- const statePart = req[stateKey];
333
- if (statePart === undefined || statePart === null)
334
- throw new Error(`Can't set ${stateKey} to null or undefined, please omit the key if you don't want to change it`);
335
- const fieldName = stateKey === 'args' ? 'currentArgs' : 'uiState';
336
- let data;
337
- let gzipped = false;
338
- if (stateKey === 'args') {
339
- // don't gzip args, workflow code can't uncompress gzip yet
340
- data = canonicalJsonBytes(statePart);
432
+ const blockConfig = info.config;
433
+ // modelAPIVersion === 2 means BlockModelV3 with .args() lambda for deriving args
434
+ if (req.modelAPIVersion !== blockConfig.modelAPIVersion) {
435
+ throw new Error(`Model API version mismatch for block ${req.blockId}: ${req.modelAPIVersion} !== ${blockConfig.modelAPIVersion}`);
436
+ }
437
+ // Derive args from storage using the block's config.args() callback
438
+ let args;
439
+ let prerunArgs;
440
+ if (req.modelAPIVersion === 2) {
441
+ const currentStorageJson = info.blockStorageJson;
442
+ if (currentStorageJson === undefined) {
443
+ throw new Error(`Block ${req.blockId} has no blockStorage - this should not happen`);
444
+ }
445
+ // Apply the state update to storage
446
+ const updatedStorageJson = this.projectHelper.applyStorageUpdateInVM(blockConfig, currentStorageJson, req.payload);
447
+ this.setBlockFieldObj(req.blockId, 'blockStorage', this.createJsonFieldValueByContent(updatedStorageJson));
448
+ // Derive args directly from storage (VM extracts data internally)
449
+ const derivedArgsResult = this.projectHelper.deriveArgsFromStorage(blockConfig, updatedStorageJson);
450
+ if (derivedArgsResult.error) {
451
+ args = undefined;
452
+ prerunArgs = undefined;
341
453
  }
342
454
  else {
343
- const { data: binary, isGzipped } = canonicalJsonGzBytes(statePart);
344
- data = binary;
345
- gzipped = isGzipped;
455
+ args = derivedArgsResult.value;
456
+ // Derive prerunArgs from storage, or fall back to args
457
+ prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(blockConfig, updatedStorageJson);
346
458
  }
347
- if (Buffer.compare(info.fields[fieldName].value, data) === 0)
348
- continue;
349
- // console.log('setting', fieldName, gzipped, data.length);
350
- const statePartRef = this.tx.createValue(gzipped ? Pl.JsonGzObject : Pl.JsonObject, data);
351
- this.setBlockField(req.blockId, fieldName, statePartRef, 'Ready', data);
352
- blockChanged = true;
353
- if (stateKey === 'args')
354
- changedArgs.push(req.blockId);
459
+ }
460
+ else {
461
+ this.setBlockFieldObj(req.blockId, 'blockStorage', this.createJsonFieldValue(req.state));
462
+ if (req.state !== null && typeof req.state === 'object' && 'args' in req.state) {
463
+ args = req.state.args;
464
+ }
465
+ else {
466
+ args = req.state;
467
+ }
468
+ // For the legacy blocks, prerunArgs = args (same as production args)
469
+ prerunArgs = args;
470
+ }
471
+ // Set or clear currentArgs based on derivation result
472
+ if (args !== undefined) {
473
+ const currentArgsData = canonicalJsonBytes(args);
474
+ const argsPartRef = this.tx.createValue(Pl.JsonObject, currentArgsData);
475
+ this.setBlockField(req.blockId, 'currentArgs', argsPartRef, 'Ready', currentArgsData);
476
+ }
477
+ else {
478
+ this.deleteBlockFields(req.blockId, 'currentArgs');
479
+ }
480
+ // Set currentPrerunArgs field and check if it actually changed
481
+ let prerunArgsChanged = false;
482
+ if (prerunArgs !== undefined) {
483
+ const prerunArgsData = canonicalJsonBytes(prerunArgs);
484
+ const oldPrerunArgsData = info.fields.currentPrerunArgs?.value;
485
+ // Check if prerunArgs actually changed
486
+ if (oldPrerunArgsData === undefined || Buffer.compare(oldPrerunArgsData, prerunArgsData) !== 0) {
487
+ prerunArgsChanged = true;
488
+ }
489
+ const prerunArgsRef = this.tx.createValue(Pl.JsonObject, prerunArgsData);
490
+ this.setBlockField(req.blockId, 'currentPrerunArgs', prerunArgsRef, 'Ready', prerunArgsData);
491
+ }
492
+ else {
493
+ // prerunArgs is undefined - check if we previously had one
494
+ if (info.fields.currentPrerunArgs !== undefined) {
495
+ prerunArgsChanged = true;
496
+ }
497
+ }
498
+ blockChanged = true;
499
+ // Only add to changedArgs if prerunArgs changed - this controls staging reset
500
+ if (prerunArgsChanged) {
501
+ changedArgs.push(req.blockId);
355
502
  }
356
503
  if (blockChanged) {
357
504
  // will be assigned our author marker
@@ -393,15 +540,25 @@ class ProjectMutator {
393
540
  exportCtx(ctx) {
394
541
  return exportContext(this.tx, Pl.unwrapHolder(this.tx, this.ctxExportTplHolder), ctx);
395
542
  }
543
+ /**
544
+ * Renders staging for a block using currentPrerunArgs.
545
+ * If currentPrerunArgs is not set (prerunArgs returned undefined), skips staging for this block.
546
+ */
396
547
  renderStagingFor(blockId) {
397
548
  this.resetStaging(blockId);
398
549
  const info = this.getBlockInfo(blockId);
550
+ // If currentPrerunArgs is not set (prerunArgs returned undefined), skip staging for this block
551
+ const prerunArgsRef = info.fields.currentPrerunArgs?.ref;
552
+ if (prerunArgsRef === undefined) {
553
+ return;
554
+ }
399
555
  const ctx = this.createStagingCtx(this.getStagingGraph().nodes.get(blockId).upstream);
400
556
  if (this.getBlock(blockId).renderingMode !== 'Heavy')
401
557
  throw new Error('not supported yet');
402
558
  const tpl = info.getTemplate(this.tx);
559
+ // Use currentPrerunArgs for staging rendering
403
560
  const results = createRenderHeavyBlock(this.tx, tpl, {
404
- args: info.fields.currentArgs.ref,
561
+ args: prerunArgsRef,
405
562
  blockId: this.tx.createValue(Pl.JsonString, JSON.stringify(blockId)),
406
563
  isProduction: this.tx.createValue(Pl.JsonBool, JSON.stringify(false)),
407
564
  context: ctx,
@@ -418,6 +575,10 @@ class ProjectMutator {
418
575
  renderProductionFor(blockId) {
419
576
  this.resetProduction(blockId);
420
577
  const info = this.getBlockInfo(blockId);
578
+ // Can't render production if currentArgs is not set
579
+ if (info.fields.currentArgs === undefined) {
580
+ throw new Error(`Can't render production for block ${blockId}: currentArgs not set`);
581
+ }
421
582
  const ctx = this.createProdCtx(this.getPendingProductionGraph().nodes.get(blockId).upstream);
422
583
  if (this.getBlock(blockId).renderingMode === 'Light')
423
584
  throw new Error('Can\'t render production for light block.');
@@ -448,10 +609,47 @@ class ProjectMutator {
448
609
  this.setBlockField(blockId, 'blockPack', Pl.wrapInHolder(this.tx, bp), 'NotReady');
449
610
  // settings
450
611
  this.setBlockFieldObj(blockId, 'blockSettings', this.createJsonFieldValue(InitialBlockSettings));
451
- // args
452
- this.setBlockFieldObj(blockId, 'currentArgs', this.createJsonFieldValueByContent(spec.args));
453
- // uiState
454
- this.setBlockFieldObj(blockId, 'uiState', this.createJsonFieldValueByContent(spec.uiState ?? '{}'));
612
+ const blockConfig = info.config;
613
+ let args;
614
+ let prerunArgs;
615
+ let storageToWrite;
616
+ if (spec.storageMode === 'fromModel') {
617
+ // Model API v2+: get initial storage and derive args from it
618
+ storageToWrite = this.projectHelper.getInitialStorageInVM(blockConfig);
619
+ // Derive args directly from storage (VM extracts data internally)
620
+ const deriveArgsResult = this.projectHelper.deriveArgsFromStorage(blockConfig, storageToWrite);
621
+ if (deriveArgsResult.error) {
622
+ args = undefined;
623
+ prerunArgs = undefined;
624
+ }
625
+ else {
626
+ args = deriveArgsResult.value;
627
+ prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(blockConfig, storageToWrite);
628
+ }
629
+ }
630
+ else if (spec.storageMode === 'legacy') {
631
+ // Model API v1: use legacyState from spec
632
+ const parsedState = JSON.parse(spec.legacyState);
633
+ args = parsedState.args;
634
+ if (args === undefined) {
635
+ throw new Error('args is undefined in legacyState');
636
+ }
637
+ prerunArgs = args;
638
+ storageToWrite = spec.legacyState;
639
+ }
640
+ else {
641
+ throw new Error(`Unknown storageMode: ${spec.storageMode}`);
642
+ }
643
+ // currentArgs
644
+ if (args !== undefined) {
645
+ this.setBlockFieldObj(blockId, 'currentArgs', this.createJsonFieldValue(args));
646
+ }
647
+ // currentPrerunArgs
648
+ if (prerunArgs !== undefined) {
649
+ this.setBlockFieldObj(blockId, 'currentPrerunArgs', this.createJsonFieldValue(prerunArgs));
650
+ }
651
+ // blockStorage
652
+ this.setBlockFieldObj(blockId, 'blockStorage', this.createJsonFieldValueByContent(storageToWrite));
455
653
  // checking structure
456
654
  info.check();
457
655
  }
@@ -627,14 +825,53 @@ class ProjectMutator {
627
825
  //
628
826
  // Block-pack migration
629
827
  //
630
- migrateBlockPack(blockId, spec, newArgsAndUiState) {
828
+ migrateBlockPack(blockId, spec, newClearState) {
631
829
  const info = this.getBlockInfo(blockId);
830
+ const newConfig = extractConfig(spec.config);
632
831
  this.setBlockField(blockId, 'blockPack', Pl.wrapInHolder(this.tx, createBlockPack(this.tx, spec)), 'NotReady');
633
- if (newArgsAndUiState !== undefined) {
634
- // this will also reset all downstream stagings
635
- this.setStates([{ blockId, args: newArgsAndUiState.args, uiState: newArgsAndUiState.uiState }]);
832
+ if (newClearState !== undefined) {
833
+ // State is being reset - no migration needed
834
+ const supportsStorageFromVM = newConfig.modelAPIVersion === 2;
835
+ if (supportsStorageFromVM) {
836
+ // V2+: Get initial storage directly from VM and derive args from it
837
+ const initialStorageJson = this.projectHelper.getInitialStorageInVM(newConfig);
838
+ this.setBlockStorageRaw(blockId, initialStorageJson);
839
+ // Derive args from storage - only set currentArgs if derivation succeeds
840
+ const deriveArgsResult = this.projectHelper.deriveArgsFromStorage(newConfig, initialStorageJson);
841
+ if (!deriveArgsResult.error) {
842
+ this.setBlockFieldObj(blockId, 'currentArgs', this.createJsonFieldValue(deriveArgsResult.value));
843
+ // Derive prerunArgs from storage
844
+ const prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(newConfig, initialStorageJson);
845
+ if (prerunArgs !== undefined) {
846
+ this.setBlockFieldObj(blockId, 'currentPrerunArgs', this.createJsonFieldValue(prerunArgs));
847
+ }
848
+ }
849
+ this.blocksWithChangedInputs.add(blockId);
850
+ this.updateLastModified();
851
+ }
852
+ else {
853
+ // V1: Use setStates with legacy state format
854
+ this.setStates([{ modelAPIVersion: 1, blockId, state: newClearState.state }]);
855
+ }
636
856
  }
637
857
  else {
858
+ // State is being preserved - run migrations if needed via VM
859
+ // Only Model API v2 blocks support migrations
860
+ const supportsStateMigrations = newConfig.modelAPIVersion === 2;
861
+ if (supportsStateMigrations) {
862
+ const currentStorageJson = info.blockStorageJson;
863
+ const migrationResult = this.projectHelper.migrateStorageInVM(newConfig, currentStorageJson);
864
+ if (migrationResult.error !== undefined) {
865
+ console.error(`[migrateBlockPack] Block ${blockId} migration error: ${migrationResult.error}`);
866
+ }
867
+ else {
868
+ console.log(`[migrateBlockPack] Block ${blockId}: ${migrationResult.info}`);
869
+ if (migrationResult.warn) {
870
+ console.warn(`[migrateBlockPack] Block ${blockId} migration warning: ${migrationResult.warn}`);
871
+ }
872
+ this.setBlockStorageRaw(blockId, migrationResult.newStorageJson);
873
+ }
874
+ }
638
875
  // resetting staging outputs for all downstream blocks
639
876
  this.getStagingGraph().traverse('downstream', [blockId], ({ id }) => this.resetStaging(id));
640
877
  }
@@ -735,13 +972,16 @@ class ProjectMutator {
735
972
  const stagingGraph = this.getStagingGraph();
736
973
  stagingGraph.nodes.forEach((node) => {
737
974
  const info = this.getBlockInfo(node.id);
738
- let lag = info.stagingRendered ? 0 : 1;
975
+ // Use requireStagingRendering to check both: staging exists AND prerunArgs hasn't changed
976
+ const requiresRendering = info.requireStagingRendering;
977
+ let lag = requiresRendering ? 1 : 0;
739
978
  node.upstream.forEach((upstream) => {
740
979
  const upstreamLag = lags.get(upstream);
741
980
  if (upstreamLag === 0)
742
981
  return;
743
982
  lag = Math.max(upstreamLag + 1, lag);
744
983
  });
984
+ if (!requiresRendering && info.stagingRendered) ;
745
985
  cb(node.id, lag);
746
986
  lags.set(node.id, lag);
747
987
  });
@@ -758,6 +998,7 @@ class ProjectMutator {
758
998
  // meaning staging already rendered
759
999
  return;
760
1000
  if (lagThreshold === undefined || lag <= lagThreshold) {
1001
+ // console.log(`[refreshStagings] RENDER staging for ${blockId} (lag=${lag})`);
761
1002
  this.renderStagingFor(blockId);
762
1003
  rendered++;
763
1004
  }