@milaboratories/pl-middle-layer 1.45.5 → 1.46.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +15 -15
  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
@@ -1,6 +1,6 @@
1
- import { SchemaVersionKey, SchemaVersionCurrent, ProjectStructureKey, projectFieldName } from '../model/project_model.js';
1
+ import { SchemaVersionKey, SchemaVersionCurrent, SchemaVersionV2, ProjectStructureKey, projectFieldName } from '../model/project_model.js';
2
2
  import { SchemaVersionV1, BlockFrontendStateKeyPrefixV1 } from '../model/project_model_v1.js';
3
- import { field } from '@milaboratories/pl-client';
3
+ import { field, isNullResourceId } from '@milaboratories/pl-client';
4
4
  import { cachedDeserialize } from '@milaboratories/ts-helpers';
5
5
  import { allBlocks } from '../model/project_model_util.js';
6
6
 
@@ -12,13 +12,19 @@ import { allBlocks } from '../model/project_model_util.js';
12
12
  */
13
13
  async function applyProjectMigrations(pl, rid) {
14
14
  await pl.withWriteTx('ProjectMigration', async (tx) => {
15
- const schemaVersion = await tx.getKValueJson(rid, SchemaVersionKey);
15
+ let schemaVersion = await tx.getKValueJson(rid, SchemaVersionKey);
16
16
  if (schemaVersion === SchemaVersionCurrent)
17
17
  return;
18
+ // Apply migrations in sequence
18
19
  if (schemaVersion === SchemaVersionV1) {
19
20
  await migrateV1ToV2(tx, rid);
21
+ schemaVersion = SchemaVersionV2;
20
22
  }
21
- else {
23
+ if (schemaVersion === SchemaVersionV2) {
24
+ await migrateV2ToV3(tx, rid);
25
+ }
26
+ else if (schemaVersion !== SchemaVersionV1) {
27
+ // If we got here and it's not v1 (which was handled above), it's unknown
22
28
  throw new Error(`Unknown project schema version: ${schemaVersion}`);
23
29
  }
24
30
  tx.setKValue(rid, SchemaVersionKey, JSON.stringify(SchemaVersionCurrent));
@@ -45,11 +51,66 @@ async function migrateV1ToV2(tx, rid) {
45
51
  const uiState = kvMap.get(kvKey);
46
52
  const valueJson = uiState ? cachedDeserialize(uiState) : {};
47
53
  const uiStateR = tx.createJsonGzValue(valueJson);
48
- const uiStateF = field(rid, projectFieldName(block.id, 'uiState'));
54
+ const uiStateF = field(rid, projectFieldName(block.id, 'blockStorage'));
49
55
  tx.createField(uiStateF, 'Dynamic', uiStateR);
50
56
  tx.deleteKValue(rid, kvKey);
51
57
  }
52
58
  }
59
+ /**
60
+ * Migrates the project from schema version 2 to 3.
61
+ *
62
+ * Summary of changes:
63
+ * - Introduces unified 'blockStorage' field containing { args, uiState }
64
+ * - Adds 'currentPrerunArgs' field for staging/prerun rendering
65
+ * - For each block:
66
+ * 1. Read existing 'blockStorage' field (contains uiState in v2)
67
+ * 2. Read existing 'currentArgs' field (contains args)
68
+ * 3. Create unified state = { args: currentArgs, uiState: oldState }
69
+ * 4. Write to new {blockId}-blockStorage field (overwrites)
70
+ * 5. Initialize {blockId}-currentPrerunArgs (same as prodArgs for v1/v2 blocks)
71
+ * - Note: currentArgs and prodArgs fields remain for compatibility layer
72
+ *
73
+ * @param tx - The transaction to use.
74
+ * @param rid - The resource id of the project.
75
+ */
76
+ async function migrateV2ToV3(tx, rid) {
77
+ const [structure, fullResourceState] = await Promise.all([
78
+ tx.getKValueJson(rid, ProjectStructureKey),
79
+ tx.getResourceData(rid, true),
80
+ ]);
81
+ // Build a map of field name -> resource id for quick lookup
82
+ const fieldMap = new Map();
83
+ for (const f of fullResourceState.fields) {
84
+ if (!isNullResourceId(f.value)) {
85
+ fieldMap.set(f.name, f.value);
86
+ }
87
+ }
88
+ for (const block of allBlocks(structure)) {
89
+ // Read existing field values
90
+ const uiStateFieldName = projectFieldName(block.id, 'uiState');
91
+ const currentArgsFieldName = projectFieldName(block.id, 'currentArgs');
92
+ const uiStateRid = fieldMap.get(uiStateFieldName);
93
+ const currentArgsRid = fieldMap.get(currentArgsFieldName);
94
+ // Read field data in parallel where available
95
+ const [uiStateData, currentArgsData] = await Promise.all([
96
+ uiStateRid ? tx.getResourceData(uiStateRid, false) : Promise.resolve(undefined),
97
+ currentArgsRid ? tx.getResourceData(currentArgsRid, false) : Promise.resolve(undefined),
98
+ ]);
99
+ // Extract values - in v2, 'blockStorage' contains raw uiState, not wrapped
100
+ const uiState = uiStateData?.data ? cachedDeserialize(uiStateData.data) : {};
101
+ const args = currentArgsData?.data ? cachedDeserialize(currentArgsData.data) : {};
102
+ // Create unified state: { args, uiState }
103
+ const unifiedState = {
104
+ args,
105
+ uiState,
106
+ };
107
+ const blockStorageFieldName = projectFieldName(block.id, 'blockStorage');
108
+ // Write new unified blockStorage field (overwrite existing)
109
+ const stateR = tx.createJsonGzValue(unifiedState);
110
+ const stateF = field(rid, blockStorageFieldName);
111
+ tx.createField(stateF, 'Dynamic', stateR);
112
+ }
113
+ }
53
114
 
54
115
  export { applyProjectMigrations };
55
116
  //# sourceMappingURL=migration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migration.js","sources":["../../src/mutator/migration.ts"],"sourcesContent":["import type { PlClient, PlTransaction, ResourceId } from '@milaboratories/pl-client';\nimport type { ProjectStructure } from '../model/project_model';\nimport { projectFieldName, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey } from '../model/project_model';\nimport { BlockFrontendStateKeyPrefixV1, SchemaVersionV1 } from '../model/project_model_v1';\nimport { field } from '@milaboratories/pl-client';\nimport { cachedDeserialize } from '@milaboratories/ts-helpers';\nimport { allBlocks } from '../model/project_model_util';\n\n/**\n * Migrates the project to the latest schema version.\n *\n * @param pl - The client to use.\n * @param rid - The resource id of the project.\n */\nexport async function applyProjectMigrations(pl: PlClient, rid: ResourceId) {\n await pl.withWriteTx('ProjectMigration', async (tx) => {\n const schemaVersion = await tx.getKValueJson<string>(rid, SchemaVersionKey);\n if (schemaVersion === SchemaVersionCurrent) return;\n if (schemaVersion === SchemaVersionV1) {\n await migrateV1ToV2(tx, rid);\n } else {\n throw new Error(`Unknown project schema version: ${schemaVersion}`);\n }\n tx.setKValue(rid, SchemaVersionKey, JSON.stringify(SchemaVersionCurrent));\n await tx.commit();\n });\n}\n\n/**\n * Migrates the project from schema version 1 to 2.\n *\n * Summary of changes:\n * - uiState is now stored in a field instead of a KV\n *\n * @param tx - The transaction to use.\n * @param rid - The resource id of the project.\n */\nasync function migrateV1ToV2(tx: PlTransaction, rid: ResourceId) {\n const [structure, allKV] = await Promise.all([\n tx.getKValueJson<ProjectStructure>(rid, ProjectStructureKey),\n tx.listKeyValues(rid),\n ]);\n const kvMap = new Map<string, Uint8Array>(allKV.map((kv) => [kv.key, kv.value]));\n for (const block of allBlocks(structure)) {\n const kvKey = BlockFrontendStateKeyPrefixV1 + block.id;\n const uiState = kvMap.get(kvKey);\n const valueJson = uiState ? cachedDeserialize(uiState) : {};\n const uiStateR = tx.createJsonGzValue(valueJson);\n const uiStateF = field(rid, projectFieldName(block.id, 'uiState'));\n tx.createField(uiStateF, 'Dynamic', uiStateR);\n tx.deleteKValue(rid, kvKey);\n }\n}\n"],"names":[],"mappings":";;;;;;AAQA;;;;;AAKG;AACI,eAAe,sBAAsB,CAAC,EAAY,EAAE,GAAe,EAAA;IACxE,MAAM,EAAE,CAAC,WAAW,CAAC,kBAAkB,EAAE,OAAO,EAAE,KAAI;QACpD,MAAM,aAAa,GAAG,MAAM,EAAE,CAAC,aAAa,CAAS,GAAG,EAAE,gBAAgB,CAAC;QAC3E,IAAI,aAAa,KAAK,oBAAoB;YAAE;AAC5C,QAAA,IAAI,aAAa,KAAK,eAAe,EAAE;AACrC,YAAA,MAAM,aAAa,CAAC,EAAE,EAAE,GAAG,CAAC;QAC9B;aAAO;AACL,YAAA,MAAM,IAAI,KAAK,CAAC,mCAAmC,aAAa,CAAA,CAAE,CAAC;QACrE;AACA,QAAA,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;AACzE,QAAA,MAAM,EAAE,CAAC,MAAM,EAAE;AACnB,IAAA,CAAC,CAAC;AACJ;AAEA;;;;;;;;AAQG;AACH,eAAe,aAAa,CAAC,EAAiB,EAAE,GAAe,EAAA;IAC7D,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;AAC3C,QAAA,EAAE,CAAC,aAAa,CAAmB,GAAG,EAAE,mBAAmB,CAAC;AAC5D,QAAA,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC;AACtB,KAAA,CAAC;IACF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAqB,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAChF,KAAK,MAAM,KAAK,IAAI,SAAS,CAAC,SAAS,CAAC,EAAE;AACxC,QAAA,MAAM,KAAK,GAAG,6BAA6B,GAAG,KAAK,CAAC,EAAE;QACtD,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;AAChC,QAAA,MAAM,SAAS,GAAG,OAAO,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,EAAE;QAC3D,MAAM,QAAQ,GAAG,EAAE,CAAC,iBAAiB,CAAC,SAAS,CAAC;AAChD,QAAA,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAClE,EAAE,CAAC,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,CAAC;AAC7C,QAAA,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC;IAC7B;AACF;;;;"}
1
+ {"version":3,"file":"migration.js","sources":["../../src/mutator/migration.ts"],"sourcesContent":["import type { PlClient, PlTransaction, ResourceId } from '@milaboratories/pl-client';\nimport type { ProjectField, ProjectStructure } from '../model/project_model';\nimport { projectFieldName, ProjectStructureKey, SchemaVersionCurrent, SchemaVersionKey, SchemaVersionV2 } from '../model/project_model';\nimport { BlockFrontendStateKeyPrefixV1, SchemaVersionV1 } from '../model/project_model_v1';\nimport { field, isNullResourceId } from '@milaboratories/pl-client';\nimport { cachedDeserialize } from '@milaboratories/ts-helpers';\nimport { allBlocks } from '../model/project_model_util';\n\n/**\n * Migrates the project to the latest schema version.\n *\n * @param pl - The client to use.\n * @param rid - The resource id of the project.\n */\nexport async function applyProjectMigrations(pl: PlClient, rid: ResourceId) {\n await pl.withWriteTx('ProjectMigration', async (tx) => {\n let schemaVersion = await tx.getKValueJson<string>(rid, SchemaVersionKey);\n if (schemaVersion === SchemaVersionCurrent) return;\n\n // Apply migrations in sequence\n if (schemaVersion === SchemaVersionV1) {\n await migrateV1ToV2(tx, rid);\n schemaVersion = SchemaVersionV2;\n }\n\n if (schemaVersion === SchemaVersionV2) {\n await migrateV2ToV3(tx, rid);\n } else if (schemaVersion !== SchemaVersionV1) {\n // If we got here and it's not v1 (which was handled above), it's unknown\n throw new Error(`Unknown project schema version: ${schemaVersion}`);\n }\n\n tx.setKValue(rid, SchemaVersionKey, JSON.stringify(SchemaVersionCurrent));\n await tx.commit();\n });\n}\n\n/**\n * Migrates the project from schema version 1 to 2.\n *\n * Summary of changes:\n * - uiState is now stored in a field instead of a KV\n *\n * @param tx - The transaction to use.\n * @param rid - The resource id of the project.\n */\nasync function migrateV1ToV2(tx: PlTransaction, rid: ResourceId) {\n const [structure, allKV] = await Promise.all([\n tx.getKValueJson<ProjectStructure>(rid, ProjectStructureKey),\n tx.listKeyValues(rid),\n ]);\n const kvMap = new Map<string, Uint8Array>(allKV.map((kv) => [kv.key, kv.value]));\n for (const block of allBlocks(structure)) {\n const kvKey = BlockFrontendStateKeyPrefixV1 + block.id;\n const uiState = kvMap.get(kvKey);\n const valueJson = uiState ? cachedDeserialize(uiState) : {};\n const uiStateR = tx.createJsonGzValue(valueJson);\n const uiStateF = field(rid, projectFieldName(block.id, 'blockStorage'));\n tx.createField(uiStateF, 'Dynamic', uiStateR);\n tx.deleteKValue(rid, kvKey);\n }\n}\n\n/**\n * Migrates the project from schema version 2 to 3.\n *\n * Summary of changes:\n * - Introduces unified 'blockStorage' field containing { args, uiState }\n * - Adds 'currentPrerunArgs' field for staging/prerun rendering\n * - For each block:\n * 1. Read existing 'blockStorage' field (contains uiState in v2)\n * 2. Read existing 'currentArgs' field (contains args)\n * 3. Create unified state = { args: currentArgs, uiState: oldState }\n * 4. Write to new {blockId}-blockStorage field (overwrites)\n * 5. Initialize {blockId}-currentPrerunArgs (same as prodArgs for v1/v2 blocks)\n * - Note: currentArgs and prodArgs fields remain for compatibility layer\n *\n * @param tx - The transaction to use.\n * @param rid - The resource id of the project.\n */\nasync function migrateV2ToV3(tx: PlTransaction, rid: ResourceId) {\n const [structure, fullResourceState] = await Promise.all([\n tx.getKValueJson<ProjectStructure>(rid, ProjectStructureKey),\n tx.getResourceData(rid, true),\n ]);\n\n // Build a map of field name -> resource id for quick lookup\n const fieldMap = new Map<string, ResourceId>();\n for (const f of fullResourceState.fields) {\n if (!isNullResourceId(f.value)) {\n fieldMap.set(f.name, f.value);\n }\n }\n\n for (const block of allBlocks(structure)) {\n // Read existing field values\n const uiStateFieldName = projectFieldName(block.id, 'uiState' as ProjectField['fieldName']);\n const currentArgsFieldName = projectFieldName(block.id, 'currentArgs');\n\n const uiStateRid = fieldMap.get(uiStateFieldName);\n const currentArgsRid = fieldMap.get(currentArgsFieldName);\n\n // Read field data in parallel where available\n const [uiStateData, currentArgsData] = await Promise.all([\n uiStateRid ? tx.getResourceData(uiStateRid, false) : Promise.resolve(undefined),\n currentArgsRid ? tx.getResourceData(currentArgsRid, false) : Promise.resolve(undefined),\n ]);\n\n // Extract values - in v2, 'blockStorage' contains raw uiState, not wrapped\n const uiState = uiStateData?.data ? cachedDeserialize(uiStateData.data) : {};\n const args = currentArgsData?.data ? cachedDeserialize(currentArgsData.data) : {};\n\n // Create unified state: { args, uiState }\n const unifiedState = {\n args,\n uiState,\n };\n\n const blockStorageFieldName = projectFieldName(block.id, 'blockStorage');\n\n // Write new unified blockStorage field (overwrite existing)\n const stateR = tx.createJsonGzValue(unifiedState);\n const stateF = field(rid, blockStorageFieldName);\n tx.createField(stateF, 'Dynamic', stateR);\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=project-v3.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-v3.test.d.ts","sourceRoot":"","sources":["../../src/mutator/project-v3.test.ts"],"names":[],"mappings":""}
@@ -57,19 +57,57 @@ class BlockInfo {
57
57
  throw new Error('inconsistent stage cache fields');
58
58
  if (this.fields.blockPack === undefined)
59
59
  throw new Error('no block pack field');
60
- if (this.fields.currentArgs === undefined)
61
- throw new Error('no current args field');
60
+ if (this.fields.blockStorage === undefined)
61
+ throw new Error('no block storage field');
62
62
  }
63
- currentArgsC = cached(() => this.fields.currentArgs.modCount, () => tsHelpers.cachedDeserialize(this.fields.currentArgs.value));
63
+ currentArgsC = cached(() => this.fields.currentArgs?.modCount, () => {
64
+ const bin = this.fields.currentArgs?.value;
65
+ if (bin === undefined)
66
+ return undefined;
67
+ return tsHelpers.cachedDeserialize(bin);
68
+ });
69
+ blockStorageC = cached(() => this.fields.blockStorage.modCount, () => {
70
+ const bin = this.fields.blockStorage?.value;
71
+ if (bin === undefined)
72
+ return undefined;
73
+ return tsHelpers.cachedDeserialize(bin);
74
+ });
75
+ blockStorageJ = cached(() => this.fields.blockStorage.modCount, () => {
76
+ const bin = this.fields.blockStorage?.value;
77
+ if (bin === undefined)
78
+ return undefined;
79
+ return tsHelpers.cachedDecode(bin);
80
+ });
64
81
  prodArgsC = cached(() => this.fields.prodArgs?.modCount, () => {
65
82
  const bin = this.fields.prodArgs?.value;
66
83
  if (bin === undefined)
67
84
  return undefined;
68
85
  return tsHelpers.cachedDeserialize(bin);
69
86
  });
87
+ currentPrerunArgsC = cached(() => this.fields.currentPrerunArgs?.modCount, () => {
88
+ const bin = this.fields.currentPrerunArgs?.value;
89
+ if (bin === undefined)
90
+ return undefined;
91
+ return tsHelpers.cachedDeserialize(bin);
92
+ });
70
93
  get currentArgs() {
71
94
  return this.currentArgsC();
72
95
  }
96
+ get blockStorage() {
97
+ try {
98
+ return this.blockStorageC();
99
+ }
100
+ catch (e) {
101
+ console.error('Error getting blockStorage:', e);
102
+ return undefined;
103
+ }
104
+ }
105
+ get blockStorageJson() {
106
+ return this.blockStorageJ();
107
+ }
108
+ get currentPrerunArgs() {
109
+ return this.currentPrerunArgsC();
110
+ }
73
111
  get stagingRendered() {
74
112
  return this.fields.stagingCtx !== undefined;
75
113
  }
@@ -81,12 +119,16 @@ class BlockInfo {
81
119
  }
82
120
  productionStaleC = cached(() => `${this.fields.currentArgs.modCount}_${this.fields.prodArgs?.modCount}`, () => this.fields.prodArgs === undefined
83
121
  || Buffer.compare(this.fields.currentArgs.value, this.fields.prodArgs.value) !== 0);
84
- // get productionStale(): boolean {
85
- // return this.productionRendered && this.productionStaleC() && ;
86
- // }
87
122
  get requireProductionRendering() {
88
123
  return !this.productionRendered || this.productionStaleC() || this.productionHasErrors;
89
124
  }
125
+ /** Returns true if staging should be re-rendered (stagingCtx is not set) */
126
+ get requireStagingRendering() {
127
+ // No staging needed if currentPrerunArgs is undefined (args derivation failed)
128
+ if (this.fields.currentPrerunArgs === undefined)
129
+ return false;
130
+ return !this.stagingRendered;
131
+ }
90
132
  get prodArgs() {
91
133
  return this.prodArgsC();
92
134
  }
@@ -161,7 +203,6 @@ class ProjectMutator {
161
203
  || this.renderingStateChanged);
162
204
  }
163
205
  get structure() {
164
- // clone
165
206
  return JSON.parse(JSON.stringify(this.struct));
166
207
  }
167
208
  //
@@ -186,9 +227,13 @@ class ProjectMutator {
186
227
  args = bInfo.prodArgs;
187
228
  }
188
229
  else {
189
- argsField = tsHelpers.notEmpty(bInfo.fields.currentArgs);
230
+ argsField = bInfo.fields.currentArgs;
190
231
  args = bInfo.currentArgs;
191
232
  }
233
+ // Can't compute enrichment targets without args
234
+ if (argsField === undefined) {
235
+ return { args, enrichmentTargets: undefined };
236
+ }
192
237
  const blockPackField = tsHelpers.notEmpty(bInfo.fields.blockPack);
193
238
  if (plClient.isResourceId(argsField.ref) && plClient.isResourceId(blockPackField.ref))
194
239
  return {
@@ -221,6 +266,8 @@ class ProjectMutator {
221
266
  return info;
222
267
  }
223
268
  createJsonFieldValueByContent(content) {
269
+ if (content === undefined)
270
+ throw new Error('content is undefined');
224
271
  const value = Buffer.from(content);
225
272
  const ref = this.tx.createValue(plClient.Pl.JsonObject, value);
226
273
  return { ref, value, status: 'Ready' };
@@ -321,6 +368,62 @@ class ProjectMutator {
321
368
  return this.deleteBlockFields(blockId, 'prodOutput', 'prodCtx', 'prodUiCtx', 'prodArgs', 'prodOutputPrevious', 'prodCtxPrevious', 'prodUiCtxPrevious');
322
369
  }
323
370
  }
371
+ /**
372
+ * Gets current block state and merges with partial updates.
373
+ * Used by legacy v1/v2 methods like setBlockArgs and setUiState.
374
+ *
375
+ * @param blockId The block to get state for
376
+ * @param partialUpdate Partial state to merge (e.g. { args } or { uiState })
377
+ * @returns Merged state in unified format { args, uiState }
378
+ */
379
+ mergeBlockState(blockId, partialUpdate) {
380
+ const info = this.getBlockInfo(blockId);
381
+ const currentState = info.blockStorage;
382
+ return { ...currentState, ...partialUpdate };
383
+ }
384
+ /**
385
+ * Sets raw block storage content directly (for testing purposes).
386
+ * This bypasses all normalization and VM transformations.
387
+ *
388
+ * @param blockId The block to set storage for
389
+ * @param rawStorageJson Raw storage as JSON string
390
+ */
391
+ setBlockStorageRaw(blockId, rawStorageJson) {
392
+ this.setBlockFieldObj(blockId, 'blockStorage', this.createJsonFieldValueByContent(rawStorageJson));
393
+ this.blocksWithChangedInputs.add(blockId);
394
+ this.updateLastModified();
395
+ }
396
+ /**
397
+ * Resets a v2+ block to its initial storage state.
398
+ * Gets initial storage from VM and derives args from it.
399
+ *
400
+ * For v1 blocks, use setStates() instead.
401
+ *
402
+ * @param blockId The block to reset
403
+ */
404
+ resetToInitialStorage(blockId) {
405
+ const info = this.getBlockInfo(blockId);
406
+ const blockConfig = info.config;
407
+ if (blockConfig.modelAPIVersion !== 2) {
408
+ throw new Error('resetToInitialStorage is only supported for model API version 2');
409
+ }
410
+ // Get initial storage from VM
411
+ const initialStorageJson = this.projectHelper.getInitialStorageInVM(blockConfig);
412
+ this.setBlockStorageRaw(blockId, initialStorageJson);
413
+ // Derive args from storage - set or clear currentArgs based on derivation result
414
+ const deriveArgsResult = this.projectHelper.deriveArgsFromStorage(blockConfig, initialStorageJson);
415
+ if (!deriveArgsResult.error) {
416
+ this.setBlockFieldObj(blockId, 'currentArgs', this.createJsonFieldValue(deriveArgsResult.value));
417
+ // Derive prerunArgs from storage
418
+ const prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(blockConfig, initialStorageJson);
419
+ if (prerunArgs !== undefined) {
420
+ this.setBlockFieldObj(blockId, 'currentPrerunArgs', this.createJsonFieldValue(prerunArgs));
421
+ }
422
+ }
423
+ else {
424
+ this.deleteBlockFields(blockId, 'currentArgs');
425
+ }
426
+ }
324
427
  /** Optimally sets inputs for multiple blocks in one go */
325
428
  setStates(requests) {
326
429
  const changedArgs = [];
@@ -328,32 +431,76 @@ class ProjectMutator {
328
431
  for (const req of requests) {
329
432
  const info = this.getBlockInfo(req.blockId);
330
433
  let blockChanged = false;
331
- for (const stateKey of ['args', 'uiState']) {
332
- if (!(stateKey in req))
333
- continue;
334
- const statePart = req[stateKey];
335
- if (statePart === undefined || statePart === null)
336
- throw new Error(`Can't set ${stateKey} to null or undefined, please omit the key if you don't want to change it`);
337
- const fieldName = stateKey === 'args' ? 'currentArgs' : 'uiState';
338
- let data;
339
- let gzipped = false;
340
- if (stateKey === 'args') {
341
- // don't gzip args, workflow code can't uncompress gzip yet
342
- data = tsHelpers.canonicalJsonBytes(statePart);
434
+ const blockConfig = info.config;
435
+ // modelAPIVersion === 2 means BlockModelV3 with .args() lambda for deriving args
436
+ if (req.modelAPIVersion !== blockConfig.modelAPIVersion) {
437
+ throw new Error(`Model API version mismatch for block ${req.blockId}: ${req.modelAPIVersion} !== ${blockConfig.modelAPIVersion}`);
438
+ }
439
+ // Derive args from storage using the block's config.args() callback
440
+ let args;
441
+ let prerunArgs;
442
+ if (req.modelAPIVersion === 2) {
443
+ const currentStorageJson = info.blockStorageJson;
444
+ if (currentStorageJson === undefined) {
445
+ throw new Error(`Block ${req.blockId} has no blockStorage - this should not happen`);
446
+ }
447
+ // Apply the state update to storage
448
+ const updatedStorageJson = this.projectHelper.applyStorageUpdateInVM(blockConfig, currentStorageJson, req.payload);
449
+ this.setBlockFieldObj(req.blockId, 'blockStorage', this.createJsonFieldValueByContent(updatedStorageJson));
450
+ // Derive args directly from storage (VM extracts data internally)
451
+ const derivedArgsResult = this.projectHelper.deriveArgsFromStorage(blockConfig, updatedStorageJson);
452
+ if (derivedArgsResult.error) {
453
+ args = undefined;
454
+ prerunArgs = undefined;
343
455
  }
344
456
  else {
345
- const { data: binary, isGzipped } = tsHelpers.canonicalJsonGzBytes(statePart);
346
- data = binary;
347
- gzipped = isGzipped;
457
+ args = derivedArgsResult.value;
458
+ // Derive prerunArgs from storage, or fall back to args
459
+ prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(blockConfig, updatedStorageJson);
348
460
  }
349
- if (Buffer.compare(info.fields[fieldName].value, data) === 0)
350
- continue;
351
- // console.log('setting', fieldName, gzipped, data.length);
352
- const statePartRef = this.tx.createValue(gzipped ? plClient.Pl.JsonGzObject : plClient.Pl.JsonObject, data);
353
- this.setBlockField(req.blockId, fieldName, statePartRef, 'Ready', data);
354
- blockChanged = true;
355
- if (stateKey === 'args')
356
- changedArgs.push(req.blockId);
461
+ }
462
+ else {
463
+ this.setBlockFieldObj(req.blockId, 'blockStorage', this.createJsonFieldValue(req.state));
464
+ if (req.state !== null && typeof req.state === 'object' && 'args' in req.state) {
465
+ args = req.state.args;
466
+ }
467
+ else {
468
+ args = req.state;
469
+ }
470
+ // For the legacy blocks, prerunArgs = args (same as production args)
471
+ prerunArgs = args;
472
+ }
473
+ // Set or clear currentArgs based on derivation result
474
+ if (args !== undefined) {
475
+ const currentArgsData = tsHelpers.canonicalJsonBytes(args);
476
+ const argsPartRef = this.tx.createValue(plClient.Pl.JsonObject, currentArgsData);
477
+ this.setBlockField(req.blockId, 'currentArgs', argsPartRef, 'Ready', currentArgsData);
478
+ }
479
+ else {
480
+ this.deleteBlockFields(req.blockId, 'currentArgs');
481
+ }
482
+ // Set currentPrerunArgs field and check if it actually changed
483
+ let prerunArgsChanged = false;
484
+ if (prerunArgs !== undefined) {
485
+ const prerunArgsData = tsHelpers.canonicalJsonBytes(prerunArgs);
486
+ const oldPrerunArgsData = info.fields.currentPrerunArgs?.value;
487
+ // Check if prerunArgs actually changed
488
+ if (oldPrerunArgsData === undefined || Buffer.compare(oldPrerunArgsData, prerunArgsData) !== 0) {
489
+ prerunArgsChanged = true;
490
+ }
491
+ const prerunArgsRef = this.tx.createValue(plClient.Pl.JsonObject, prerunArgsData);
492
+ this.setBlockField(req.blockId, 'currentPrerunArgs', prerunArgsRef, 'Ready', prerunArgsData);
493
+ }
494
+ else {
495
+ // prerunArgs is undefined - check if we previously had one
496
+ if (info.fields.currentPrerunArgs !== undefined) {
497
+ prerunArgsChanged = true;
498
+ }
499
+ }
500
+ blockChanged = true;
501
+ // Only add to changedArgs if prerunArgs changed - this controls staging reset
502
+ if (prerunArgsChanged) {
503
+ changedArgs.push(req.blockId);
357
504
  }
358
505
  if (blockChanged) {
359
506
  // will be assigned our author marker
@@ -395,15 +542,25 @@ class ProjectMutator {
395
542
  exportCtx(ctx) {
396
543
  return context_export.exportContext(this.tx, plClient.Pl.unwrapHolder(this.tx, this.ctxExportTplHolder), ctx);
397
544
  }
545
+ /**
546
+ * Renders staging for a block using currentPrerunArgs.
547
+ * If currentPrerunArgs is not set (prerunArgs returned undefined), skips staging for this block.
548
+ */
398
549
  renderStagingFor(blockId) {
399
550
  this.resetStaging(blockId);
400
551
  const info = this.getBlockInfo(blockId);
552
+ // If currentPrerunArgs is not set (prerunArgs returned undefined), skip staging for this block
553
+ const prerunArgsRef = info.fields.currentPrerunArgs?.ref;
554
+ if (prerunArgsRef === undefined) {
555
+ return;
556
+ }
401
557
  const ctx = this.createStagingCtx(this.getStagingGraph().nodes.get(blockId).upstream);
402
558
  if (this.getBlock(blockId).renderingMode !== 'Heavy')
403
559
  throw new Error('not supported yet');
404
560
  const tpl = info.getTemplate(this.tx);
561
+ // Use currentPrerunArgs for staging rendering
405
562
  const results = render_block.createRenderHeavyBlock(this.tx, tpl, {
406
- args: info.fields.currentArgs.ref,
563
+ args: prerunArgsRef,
407
564
  blockId: this.tx.createValue(plClient.Pl.JsonString, JSON.stringify(blockId)),
408
565
  isProduction: this.tx.createValue(plClient.Pl.JsonBool, JSON.stringify(false)),
409
566
  context: ctx,
@@ -420,6 +577,10 @@ class ProjectMutator {
420
577
  renderProductionFor(blockId) {
421
578
  this.resetProduction(blockId);
422
579
  const info = this.getBlockInfo(blockId);
580
+ // Can't render production if currentArgs is not set
581
+ if (info.fields.currentArgs === undefined) {
582
+ throw new Error(`Can't render production for block ${blockId}: currentArgs not set`);
583
+ }
423
584
  const ctx = this.createProdCtx(this.getPendingProductionGraph().nodes.get(blockId).upstream);
424
585
  if (this.getBlock(blockId).renderingMode === 'Light')
425
586
  throw new Error('Can\'t render production for light block.');
@@ -450,10 +611,47 @@ class ProjectMutator {
450
611
  this.setBlockField(blockId, 'blockPack', plClient.Pl.wrapInHolder(this.tx, bp), 'NotReady');
451
612
  // settings
452
613
  this.setBlockFieldObj(blockId, 'blockSettings', this.createJsonFieldValue(plModelMiddleLayer.InitialBlockSettings));
453
- // args
454
- this.setBlockFieldObj(blockId, 'currentArgs', this.createJsonFieldValueByContent(spec.args));
455
- // uiState
456
- this.setBlockFieldObj(blockId, 'uiState', this.createJsonFieldValueByContent(spec.uiState ?? '{}'));
614
+ const blockConfig = info.config;
615
+ let args;
616
+ let prerunArgs;
617
+ let storageToWrite;
618
+ if (spec.storageMode === 'fromModel') {
619
+ // Model API v2+: get initial storage and derive args from it
620
+ storageToWrite = this.projectHelper.getInitialStorageInVM(blockConfig);
621
+ // Derive args directly from storage (VM extracts data internally)
622
+ const deriveArgsResult = this.projectHelper.deriveArgsFromStorage(blockConfig, storageToWrite);
623
+ if (deriveArgsResult.error) {
624
+ args = undefined;
625
+ prerunArgs = undefined;
626
+ }
627
+ else {
628
+ args = deriveArgsResult.value;
629
+ prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(blockConfig, storageToWrite);
630
+ }
631
+ }
632
+ else if (spec.storageMode === 'legacy') {
633
+ // Model API v1: use legacyState from spec
634
+ const parsedState = JSON.parse(spec.legacyState);
635
+ args = parsedState.args;
636
+ if (args === undefined) {
637
+ throw new Error('args is undefined in legacyState');
638
+ }
639
+ prerunArgs = args;
640
+ storageToWrite = spec.legacyState;
641
+ }
642
+ else {
643
+ throw new Error(`Unknown storageMode: ${spec.storageMode}`);
644
+ }
645
+ // currentArgs
646
+ if (args !== undefined) {
647
+ this.setBlockFieldObj(blockId, 'currentArgs', this.createJsonFieldValue(args));
648
+ }
649
+ // currentPrerunArgs
650
+ if (prerunArgs !== undefined) {
651
+ this.setBlockFieldObj(blockId, 'currentPrerunArgs', this.createJsonFieldValue(prerunArgs));
652
+ }
653
+ // blockStorage
654
+ this.setBlockFieldObj(blockId, 'blockStorage', this.createJsonFieldValueByContent(storageToWrite));
457
655
  // checking structure
458
656
  info.check();
459
657
  }
@@ -629,14 +827,53 @@ class ProjectMutator {
629
827
  //
630
828
  // Block-pack migration
631
829
  //
632
- migrateBlockPack(blockId, spec, newArgsAndUiState) {
830
+ migrateBlockPack(blockId, spec, newClearState) {
633
831
  const info = this.getBlockInfo(blockId);
832
+ const newConfig = model.extractConfig(spec.config);
634
833
  this.setBlockField(blockId, 'blockPack', plClient.Pl.wrapInHolder(this.tx, block_pack.createBlockPack(this.tx, spec)), 'NotReady');
635
- if (newArgsAndUiState !== undefined) {
636
- // this will also reset all downstream stagings
637
- this.setStates([{ blockId, args: newArgsAndUiState.args, uiState: newArgsAndUiState.uiState }]);
834
+ if (newClearState !== undefined) {
835
+ // State is being reset - no migration needed
836
+ const supportsStorageFromVM = newConfig.modelAPIVersion === 2;
837
+ if (supportsStorageFromVM) {
838
+ // V2+: Get initial storage directly from VM and derive args from it
839
+ const initialStorageJson = this.projectHelper.getInitialStorageInVM(newConfig);
840
+ this.setBlockStorageRaw(blockId, initialStorageJson);
841
+ // Derive args from storage - only set currentArgs if derivation succeeds
842
+ const deriveArgsResult = this.projectHelper.deriveArgsFromStorage(newConfig, initialStorageJson);
843
+ if (!deriveArgsResult.error) {
844
+ this.setBlockFieldObj(blockId, 'currentArgs', this.createJsonFieldValue(deriveArgsResult.value));
845
+ // Derive prerunArgs from storage
846
+ const prerunArgs = this.projectHelper.derivePrerunArgsFromStorage(newConfig, initialStorageJson);
847
+ if (prerunArgs !== undefined) {
848
+ this.setBlockFieldObj(blockId, 'currentPrerunArgs', this.createJsonFieldValue(prerunArgs));
849
+ }
850
+ }
851
+ this.blocksWithChangedInputs.add(blockId);
852
+ this.updateLastModified();
853
+ }
854
+ else {
855
+ // V1: Use setStates with legacy state format
856
+ this.setStates([{ modelAPIVersion: 1, blockId, state: newClearState.state }]);
857
+ }
638
858
  }
639
859
  else {
860
+ // State is being preserved - run migrations if needed via VM
861
+ // Only Model API v2 blocks support migrations
862
+ const supportsStateMigrations = newConfig.modelAPIVersion === 2;
863
+ if (supportsStateMigrations) {
864
+ const currentStorageJson = info.blockStorageJson;
865
+ const migrationResult = this.projectHelper.migrateStorageInVM(newConfig, currentStorageJson);
866
+ if (migrationResult.error !== undefined) {
867
+ console.error(`[migrateBlockPack] Block ${blockId} migration error: ${migrationResult.error}`);
868
+ }
869
+ else {
870
+ console.log(`[migrateBlockPack] Block ${blockId}: ${migrationResult.info}`);
871
+ if (migrationResult.warn) {
872
+ console.warn(`[migrateBlockPack] Block ${blockId} migration warning: ${migrationResult.warn}`);
873
+ }
874
+ this.setBlockStorageRaw(blockId, migrationResult.newStorageJson);
875
+ }
876
+ }
640
877
  // resetting staging outputs for all downstream blocks
641
878
  this.getStagingGraph().traverse('downstream', [blockId], ({ id }) => this.resetStaging(id));
642
879
  }
@@ -737,13 +974,16 @@ class ProjectMutator {
737
974
  const stagingGraph = this.getStagingGraph();
738
975
  stagingGraph.nodes.forEach((node) => {
739
976
  const info = this.getBlockInfo(node.id);
740
- let lag = info.stagingRendered ? 0 : 1;
977
+ // Use requireStagingRendering to check both: staging exists AND prerunArgs hasn't changed
978
+ const requiresRendering = info.requireStagingRendering;
979
+ let lag = requiresRendering ? 1 : 0;
741
980
  node.upstream.forEach((upstream) => {
742
981
  const upstreamLag = lags.get(upstream);
743
982
  if (upstreamLag === 0)
744
983
  return;
745
984
  lag = Math.max(upstreamLag + 1, lag);
746
985
  });
986
+ if (!requiresRendering && info.stagingRendered) ;
747
987
  cb(node.id, lag);
748
988
  lags.set(node.id, lag);
749
989
  });
@@ -760,6 +1000,7 @@ class ProjectMutator {
760
1000
  // meaning staging already rendered
761
1001
  return;
762
1002
  if (lagThreshold === undefined || lag <= lagThreshold) {
1003
+ // console.log(`[refreshStagings] RENDER staging for ${blockId} (lag=${lag})`);
763
1004
  this.renderStagingFor(blockId);
764
1005
  rendered++;
765
1006
  }