@milaboratories/pl-middle-layer 1.46.28 → 1.46.30
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/js_render/computable_context.cjs +1 -1
- package/dist/js_render/computable_context.cjs.map +1 -1
- package/dist/js_render/computable_context.d.ts +2 -2
- package/dist/js_render/computable_context.d.ts.map +1 -1
- package/dist/js_render/computable_context.js +2 -2
- package/dist/js_render/computable_context.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.map +1 -1
- package/dist/middle_layer/middle_layer.js +1 -0
- package/dist/middle_layer/middle_layer.js.map +1 -1
- package/dist/mutator/migration.cjs +6 -0
- package/dist/mutator/migration.cjs.map +1 -1
- package/dist/mutator/migration.js +6 -0
- package/dist/mutator/migration.js.map +1 -1
- package/dist/mutator/project.cjs +17 -0
- package/dist/mutator/project.cjs.map +1 -1
- package/dist/mutator/project.d.ts.map +1 -1
- package/dist/mutator/project.js +17 -0
- package/dist/mutator/project.js.map +1 -1
- package/package.json +15 -15
- package/src/js_render/computable_context.ts +5 -3
- package/src/middle_layer/middle_layer.ts +1 -0
- package/src/mutator/migration.ts +7 -0
- package/src/mutator/project-v3.test.ts +204 -2
- package/src/mutator/project.ts +33 -0
|
@@ -282,6 +282,7 @@ export class MiddleLayer {
|
|
|
282
282
|
// add runtime capabilities of model here
|
|
283
283
|
runtimeCapabilities.addSupportedRequirement("requiresModelAPIVersion", 1);
|
|
284
284
|
runtimeCapabilities.addSupportedRequirement("requiresModelAPIVersion", 2);
|
|
285
|
+
runtimeCapabilities.addSupportedRequirement("requiresCreatePTableV2", true);
|
|
285
286
|
// runtime capabilities of the desktop are to be added by the desktop app / test framework
|
|
286
287
|
|
|
287
288
|
const env: MiddleLayerEnvironment = {
|
package/src/mutator/migration.ts
CHANGED
|
@@ -128,5 +128,12 @@ async function migrateV2ToV3(tx: PlTransaction, rid: ResourceId) {
|
|
|
128
128
|
const stateR = tx.createJsonGzValue(unifiedState);
|
|
129
129
|
const stateF = field(rid, blockStorageFieldName);
|
|
130
130
|
tx.createField(stateF, "Dynamic", stateR);
|
|
131
|
+
|
|
132
|
+
// Initialize currentPrerunArgs from currentArgs (for legacy blocks, prerunArgs = args)
|
|
133
|
+
if (currentArgsRid) {
|
|
134
|
+
const prerunArgsR = tx.createJsonGzValue(args);
|
|
135
|
+
const prerunArgsF = field(rid, projectFieldName(block.id, "currentPrerunArgs"));
|
|
136
|
+
tx.createField(prerunArgsF, "Dynamic", prerunArgsR);
|
|
137
|
+
}
|
|
131
138
|
}
|
|
132
139
|
}
|
|
@@ -3,10 +3,10 @@ import { getQuickJS } from "quickjs-emscripten";
|
|
|
3
3
|
import { expect, test } from "vitest";
|
|
4
4
|
import { outputRef } from "../model/args";
|
|
5
5
|
import { ProjectHelper } from "../model/project_helper";
|
|
6
|
-
import { projectFieldName } from "../model/project_model";
|
|
6
|
+
import { blockArgsAuthorKey, projectFieldName } from "../model/project_model";
|
|
7
7
|
import { TestBPPreparer } from "../test/block_packs";
|
|
8
8
|
import { createProject, ProjectMutator } from "./project";
|
|
9
|
-
import type { BlockPackSpec } from "@milaboratories/pl-model-middle-layer";
|
|
9
|
+
import type { AuthorMarker, BlockPackSpec } from "@milaboratories/pl-model-middle-layer";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
|
|
12
12
|
// V3 block specs - using dev-v2 type with local folders
|
|
@@ -283,3 +283,205 @@ test("v3 blocks: prerunArgs skip test", async () => {
|
|
|
283
283
|
});
|
|
284
284
|
});
|
|
285
285
|
});
|
|
286
|
+
|
|
287
|
+
test("v3 blocks: migrateBlockPack preserves state and re-derives args and prerunArgs", async () => {
|
|
288
|
+
const quickJs = await getQuickJS();
|
|
289
|
+
|
|
290
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
291
|
+
const prj = await pl.withWriteTx("CreatingProject", async (tx) => {
|
|
292
|
+
const prjRef = await createProject(tx);
|
|
293
|
+
tx.createField(field(tx.clientRoot, "prj"), "Dynamic", prjRef);
|
|
294
|
+
await tx.commit();
|
|
295
|
+
return await toGlobalResourceId(prjRef);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Add enter-numbers-v3 block and set data with even numbers
|
|
299
|
+
await pl.withWriteTx("AddBlock", async (tx) => {
|
|
300
|
+
const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
|
|
301
|
+
mut.addBlock(
|
|
302
|
+
{ id: "enter1", label: "Enter Numbers V3", renderingMode: "Heavy" },
|
|
303
|
+
{
|
|
304
|
+
storageMode: "fromModel",
|
|
305
|
+
blockPack: await TestBPPreparer.prepare(BPSpecEnterV3),
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
mut.setStates([
|
|
309
|
+
{
|
|
310
|
+
modelAPIVersion: 2,
|
|
311
|
+
blockId: "enter1",
|
|
312
|
+
payload: { operation: "update-data", value: { numbers: [4, 2, 6] } },
|
|
313
|
+
},
|
|
314
|
+
]);
|
|
315
|
+
mut.save();
|
|
316
|
+
await tx.commit();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Verify initial state
|
|
320
|
+
await poll(pl, async (tx) => {
|
|
321
|
+
const prjR = await tx.get(prj);
|
|
322
|
+
|
|
323
|
+
const currentArgs = await prjR.get(projectFieldName("enter1", "currentArgs"));
|
|
324
|
+
const argsData = JSON.parse(Buffer.from(currentArgs.data.data!).toString());
|
|
325
|
+
expect(argsData).toStrictEqual({ numbers: [2, 4, 6] });
|
|
326
|
+
|
|
327
|
+
const currentPrerunArgs = await prjR.get(projectFieldName("enter1", "currentPrerunArgs"));
|
|
328
|
+
const prerunData = JSON.parse(Buffer.from(currentPrerunArgs.data.data!).toString());
|
|
329
|
+
expect(prerunData).toStrictEqual({ evenNumbers: [2, 4, 6] });
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Call migrateBlockPack without newClearState (state-preserved path)
|
|
333
|
+
await pl.withWriteTx("MigrateBlockPack", async (tx) => {
|
|
334
|
+
const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
|
|
335
|
+
mut.migrateBlockPack("enter1", await TestBPPreparer.prepare(BPSpecEnterV3));
|
|
336
|
+
mut.save();
|
|
337
|
+
await tx.commit();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Verify: storage preserved, args and prerunArgs re-derived
|
|
341
|
+
await poll(pl, async (tx) => {
|
|
342
|
+
const prjR = await tx.get(prj);
|
|
343
|
+
|
|
344
|
+
const blockStorage = await prjR.get(projectFieldName("enter1", "blockStorage"));
|
|
345
|
+
const storageData = JSON.parse(Buffer.from(blockStorage.data.data!).toString());
|
|
346
|
+
expect(storageData.__data).toStrictEqual({ numbers: [4, 2, 6] });
|
|
347
|
+
|
|
348
|
+
const currentArgs = await prjR.get(projectFieldName("enter1", "currentArgs"));
|
|
349
|
+
const argsData = JSON.parse(Buffer.from(currentArgs.data.data!).toString());
|
|
350
|
+
expect(argsData).toStrictEqual({ numbers: [2, 4, 6] });
|
|
351
|
+
|
|
352
|
+
const currentPrerunArgs = await prjR.get(projectFieldName("enter1", "currentPrerunArgs"));
|
|
353
|
+
const prerunData = JSON.parse(Buffer.from(currentPrerunArgs.data.data!).toString());
|
|
354
|
+
expect(prerunData).toStrictEqual({ evenNumbers: [2, 4, 6] });
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("v3 blocks: migrateBlockPack with storage migration re-derives args and prerunArgs", async () => {
|
|
360
|
+
const quickJs = await getQuickJS();
|
|
361
|
+
|
|
362
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
363
|
+
const prj = await pl.withWriteTx("CreatingProject", async (tx) => {
|
|
364
|
+
const prjRef = await createProject(tx);
|
|
365
|
+
tx.createField(field(tx.clientRoot, "prj"), "Dynamic", prjRef);
|
|
366
|
+
await tx.commit();
|
|
367
|
+
return await toGlobalResourceId(prjRef);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Add enter-numbers-v3 block with initial data
|
|
371
|
+
await pl.withWriteTx("AddBlock", async (tx) => {
|
|
372
|
+
const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
|
|
373
|
+
mut.addBlock(
|
|
374
|
+
{ id: "enter1", label: "Enter Numbers V3", renderingMode: "Heavy" },
|
|
375
|
+
{
|
|
376
|
+
storageMode: "fromModel",
|
|
377
|
+
blockPack: await TestBPPreparer.prepare(BPSpecEnterV3),
|
|
378
|
+
},
|
|
379
|
+
);
|
|
380
|
+
mut.setStates([
|
|
381
|
+
{
|
|
382
|
+
modelAPIVersion: 2,
|
|
383
|
+
blockId: "enter1",
|
|
384
|
+
payload: { operation: "update-data", value: { numbers: [1] } },
|
|
385
|
+
},
|
|
386
|
+
]);
|
|
387
|
+
mut.save();
|
|
388
|
+
await tx.commit();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Overwrite blockStorage with v1-format data (simulating old block version)
|
|
392
|
+
await pl.withWriteTx("DowngradeStorage", async (tx) => {
|
|
393
|
+
const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
|
|
394
|
+
const v1Storage = JSON.stringify({
|
|
395
|
+
__pl_a7f3e2b9__: "v1",
|
|
396
|
+
__dataVersion: "v1",
|
|
397
|
+
__data: { numbers: [3, 1, 5] },
|
|
398
|
+
});
|
|
399
|
+
mut.setBlockStorageRaw("enter1", v1Storage);
|
|
400
|
+
mut.save();
|
|
401
|
+
await tx.commit();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Call migrateBlockPack (state-preserved) — triggers v1→v2→v3 migration
|
|
405
|
+
await pl.withWriteTx("MigrateBlockPack", async (tx) => {
|
|
406
|
+
const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
|
|
407
|
+
mut.migrateBlockPack("enter1", await TestBPPreparer.prepare(BPSpecEnterV3));
|
|
408
|
+
mut.save();
|
|
409
|
+
await tx.commit();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Verify migrated storage + re-derived args + prerunArgs
|
|
413
|
+
await poll(pl, async (tx) => {
|
|
414
|
+
const prjR = await tx.get(prj);
|
|
415
|
+
|
|
416
|
+
// Storage migrated to v3: v1→v2 sorts + adds labels, v2→v3 adds description
|
|
417
|
+
const blockStorage = await prjR.get(projectFieldName("enter1", "blockStorage"));
|
|
418
|
+
const storageData = JSON.parse(Buffer.from(blockStorage.data.data!).toString());
|
|
419
|
+
expect(storageData.__dataVersion).toBe("v3");
|
|
420
|
+
expect(storageData.__data).toStrictEqual({
|
|
421
|
+
numbers: [1, 3, 5],
|
|
422
|
+
labels: ["migrated-from-v1"],
|
|
423
|
+
description: "Migrated: migrated-from-v1",
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// currentArgs derived from migrated storage: args() sorts numbers
|
|
427
|
+
const currentArgs = await prjR.get(projectFieldName("enter1", "currentArgs"));
|
|
428
|
+
const argsData = JSON.parse(Buffer.from(currentArgs.data.data!).toString());
|
|
429
|
+
expect(argsData).toStrictEqual({ numbers: [1, 3, 5] });
|
|
430
|
+
|
|
431
|
+
// currentPrerunArgs derived: prerunArgs() filters even numbers
|
|
432
|
+
const currentPrerunArgs = await prjR.get(projectFieldName("enter1", "currentPrerunArgs"));
|
|
433
|
+
const prerunData = JSON.parse(Buffer.from(currentPrerunArgs.data.data!).toString());
|
|
434
|
+
expect(prerunData).toStrictEqual({ evenNumbers: [] });
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
test("v3 blocks: migrateBlockPack assigns author marker", async () => {
|
|
440
|
+
const quickJs = await getQuickJS();
|
|
441
|
+
|
|
442
|
+
await TestHelpers.withTempRoot(async (pl) => {
|
|
443
|
+
const prj = await pl.withWriteTx("CreatingProject", async (tx) => {
|
|
444
|
+
const prjRef = await createProject(tx);
|
|
445
|
+
tx.createField(field(tx.clientRoot, "prj"), "Dynamic", prjRef);
|
|
446
|
+
await tx.commit();
|
|
447
|
+
return await toGlobalResourceId(prjRef);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// Add block with data
|
|
451
|
+
await pl.withWriteTx("AddBlock", async (tx) => {
|
|
452
|
+
const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj);
|
|
453
|
+
mut.addBlock(
|
|
454
|
+
{ id: "enter1", label: "Enter Numbers V3", renderingMode: "Heavy" },
|
|
455
|
+
{
|
|
456
|
+
storageMode: "fromModel",
|
|
457
|
+
blockPack: await TestBPPreparer.prepare(BPSpecEnterV3),
|
|
458
|
+
},
|
|
459
|
+
);
|
|
460
|
+
mut.setStates([
|
|
461
|
+
{
|
|
462
|
+
modelAPIVersion: 2,
|
|
463
|
+
blockId: "enter1",
|
|
464
|
+
payload: { operation: "update-data", value: { numbers: [1, 2, 3] } },
|
|
465
|
+
},
|
|
466
|
+
]);
|
|
467
|
+
mut.save();
|
|
468
|
+
await tx.commit();
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// Call migrateBlockPack with an author marker
|
|
472
|
+
const testAuthor: AuthorMarker = { authorId: "test-author-123", localVersion: 1 };
|
|
473
|
+
await pl.withWriteTx("MigrateWithAuthor", async (tx) => {
|
|
474
|
+
const mut = await ProjectMutator.load(new ProjectHelper(quickJs), tx, prj, testAuthor);
|
|
475
|
+
mut.migrateBlockPack("enter1", await TestBPPreparer.prepare(BPSpecEnterV3));
|
|
476
|
+
mut.save();
|
|
477
|
+
await tx.commit();
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Verify the author marker KV is set
|
|
481
|
+
await poll(pl, async (tx) => {
|
|
482
|
+
const prjR = await tx.get(prj);
|
|
483
|
+
const author = await prjR.getKValueObj<AuthorMarker>(blockArgsAuthorKey("enter1"));
|
|
484
|
+
expect(author).toStrictEqual(testAuthor);
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
});
|
package/src/mutator/project.ts
CHANGED
|
@@ -1250,9 +1250,42 @@ export class ProjectMutator {
|
|
|
1250
1250
|
);
|
|
1251
1251
|
}
|
|
1252
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
|
+
}
|
|
1280
|
+
} else {
|
|
1281
|
+
// Legacy blocks (modelAPIVersion 1): prerunArgs = currentArgs
|
|
1282
|
+
if (info.fields.currentArgs !== undefined) {
|
|
1283
|
+
this.setBlockFieldObj(blockId, "currentPrerunArgs", info.fields.currentArgs);
|
|
1253
1284
|
}
|
|
1254
1285
|
}
|
|
1255
1286
|
|
|
1287
|
+
this.blocksWithChangedInputs.add(blockId);
|
|
1288
|
+
|
|
1256
1289
|
// resetting staging outputs for all downstream blocks
|
|
1257
1290
|
this.getStagingGraph().traverse("downstream", [blockId], ({ id }) => this.resetStaging(id));
|
|
1258
1291
|
}
|