@milaboratories/pl-middle-layer 1.46.34 → 1.47.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 (55) hide show
  1. package/dist/index.cjs +0 -58
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.js +0 -2
  4. package/dist/index.js.map +1 -1
  5. package/dist/middle_layer/frontend_path.cjs +0 -1
  6. package/dist/middle_layer/frontend_path.cjs.map +1 -1
  7. package/dist/middle_layer/frontend_path.js +0 -1
  8. package/dist/middle_layer/frontend_path.js.map +1 -1
  9. package/dist/middle_layer/middle_layer.cjs +5 -1
  10. package/dist/middle_layer/middle_layer.cjs.map +1 -1
  11. package/dist/middle_layer/middle_layer.d.ts +3 -1
  12. package/dist/middle_layer/middle_layer.d.ts.map +1 -1
  13. package/dist/middle_layer/middle_layer.js +5 -1
  14. package/dist/middle_layer/middle_layer.js.map +1 -1
  15. package/dist/middle_layer/project.cjs +8 -6
  16. package/dist/middle_layer/project.cjs.map +1 -1
  17. package/dist/middle_layer/project.d.ts.map +1 -1
  18. package/dist/middle_layer/project.js +9 -7
  19. package/dist/middle_layer/project.js.map +1 -1
  20. package/dist/middle_layer/project_overview.cjs +2 -2
  21. package/dist/middle_layer/project_overview.cjs.map +1 -1
  22. package/dist/middle_layer/project_overview.js +3 -3
  23. package/dist/middle_layer/project_overview.js.map +1 -1
  24. package/dist/model/index.d.ts +0 -1
  25. package/dist/model/index.d.ts.map +1 -1
  26. package/dist/model/project_helper.cjs +20 -32
  27. package/dist/model/project_helper.cjs.map +1 -1
  28. package/dist/model/project_helper.d.ts +3 -4
  29. package/dist/model/project_helper.d.ts.map +1 -1
  30. package/dist/model/project_helper.js +21 -33
  31. package/dist/model/project_helper.js.map +1 -1
  32. package/dist/mutator/block-pack/frontend.cjs +0 -1
  33. package/dist/mutator/block-pack/frontend.cjs.map +1 -1
  34. package/dist/mutator/block-pack/frontend.js +0 -1
  35. package/dist/mutator/block-pack/frontend.js.map +1 -1
  36. package/dist/mutator/project.cjs +28 -35
  37. package/dist/mutator/project.cjs.map +1 -1
  38. package/dist/mutator/project.d.ts.map +1 -1
  39. package/dist/mutator/project.js +29 -36
  40. package/dist/mutator/project.js.map +1 -1
  41. package/package.json +13 -13
  42. package/src/middle_layer/middle_layer.ts +7 -2
  43. package/src/middle_layer/project.ts +12 -7
  44. package/src/middle_layer/project_overview.ts +3 -3
  45. package/src/model/index.ts +0 -1
  46. package/src/model/project_helper.ts +33 -37
  47. package/src/mutator/project-v3.test.ts +9 -9
  48. package/src/mutator/project.ts +50 -74
  49. package/dist/model/block_storage_helper.cjs +0 -210
  50. package/dist/model/block_storage_helper.cjs.map +0 -1
  51. package/dist/model/block_storage_helper.d.ts +0 -98
  52. package/dist/model/block_storage_helper.d.ts.map +0 -1
  53. package/dist/model/block_storage_helper.js +0 -153
  54. package/dist/model/block_storage_helper.js.map +0 -1
  55. package/src/model/block_storage_helper.ts +0 -213
@@ -34,7 +34,7 @@ import type {
34
34
  } from "@milaboratories/pl-model-middle-layer";
35
35
  import { activeConfigs } from "./active_cfg";
36
36
  import { NavigationStates } from "./navigation_states";
37
- import { extractConfig } from "@platforma-sdk/model";
37
+ import { extractConfig, BLOCK_STORAGE_FACADE_VERSION } from "@platforma-sdk/model";
38
38
  import fs from "node:fs/promises";
39
39
  import canonicalize from "canonicalize";
40
40
  import type { ProjectOverviewLight } from "./project_overview_light";
@@ -187,9 +187,11 @@ export class Project {
187
187
  const blockCfgContainer = await this.env.bpPreparer.getBlockConfigContainer(blockPackSpec);
188
188
  const blockCfg = extractConfig(blockCfgContainer); // full content of this var should never be persisted
189
189
 
190
+ this.env.runtimeCapabilities.throwIfIncompatible(blockCfg.featureFlags);
191
+
190
192
  // Build NewBlockSpec based on model API version
191
193
  const newBlockSpec =
192
- blockCfg.modelAPIVersion === 2
194
+ blockCfg.modelAPIVersion === BLOCK_STORAGE_FACADE_VERSION
193
195
  ? { storageMode: "fromModel" as const, blockPack: preparedBp }
194
196
  : {
195
197
  storageMode: "legacy" as const,
@@ -276,15 +278,18 @@ export class Project {
276
278
  const blockCfg = extractConfig(
277
279
  await this.env.bpPreparer.getBlockConfigContainer(blockPackSpec),
278
280
  );
281
+
282
+ this.env.runtimeCapabilities.throwIfIncompatible(blockCfg.featureFlags);
283
+
279
284
  // resetState signals to mutator to reset storage
280
285
  // For v2+ blocks: mutator gets initial storage directly via getInitialStorageInVM
281
286
  // For v1 blocks: we pass the legacy state format
282
287
  const resetState = resetArgs
283
288
  ? {
284
289
  state:
285
- blockCfg.modelAPIVersion === 2
286
- ? {}
287
- : { args: blockCfg.initialArgs, uiState: blockCfg.initialUiState },
290
+ blockCfg.modelAPIVersion === 1
291
+ ? { args: blockCfg.initialArgs, uiState: blockCfg.initialUiState }
292
+ : {},
288
293
  }
289
294
  : undefined;
290
295
  await withProjectAuthored(
@@ -459,7 +464,7 @@ export class Project {
459
464
  /**
460
465
  * Sets navigation state.
461
466
  * */
462
- // eslint-disable-next-line @typescript-eslint/require-await
467
+ //
463
468
  public async setNavigationState(blockId: string, state: NavigationState): Promise<void> {
464
469
  this.navigationStates.setState(blockId, state);
465
470
  }
@@ -545,7 +550,7 @@ export class Project {
545
550
  this.rid,
546
551
  author,
547
552
  (prj) => {
548
- if (config.modelAPIVersion === 2) {
553
+ if (config.modelAPIVersion === BLOCK_STORAGE_FACADE_VERSION) {
549
554
  // V2+: Reset to initial storage via VM
550
555
  prj.resetToInitialStorage(blockId);
551
556
  } else {
@@ -23,7 +23,7 @@ import type {
23
23
  } from "@milaboratories/pl-model-middle-layer";
24
24
  import { constructBlockContext, constructBlockContextArgsOnly } from "./block_ctx";
25
25
  import { ifNotUndef } from "../cfg_render/util";
26
- import { type BlockSection } from "@platforma-sdk/model";
26
+ import { type BlockSection, BLOCK_STORAGE_FACADE_VERSION } from "@platforma-sdk/model";
27
27
  import { extractCodeWithInfo, wrapCallback } from "@platforma-sdk/model";
28
28
  import { computableFromCfgOrRF } from "./render";
29
29
  import type { NavigationStates } from "./navigation_states";
@@ -231,7 +231,7 @@ export function projectOverview(
231
231
  // inputsValid: for modelAPIVersion 2, it's true if currentArgs exists (args derivation succeeded)
232
232
  // For older blocks, use the inputsValid callback from config
233
233
  inputsValid:
234
- cfg.modelAPIVersion === 2
234
+ cfg.modelAPIVersion === BLOCK_STORAGE_FACADE_VERSION
235
235
  ? info.currentArguments !== undefined
236
236
  : cfg.inputsValid
237
237
  ? (computableFromCfgOrRF(
@@ -265,7 +265,7 @@ export function projectOverview(
265
265
 
266
266
  // Get block storage debug view by calling VM function (only for Model API v2 blocks)
267
267
  const storageDebugView = ifNotUndef(bp, ({ cfg }) => {
268
- if (cfg.modelAPIVersion !== 2) {
268
+ if (cfg.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {
269
269
  return undefined;
270
270
  }
271
271
  const storageNode = prj.traverse({
@@ -1,3 +1,2 @@
1
1
  export * from "./block_pack_spec";
2
- export * from "./block_storage_helper";
3
2
  export { type ProjectListEntry, type ProjectField } from "./project_model";
@@ -1,12 +1,17 @@
1
1
  import type {
2
2
  ResultOrError,
3
3
  BlockConfig,
4
+ BlockStorage,
4
5
  PlRef,
5
- ConfigRenderLambda,
6
6
  StorageDebugView,
7
7
  } from "@platforma-sdk/model";
8
8
  import type { StringifiedJson } from "@milaboratories/pl-model-common";
9
- import { extractCodeWithInfo, ensureError } from "@platforma-sdk/model";
9
+ import {
10
+ extractCodeWithInfo,
11
+ ensureError,
12
+ BlockStorageFacadeCallbacks,
13
+ BLOCK_STORAGE_FACADE_VERSION,
14
+ } from "@platforma-sdk/model";
10
15
  import { LRUCache } from "lru-cache";
11
16
  import type { QuickJSWASMModule } from "quickjs-emscripten";
12
17
  import { executeSingleLambda } from "../js_render";
@@ -26,36 +31,11 @@ type EnrichmentTargetsValue = {
26
31
  * Returned by migrateStorageInVM().
27
32
  *
28
33
  * - Error result: { error: string } - serious failure (no context, etc.)
29
- * - Success result: { newStorageJson: string, info: string, warn?: string } - migration succeeded or reset to initial
34
+ * - Success result: { newStorageJson: StringifiedJson<BlockStorage>, info: string } - migration succeeded
30
35
  */
31
36
  export type MigrationResult =
32
37
  | { error: string }
33
- | { error?: undefined; newStorageJson: string; info: string; warn?: string };
34
-
35
- // Internal lambda handles for storage operations (registered by SDK's block_storage_vm.ts)
36
- // All callbacks are prefixed with `__pl_` to indicate internal SDK use
37
- const STORAGE_APPLY_UPDATE_HANDLE: ConfigRenderLambda = {
38
- __renderLambda: true,
39
- handle: "__pl_storage_applyUpdate",
40
- };
41
- const STORAGE_DEBUG_VIEW_HANDLE: ConfigRenderLambda = {
42
- __renderLambda: true,
43
- handle: "__pl_storage_debugView",
44
- };
45
- const STORAGE_MIGRATE_HANDLE: ConfigRenderLambda = {
46
- __renderLambda: true,
47
- handle: "__pl_storage_migrate",
48
- };
49
- const ARGS_DERIVE_HANDLE: ConfigRenderLambda = { __renderLambda: true, handle: "__pl_args_derive" };
50
- const PRERUN_ARGS_DERIVE_HANDLE: ConfigRenderLambda = {
51
- __renderLambda: true,
52
- handle: "__pl_prerunArgs_derive",
53
- };
54
- // Registered by DataModel.registerCallbacks()
55
- const INITIAL_STORAGE_HANDLE: ConfigRenderLambda = {
56
- __renderLambda: true,
57
- handle: "__pl_storage_initial",
58
- };
38
+ | { error?: undefined; newStorageJson: StringifiedJson<BlockStorage>; info: string };
59
39
 
60
40
  /**
61
41
  * Result of args derivation from storage.
@@ -96,7 +76,7 @@ export class ProjectHelper {
96
76
  blockConfig: BlockConfig,
97
77
  storageJson: string,
98
78
  ): ResultOrError<unknown> {
99
- if (blockConfig.modelAPIVersion !== 2) {
79
+ if (blockConfig.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {
100
80
  return {
101
81
  error: new Error("deriveArgsFromStorage is only supported for model API version 2"),
102
82
  };
@@ -105,7 +85,7 @@ export class ProjectHelper {
105
85
  try {
106
86
  const result = executeSingleLambda(
107
87
  this.quickJs,
108
- ARGS_DERIVE_HANDLE,
88
+ blockConfig.blockLifecycleCallbacks[BlockStorageFacadeCallbacks.ArgsDerive],
109
89
  extractCodeWithInfo(blockConfig),
110
90
  storageJson,
111
91
  ) as ArgsDeriveResult;
@@ -128,14 +108,14 @@ export class ProjectHelper {
128
108
  * @returns The derived prerunArgs, or undefined if derivation fails
129
109
  */
130
110
  public derivePrerunArgsFromStorage(blockConfig: BlockConfig, storageJson: string): unknown {
131
- if (blockConfig.modelAPIVersion !== 2) {
111
+ if (blockConfig.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {
132
112
  throw new Error("derivePrerunArgsFromStorage is only supported for model API version 2");
133
113
  }
134
114
 
135
115
  try {
136
116
  const result = executeSingleLambda(
137
117
  this.quickJs,
138
- PRERUN_ARGS_DERIVE_HANDLE,
118
+ blockConfig.blockLifecycleCallbacks[BlockStorageFacadeCallbacks.PrerunArgsDerive],
139
119
  extractCodeWithInfo(blockConfig),
140
120
  storageJson,
141
121
  ) as ArgsDeriveResult;
@@ -190,10 +170,14 @@ export class ProjectHelper {
190
170
  * @throws Error if storage creation fails
191
171
  */
192
172
  public getInitialStorageInVM(blockConfig: BlockConfig): string {
173
+ if (blockConfig.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {
174
+ throw new Error("getInitialStorageInVM is only supported for model API version 2");
175
+ }
176
+
193
177
  try {
194
178
  const result = executeSingleLambda(
195
179
  this.quickJs,
196
- INITIAL_STORAGE_HANDLE,
180
+ blockConfig.blockLifecycleCallbacks[BlockStorageFacadeCallbacks.StorageInitial],
197
181
  extractCodeWithInfo(blockConfig),
198
182
  ) as string;
199
183
  return result;
@@ -221,10 +205,14 @@ export class ProjectHelper {
221
205
  currentStorageJson: string,
222
206
  payload: { operation: string; value: unknown },
223
207
  ): string {
208
+ if (blockConfig.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {
209
+ throw new Error("applyStorageUpdateInVM is only supported for model API version 2");
210
+ }
211
+
224
212
  try {
225
213
  const result = executeSingleLambda(
226
214
  this.quickJs,
227
- STORAGE_APPLY_UPDATE_HANDLE,
215
+ blockConfig.blockLifecycleCallbacks[BlockStorageFacadeCallbacks.StorageApplyUpdate],
228
216
  extractCodeWithInfo(blockConfig),
229
217
  currentStorageJson,
230
218
  payload,
@@ -248,10 +236,14 @@ export class ProjectHelper {
248
236
  blockConfig: BlockConfig,
249
237
  rawStorageJson: string | undefined,
250
238
  ): StringifiedJson<StorageDebugView> | undefined {
239
+ if (blockConfig.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {
240
+ throw new Error("getStorageDebugViewInVM is only supported for model API version 2");
241
+ }
242
+
251
243
  try {
252
244
  const result = executeSingleLambda(
253
245
  this.quickJs,
254
- STORAGE_DEBUG_VIEW_HANDLE,
246
+ blockConfig.blockLifecycleCallbacks[BlockStorageFacadeCallbacks.StorageDebugView],
255
247
  extractCodeWithInfo(blockConfig),
256
248
  rawStorageJson,
257
249
  ) as StringifiedJson<StorageDebugView>;
@@ -285,10 +277,14 @@ export class ProjectHelper {
285
277
  blockConfig: BlockConfig,
286
278
  currentStorageJson: string | undefined,
287
279
  ): MigrationResult {
280
+ if (blockConfig.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {
281
+ return { error: "migrateStorageInVM is only supported for model API version 2" };
282
+ }
283
+
288
284
  try {
289
285
  const result = executeSingleLambda(
290
286
  this.quickJs,
291
- STORAGE_MIGRATE_HANDLE,
287
+ blockConfig.blockLifecycleCallbacks[BlockStorageFacadeCallbacks.StorageMigrate],
292
288
  extractCodeWithInfo(blockConfig),
293
289
  currentStorageJson,
294
290
  ) as MigrationResult;
@@ -50,7 +50,7 @@ test("v3 blocks: basic test with unified state", async () => {
50
50
  modelAPIVersion: 2,
51
51
  blockId: "enter1",
52
52
  payload: {
53
- operation: "update-data",
53
+ operation: "update-block-data",
54
54
  value: { numbers: [1, 2, 3] },
55
55
  },
56
56
  },
@@ -82,7 +82,7 @@ test("v3 blocks: basic test with unified state", async () => {
82
82
  modelAPIVersion: 2,
83
83
  blockId: "enter2",
84
84
  payload: {
85
- operation: "update-data",
85
+ operation: "update-block-data",
86
86
  value: { numbers: [4, 5, 6] },
87
87
  },
88
88
  },
@@ -108,7 +108,7 @@ test("v3 blocks: basic test with unified state", async () => {
108
108
  modelAPIVersion: 2,
109
109
  blockId: "sum1",
110
110
  payload: {
111
- operation: "update-data",
111
+ operation: "update-block-data",
112
112
  value: { sources: [outputRef("enter1", "numbers"), outputRef("enter2", "numbers")] },
113
113
  },
114
114
  },
@@ -182,7 +182,7 @@ test("v3 blocks: prerunArgs skip test", async () => {
182
182
  modelAPIVersion: 2,
183
183
  blockId: "enter1",
184
184
  payload: {
185
- operation: "update-data",
185
+ operation: "update-block-data",
186
186
  value: { numbers: [3, 1, 2] },
187
187
  },
188
188
  },
@@ -215,7 +215,7 @@ test("v3 blocks: prerunArgs skip test", async () => {
215
215
  modelAPIVersion: 2,
216
216
  blockId: "enter1",
217
217
  payload: {
218
- operation: "update-data",
218
+ operation: "update-block-data",
219
219
  value: { numbers: [5, 1, 2] }, // Changed odd numbers only
220
220
  },
221
221
  },
@@ -245,7 +245,7 @@ test("v3 blocks: prerunArgs skip test", async () => {
245
245
  modelAPIVersion: 2,
246
246
  blockId: "enter1",
247
247
  payload: {
248
- operation: "update-data",
248
+ operation: "update-block-data",
249
249
  value: { numbers: [5, 1, 4] }, // Changed even number from 2 to 4
250
250
  },
251
251
  },
@@ -309,7 +309,7 @@ test("v3 blocks: migrateBlockPack preserves state and re-derives args and prerun
309
309
  {
310
310
  modelAPIVersion: 2,
311
311
  blockId: "enter1",
312
- payload: { operation: "update-data", value: { numbers: [4, 2, 6] } },
312
+ payload: { operation: "update-block-data", value: { numbers: [4, 2, 6] } },
313
313
  },
314
314
  ]);
315
315
  mut.save();
@@ -381,7 +381,7 @@ test("v3 blocks: migrateBlockPack with storage migration re-derives args and pre
381
381
  {
382
382
  modelAPIVersion: 2,
383
383
  blockId: "enter1",
384
- payload: { operation: "update-data", value: { numbers: [1] } },
384
+ payload: { operation: "update-block-data", value: { numbers: [1] } },
385
385
  },
386
386
  ]);
387
387
  mut.save();
@@ -461,7 +461,7 @@ test("v3 blocks: migrateBlockPack assigns author marker", async () => {
461
461
  {
462
462
  modelAPIVersion: 2,
463
463
  blockId: "enter1",
464
- payload: { operation: "update-data", value: { numbers: [1, 2, 3] } },
464
+ payload: { operation: "update-block-data", value: { numbers: [1, 2, 3] } },
465
465
  },
466
466
  ]);
467
467
  mut.save();
@@ -65,7 +65,12 @@ import {
65
65
  cachedDecode,
66
66
  } from "@milaboratories/ts-helpers";
67
67
  import type { ProjectHelper } from "../model/project_helper";
68
- import { extractConfig, UiError, type BlockConfig } from "@platforma-sdk/model";
68
+ import {
69
+ extractConfig,
70
+ UiError,
71
+ BLOCK_STORAGE_FACADE_VERSION,
72
+ type BlockConfig,
73
+ } from "@platforma-sdk/model";
69
74
  import { getDebugFlags } from "../debug";
70
75
  import type { BlockPackInfo } from "../model/block_pack";
71
76
 
@@ -622,7 +627,7 @@ export class ProjectMutator {
622
627
  const info = this.getBlockInfo(blockId);
623
628
  const blockConfig = info.config;
624
629
 
625
- if (blockConfig.modelAPIVersion !== 2) {
630
+ if (blockConfig.modelAPIVersion !== BLOCK_STORAGE_FACADE_VERSION) {
626
631
  throw new Error("resetToInitialStorage is only supported for model API version 2");
627
632
  }
628
633
 
@@ -675,7 +680,7 @@ export class ProjectMutator {
675
680
  let args: unknown;
676
681
  let prerunArgs: unknown;
677
682
 
678
- if (req.modelAPIVersion === 2) {
683
+ if (req.modelAPIVersion === BLOCK_STORAGE_FACADE_VERSION) {
679
684
  const currentStorageJson = info.blockStorageJson;
680
685
  if (currentStorageJson === undefined) {
681
686
  throw new Error(`Block ${req.blockId} has no blockStorage - this should not happen`);
@@ -1179,106 +1184,77 @@ export class ProjectMutator {
1179
1184
  const info = this.getBlockInfo(blockId);
1180
1185
  const newConfig = extractConfig(spec.config);
1181
1186
 
1182
- this.setBlockField(
1183
- blockId,
1184
- "blockPack",
1185
- Pl.wrapInHolder(this.tx, createBlockPack(this.tx, spec)),
1186
- "NotReady",
1187
- );
1187
+ const persistBlockPack = () => {
1188
+ this.setBlockField(
1189
+ blockId,
1190
+ "blockPack",
1191
+ Pl.wrapInHolder(this.tx, createBlockPack(this.tx, spec)),
1192
+ "NotReady",
1193
+ );
1194
+ };
1195
+
1196
+ const applyStorageAndDeriveArgs = (storageJson: string) => {
1197
+ persistBlockPack();
1198
+ this.setBlockStorageRaw(blockId, storageJson);
1199
+ const deriveArgsResult = this.projectHelper.deriveArgsFromStorage(newConfig, storageJson);
1200
+ if (!deriveArgsResult.error) {
1201
+ this.setBlockFieldObj(
1202
+ blockId,
1203
+ "currentArgs",
1204
+ this.createJsonFieldValue(deriveArgsResult.value),
1205
+ );
1206
+ const prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(newConfig, storageJson);
1207
+ if (prerunArgs !== undefined) {
1208
+ this.setBlockFieldObj(
1209
+ blockId,
1210
+ "currentPrerunArgs",
1211
+ this.createJsonFieldValue(prerunArgs),
1212
+ );
1213
+ }
1214
+ }
1215
+ };
1188
1216
 
1189
1217
  if (newClearState !== undefined) {
1190
1218
  // State is being reset - no migration needed
1191
- const supportsStorageFromVM = newConfig.modelAPIVersion === 2;
1219
+ const supportsStorageFromVM = newConfig.modelAPIVersion === BLOCK_STORAGE_FACADE_VERSION;
1192
1220
 
1193
1221
  if (supportsStorageFromVM) {
1194
1222
  // V2+: Get initial storage directly from VM and derive args from it
1195
1223
  const initialStorageJson = this.projectHelper.getInitialStorageInVM(newConfig);
1196
- this.setBlockStorageRaw(blockId, initialStorageJson);
1197
-
1198
- // Derive args from storage - only set currentArgs if derivation succeeds
1199
- const deriveArgsResult = this.projectHelper.deriveArgsFromStorage(
1200
- newConfig,
1201
- initialStorageJson,
1202
- );
1203
- if (!deriveArgsResult.error) {
1204
- this.setBlockFieldObj(
1205
- blockId,
1206
- "currentArgs",
1207
- this.createJsonFieldValue(deriveArgsResult.value),
1208
- );
1209
- // Derive prerunArgs from storage
1210
- const prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(
1211
- newConfig,
1212
- initialStorageJson,
1213
- );
1214
- if (prerunArgs !== undefined) {
1215
- this.setBlockFieldObj(
1216
- blockId,
1217
- "currentPrerunArgs",
1218
- this.createJsonFieldValue(prerunArgs),
1219
- );
1220
- }
1221
- }
1224
+ applyStorageAndDeriveArgs(initialStorageJson);
1222
1225
  this.blocksWithChangedInputs.add(blockId);
1223
1226
  this.updateLastModified();
1224
1227
  } else {
1225
1228
  // V1: Use setStates with legacy state format
1229
+ persistBlockPack();
1226
1230
  this.setStates([{ modelAPIVersion: 1, blockId, state: newClearState.state }]);
1227
1231
  }
1228
1232
  } else {
1229
1233
  // State is being preserved - run migrations if needed via VM
1230
1234
  // Only Model API v2 blocks support migrations
1231
- const supportsStateMigrations = newConfig.modelAPIVersion === 2;
1235
+ const supportsStateMigrations = newConfig.modelAPIVersion === BLOCK_STORAGE_FACADE_VERSION;
1232
1236
 
1233
1237
  if (supportsStateMigrations) {
1234
1238
  const currentStorageJson = info.blockStorageJson;
1235
1239
 
1240
+ // Attempt migration BEFORE persisting block pack — on failure,
1241
+ // block stays on old version (no inconsistent new-code/old-storage state)
1236
1242
  const migrationResult = this.projectHelper.migrateStorageInVM(
1237
1243
  newConfig,
1238
1244
  currentStorageJson,
1239
1245
  );
1240
1246
 
1241
1247
  if (migrationResult.error !== undefined) {
1242
- console.error(
1243
- `[migrateBlockPack] Block ${blockId} migration error: ${migrationResult.error}`,
1248
+ throw new Error(
1249
+ `[migrateBlockPack] Block ${blockId} migration failed: ${migrationResult.error}`,
1244
1250
  );
1245
- } else {
1246
- console.log(`[migrateBlockPack] Block ${blockId}: ${migrationResult.info}`);
1247
- if (migrationResult.warn) {
1248
- console.warn(
1249
- `[migrateBlockPack] Block ${blockId} migration warning: ${migrationResult.warn}`,
1250
- );
1251
- }
1252
- this.setBlockStorageRaw(blockId, migrationResult.newStorageJson);
1253
-
1254
- // Re-derive currentArgs from migrated storage (new block code + migrated data)
1255
- const deriveArgsResult = this.projectHelper.deriveArgsFromStorage(
1256
- newConfig,
1257
- migrationResult.newStorageJson,
1258
- );
1259
- if (!deriveArgsResult.error) {
1260
- this.setBlockFieldObj(
1261
- blockId,
1262
- "currentArgs",
1263
- this.createJsonFieldValue(deriveArgsResult.value),
1264
- );
1265
- }
1266
-
1267
- // Derive prerunArgs from the migrated storage so staging can re-render
1268
- const prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(
1269
- newConfig,
1270
- migrationResult.newStorageJson,
1271
- );
1272
- if (prerunArgs !== undefined) {
1273
- this.setBlockFieldObj(
1274
- blockId,
1275
- "currentPrerunArgs",
1276
- this.createJsonFieldValue(prerunArgs),
1277
- );
1278
- }
1279
1251
  }
1252
+
1253
+ console.log(`[migrateBlockPack] Block ${blockId}: ${migrationResult.info}`);
1254
+ applyStorageAndDeriveArgs(migrationResult.newStorageJson);
1280
1255
  } else {
1281
- // Legacy blocks (modelAPIVersion 1): prerunArgs = currentArgs
1256
+ // Legacy blocks (modelAPIVersion 1): persist block pack, set prerunArgs = currentArgs
1257
+ persistBlockPack();
1282
1258
  if (info.fields.currentArgs !== undefined) {
1283
1259
  this.setBlockFieldObj(blockId, "currentPrerunArgs", info.fields.currentArgs);
1284
1260
  }