@platforma-sdk/model 1.58.5 → 1.58.11

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 (365) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +43 -0
  2. package/dist/_virtual/_rolldown/runtime.js +18 -0
  3. package/dist/annotations/converter.cjs +15 -20
  4. package/dist/annotations/converter.cjs.map +1 -1
  5. package/dist/annotations/converter.d.ts +6 -2
  6. package/dist/annotations/converter.js +14 -18
  7. package/dist/annotations/converter.js.map +1 -1
  8. package/dist/annotations/index.cjs +1 -0
  9. package/dist/annotations/index.d.ts +2 -3
  10. package/dist/annotations/index.js +1 -0
  11. package/dist/annotations/types.d.ts +21 -16
  12. package/dist/bconfig/index.cjs +2 -0
  13. package/dist/bconfig/index.d.ts +5 -6
  14. package/dist/bconfig/index.js +2 -0
  15. package/dist/bconfig/lambdas.d.ts +52 -52
  16. package/dist/bconfig/normalization.cjs +13 -18
  17. package/dist/bconfig/normalization.cjs.map +1 -1
  18. package/dist/bconfig/normalization.d.ts +10 -6
  19. package/dist/bconfig/normalization.js +12 -16
  20. package/dist/bconfig/normalization.js.map +1 -1
  21. package/dist/bconfig/types.cjs +4 -3
  22. package/dist/bconfig/types.cjs.map +1 -1
  23. package/dist/bconfig/types.d.ts +10 -5
  24. package/dist/bconfig/types.js +4 -2
  25. package/dist/bconfig/types.js.map +1 -1
  26. package/dist/bconfig/util.d.ts +7 -4
  27. package/dist/bconfig/v3.d.ts +10 -6
  28. package/dist/block_api_v1.d.ts +62 -58
  29. package/dist/block_api_v2.d.ts +51 -47
  30. package/dist/block_api_v3.d.ts +33 -29
  31. package/dist/block_migrations.cjs +481 -413
  32. package/dist/block_migrations.cjs.map +1 -1
  33. package/dist/block_migrations.d.ts +258 -204
  34. package/dist/block_migrations.js +482 -408
  35. package/dist/block_migrations.js.map +1 -1
  36. package/dist/block_model.cjs +312 -343
  37. package/dist/block_model.cjs.map +1 -1
  38. package/dist/block_model.d.ts +143 -147
  39. package/dist/block_model.js +312 -341
  40. package/dist/block_model.js.map +1 -1
  41. package/dist/block_model_legacy.cjs +231 -255
  42. package/dist/block_model_legacy.cjs.map +1 -1
  43. package/dist/block_model_legacy.d.ts +108 -106
  44. package/dist/block_model_legacy.js +231 -253
  45. package/dist/block_model_legacy.js.map +1 -1
  46. package/dist/block_state_patch.d.ts +10 -10
  47. package/dist/block_state_util.cjs +15 -19
  48. package/dist/block_state_util.cjs.map +1 -1
  49. package/dist/block_state_util.d.ts +14 -13
  50. package/dist/block_state_util.js +15 -18
  51. package/dist/block_state_util.js.map +1 -1
  52. package/dist/block_storage.cjs +233 -238
  53. package/dist/block_storage.cjs.map +1 -1
  54. package/dist/block_storage.d.ts +62 -78
  55. package/dist/block_storage.js +234 -237
  56. package/dist/block_storage.js.map +1 -1
  57. package/dist/block_storage_callbacks.cjs +156 -195
  58. package/dist/block_storage_callbacks.cjs.map +1 -1
  59. package/dist/block_storage_callbacks.js +156 -192
  60. package/dist/block_storage_callbacks.js.map +1 -1
  61. package/dist/block_storage_facade.cjs +29 -85
  62. package/dist/block_storage_facade.cjs.map +1 -1
  63. package/dist/block_storage_facade.d.ts +83 -126
  64. package/dist/block_storage_facade.js +29 -83
  65. package/dist/block_storage_facade.js.map +1 -1
  66. package/dist/components/PFrameForGraphs.cjs +26 -24
  67. package/dist/components/PFrameForGraphs.cjs.map +1 -1
  68. package/dist/components/PFrameForGraphs.d.ts +12 -7
  69. package/dist/components/PFrameForGraphs.js +25 -22
  70. package/dist/components/PFrameForGraphs.js.map +1 -1
  71. package/dist/components/PlAnnotations/filter.d.ts +62 -79
  72. package/dist/components/PlAnnotations/filters_ui.cjs +135 -171
  73. package/dist/components/PlAnnotations/filters_ui.cjs.map +1 -1
  74. package/dist/components/PlAnnotations/filters_ui.d.ts +48 -46
  75. package/dist/components/PlAnnotations/filters_ui.js +135 -170
  76. package/dist/components/PlAnnotations/filters_ui.js.map +1 -1
  77. package/dist/components/PlAnnotations/index.d.ts +2 -3
  78. package/dist/components/PlDataTable/index.cjs +2 -0
  79. package/dist/components/PlDataTable/index.d.ts +3 -5
  80. package/dist/components/PlDataTable/index.js +2 -0
  81. package/dist/components/PlDataTable/labels.cjs +59 -81
  82. package/dist/components/PlDataTable/labels.cjs.map +1 -1
  83. package/dist/components/PlDataTable/labels.js +58 -79
  84. package/dist/components/PlDataTable/labels.js.map +1 -1
  85. package/dist/components/PlDataTable/state-migration.cjs +186 -144
  86. package/dist/components/PlDataTable/state-migration.cjs.map +1 -1
  87. package/dist/components/PlDataTable/state-migration.d.ts +85 -82
  88. package/dist/components/PlDataTable/state-migration.js +185 -142
  89. package/dist/components/PlDataTable/state-migration.js.map +1 -1
  90. package/dist/components/PlDataTable/table.cjs +172 -194
  91. package/dist/components/PlDataTable/table.cjs.map +1 -1
  92. package/dist/components/PlDataTable/table.d.ts +16 -12
  93. package/dist/components/PlDataTable/table.js +171 -192
  94. package/dist/components/PlDataTable/table.js.map +1 -1
  95. package/dist/components/PlDataTable/v4.d.ts +84 -119
  96. package/dist/components/PlDataTable/v5.d.ts +80 -103
  97. package/dist/components/PlMultiSequenceAlignment.cjs +27 -29
  98. package/dist/components/PlMultiSequenceAlignment.cjs.map +1 -1
  99. package/dist/components/PlMultiSequenceAlignment.d.ts +36 -27
  100. package/dist/components/PlMultiSequenceAlignment.js +26 -27
  101. package/dist/components/PlMultiSequenceAlignment.js.map +1 -1
  102. package/dist/components/PlSelectionModel.cjs +7 -6
  103. package/dist/components/PlSelectionModel.cjs.map +1 -1
  104. package/dist/components/PlSelectionModel.d.ts +10 -8
  105. package/dist/components/PlSelectionModel.js +7 -5
  106. package/dist/components/PlSelectionModel.js.map +1 -1
  107. package/dist/components/index.cjs +8 -0
  108. package/dist/components/index.d.ts +11 -6
  109. package/dist/components/index.js +8 -0
  110. package/dist/config/actions.cjs +138 -171
  111. package/dist/config/actions.cjs.map +1 -1
  112. package/dist/config/actions.d.ts +47 -47
  113. package/dist/config/actions.js +146 -178
  114. package/dist/config/actions.js.map +1 -1
  115. package/dist/config/actions_kinds.d.ts +114 -121
  116. package/dist/config/index.cjs +1 -0
  117. package/dist/config/index.d.ts +6 -7
  118. package/dist/config/index.js +1 -0
  119. package/dist/config/model.d.ts +131 -127
  120. package/dist/config/model_meta.d.ts +4 -1
  121. package/dist/config/type_engine.d.ts +22 -21
  122. package/dist/config/type_util.d.ts +12 -10
  123. package/dist/env_value.cjs +5 -6
  124. package/dist/env_value.cjs.map +1 -1
  125. package/dist/env_value.d.ts +4 -1
  126. package/dist/env_value.js +5 -5
  127. package/dist/env_value.js.map +1 -1
  128. package/dist/filters/converters/filterToQuery.cjs +273 -239
  129. package/dist/filters/converters/filterToQuery.cjs.map +1 -1
  130. package/dist/filters/converters/filterToQuery.d.ts +6 -2
  131. package/dist/filters/converters/filterToQuery.js +272 -237
  132. package/dist/filters/converters/filterToQuery.js.map +1 -1
  133. package/dist/filters/converters/filterUiToExpressionImpl.cjs +56 -85
  134. package/dist/filters/converters/filterUiToExpressionImpl.cjs.map +1 -1
  135. package/dist/filters/converters/filterUiToExpressionImpl.d.ts +8 -4
  136. package/dist/filters/converters/filterUiToExpressionImpl.js +55 -83
  137. package/dist/filters/converters/filterUiToExpressionImpl.js.map +1 -1
  138. package/dist/filters/converters/index.cjs +2 -0
  139. package/dist/filters/converters/index.d.ts +2 -3
  140. package/dist/filters/converters/index.js +2 -0
  141. package/dist/filters/distill.cjs +59 -60
  142. package/dist/filters/distill.cjs.map +1 -1
  143. package/dist/filters/distill.d.ts +6 -3
  144. package/dist/filters/distill.js +58 -58
  145. package/dist/filters/distill.js.map +1 -1
  146. package/dist/filters/index.cjs +4 -0
  147. package/dist/filters/index.d.ts +5 -4
  148. package/dist/filters/index.js +4 -0
  149. package/dist/filters/traverse.cjs +31 -40
  150. package/dist/filters/traverse.cjs.map +1 -1
  151. package/dist/filters/traverse.js +31 -39
  152. package/dist/filters/traverse.js.map +1 -1
  153. package/dist/filters/types.d.ts +10 -7
  154. package/dist/index.cjs +193 -187
  155. package/dist/index.d.ts +61 -28
  156. package/dist/index.js +49 -41
  157. package/dist/internal.cjs +48 -62
  158. package/dist/internal.cjs.map +1 -1
  159. package/dist/internal.js +49 -60
  160. package/dist/internal.js.map +1 -1
  161. package/dist/package.cjs +12 -0
  162. package/dist/package.cjs.map +1 -0
  163. package/dist/package.js +6 -0
  164. package/dist/package.js.map +1 -0
  165. package/dist/pframe.cjs +32 -37
  166. package/dist/pframe.cjs.map +1 -1
  167. package/dist/pframe.d.ts +14 -10
  168. package/dist/pframe.js +32 -35
  169. package/dist/pframe.js.map +1 -1
  170. package/dist/pframe_utils/axes.cjs +81 -114
  171. package/dist/pframe_utils/axes.cjs.map +1 -1
  172. package/dist/pframe_utils/axes.d.ts +10 -13
  173. package/dist/pframe_utils/axes.js +80 -112
  174. package/dist/pframe_utils/axes.js.map +1 -1
  175. package/dist/pframe_utils/columns.cjs +61 -81
  176. package/dist/pframe_utils/columns.cjs.map +1 -1
  177. package/dist/pframe_utils/columns.d.ts +15 -6
  178. package/dist/pframe_utils/columns.js +60 -79
  179. package/dist/pframe_utils/columns.js.map +1 -1
  180. package/dist/pframe_utils/index.cjs +232 -253
  181. package/dist/pframe_utils/index.cjs.map +1 -1
  182. package/dist/pframe_utils/index.d.ts +39 -35
  183. package/dist/pframe_utils/index.js +231 -251
  184. package/dist/pframe_utils/index.js.map +1 -1
  185. package/dist/platforma.d.ts +48 -46
  186. package/dist/plugin_handle.cjs +6 -14
  187. package/dist/plugin_handle.cjs.map +1 -1
  188. package/dist/plugin_handle.d.ts +20 -25
  189. package/dist/plugin_handle.js +6 -13
  190. package/dist/plugin_handle.js.map +1 -1
  191. package/dist/plugin_model.cjs +364 -163
  192. package/dist/plugin_model.cjs.map +1 -1
  193. package/dist/plugin_model.d.ts +280 -129
  194. package/dist/plugin_model.js +362 -163
  195. package/dist/plugin_model.js.map +1 -1
  196. package/dist/raw_globals.cjs +10 -22
  197. package/dist/raw_globals.cjs.map +1 -1
  198. package/dist/raw_globals.d.ts +8 -5
  199. package/dist/raw_globals.js +10 -20
  200. package/dist/raw_globals.js.map +1 -1
  201. package/dist/ref_util.cjs +14 -13
  202. package/dist/ref_util.cjs.map +1 -1
  203. package/dist/ref_util.d.ts +18 -12
  204. package/dist/ref_util.js +14 -11
  205. package/dist/ref_util.js.map +1 -1
  206. package/dist/render/accessor.cjs +213 -226
  207. package/dist/render/accessor.cjs.map +1 -1
  208. package/dist/render/accessor.d.ts +115 -120
  209. package/dist/render/accessor.js +212 -224
  210. package/dist/render/accessor.js.map +1 -1
  211. package/dist/render/api.cjs +478 -580
  212. package/dist/render/api.cjs.map +1 -1
  213. package/dist/render/api.d.ts +207 -209
  214. package/dist/render/api.js +476 -578
  215. package/dist/render/api.js.map +1 -1
  216. package/dist/render/future.cjs +28 -32
  217. package/dist/render/future.cjs.map +1 -1
  218. package/dist/render/future.d.ts +15 -11
  219. package/dist/render/future.js +28 -30
  220. package/dist/render/future.js.map +1 -1
  221. package/dist/render/index.cjs +8 -0
  222. package/dist/render/index.d.ts +10 -8
  223. package/dist/render/index.js +8 -0
  224. package/dist/render/internal.cjs +33 -29
  225. package/dist/render/internal.cjs.map +1 -1
  226. package/dist/render/internal.d.ts +78 -72
  227. package/dist/render/internal.js +29 -26
  228. package/dist/render/internal.js.map +1 -1
  229. package/dist/render/traversal_ops.d.ts +42 -43
  230. package/dist/render/util/axis_filtering.cjs +63 -86
  231. package/dist/render/util/axis_filtering.cjs.map +1 -1
  232. package/dist/render/util/axis_filtering.d.ts +10 -7
  233. package/dist/render/util/axis_filtering.js +63 -85
  234. package/dist/render/util/axis_filtering.js.map +1 -1
  235. package/dist/render/util/column_collection.cjs +266 -321
  236. package/dist/render/util/column_collection.cjs.map +1 -1
  237. package/dist/render/util/column_collection.d.ts +47 -47
  238. package/dist/render/util/column_collection.js +264 -319
  239. package/dist/render/util/column_collection.js.map +1 -1
  240. package/dist/render/util/index.cjs +4 -0
  241. package/dist/render/util/index.d.ts +4 -5
  242. package/dist/render/util/index.js +4 -0
  243. package/dist/render/util/label.cjs +129 -163
  244. package/dist/render/util/label.cjs.map +1 -1
  245. package/dist/render/util/label.d.ts +45 -46
  246. package/dist/render/util/label.js +128 -161
  247. package/dist/render/util/label.js.map +1 -1
  248. package/dist/render/util/pcolumn_data.cjs +315 -375
  249. package/dist/render/util/pcolumn_data.cjs.map +1 -1
  250. package/dist/render/util/pcolumn_data.d.ts +33 -32
  251. package/dist/render/util/pcolumn_data.js +314 -373
  252. package/dist/render/util/pcolumn_data.js.map +1 -1
  253. package/dist/render/util/pframe_upgraders.cjs +37 -42
  254. package/dist/render/util/pframe_upgraders.cjs.map +1 -1
  255. package/dist/render/util/pframe_upgraders.js +37 -41
  256. package/dist/render/util/pframe_upgraders.js.map +1 -1
  257. package/dist/render/util/split_selectors.d.ts +13 -9
  258. package/dist/version.cjs +6 -8
  259. package/dist/version.cjs.map +1 -1
  260. package/dist/version.d.ts +7 -4
  261. package/dist/version.js +5 -5
  262. package/dist/version.js.map +1 -1
  263. package/package.json +15 -9
  264. package/src/block_migrations.test.ts +184 -14
  265. package/src/block_migrations.ts +185 -30
  266. package/src/block_model.ts +111 -66
  267. package/src/block_storage.test.ts +147 -3
  268. package/src/block_storage.ts +43 -8
  269. package/src/block_storage_callbacks.ts +9 -4
  270. package/src/env_value.ts +0 -2
  271. package/src/index.ts +12 -11
  272. package/src/internal.ts +0 -1
  273. package/src/platforma.ts +4 -4
  274. package/src/plugin_handle.ts +1 -1
  275. package/src/plugin_model.test.ts +217 -21
  276. package/src/plugin_model.ts +450 -55
  277. package/src/raw_globals.ts +0 -1
  278. package/dist/annotations/converter.d.ts.map +0 -1
  279. package/dist/annotations/index.d.ts.map +0 -1
  280. package/dist/annotations/types.d.ts.map +0 -1
  281. package/dist/bconfig/index.d.ts.map +0 -1
  282. package/dist/bconfig/lambdas.d.ts.map +0 -1
  283. package/dist/bconfig/normalization.d.ts.map +0 -1
  284. package/dist/bconfig/types.d.ts.map +0 -1
  285. package/dist/bconfig/util.d.ts.map +0 -1
  286. package/dist/bconfig/v3.d.ts.map +0 -1
  287. package/dist/block_api_v1.d.ts.map +0 -1
  288. package/dist/block_api_v2.d.ts.map +0 -1
  289. package/dist/block_api_v3.d.ts.map +0 -1
  290. package/dist/block_migrations.d.ts.map +0 -1
  291. package/dist/block_model.d.ts.map +0 -1
  292. package/dist/block_model_legacy.d.ts.map +0 -1
  293. package/dist/block_state_patch.d.ts.map +0 -1
  294. package/dist/block_state_util.d.ts.map +0 -1
  295. package/dist/block_storage.d.ts.map +0 -1
  296. package/dist/block_storage_callbacks.d.ts +0 -115
  297. package/dist/block_storage_callbacks.d.ts.map +0 -1
  298. package/dist/block_storage_facade.d.ts.map +0 -1
  299. package/dist/components/PFrameForGraphs.d.ts.map +0 -1
  300. package/dist/components/PlAnnotations/filter.d.ts.map +0 -1
  301. package/dist/components/PlAnnotations/filters_ui.d.ts.map +0 -1
  302. package/dist/components/PlAnnotations/index.d.ts.map +0 -1
  303. package/dist/components/PlAnnotations/types.d.ts +0 -3
  304. package/dist/components/PlAnnotations/types.d.ts.map +0 -1
  305. package/dist/components/PlDataTable/index.d.ts.map +0 -1
  306. package/dist/components/PlDataTable/labels.d.ts +0 -7
  307. package/dist/components/PlDataTable/labels.d.ts.map +0 -1
  308. package/dist/components/PlDataTable/state-migration.d.ts.map +0 -1
  309. package/dist/components/PlDataTable/table.d.ts.map +0 -1
  310. package/dist/components/PlDataTable/v4.d.ts.map +0 -1
  311. package/dist/components/PlDataTable/v5.d.ts.map +0 -1
  312. package/dist/components/PlMultiSequenceAlignment.d.ts.map +0 -1
  313. package/dist/components/PlSelectionModel.d.ts.map +0 -1
  314. package/dist/components/index.d.ts.map +0 -1
  315. package/dist/config/actions.d.ts.map +0 -1
  316. package/dist/config/actions_kinds.d.ts.map +0 -1
  317. package/dist/config/index.d.ts.map +0 -1
  318. package/dist/config/model.d.ts.map +0 -1
  319. package/dist/config/model_meta.d.ts.map +0 -1
  320. package/dist/config/type_engine.d.ts.map +0 -1
  321. package/dist/config/type_util.d.ts.map +0 -1
  322. package/dist/env_value.d.ts.map +0 -1
  323. package/dist/filters/converters/filterToQuery.d.ts.map +0 -1
  324. package/dist/filters/converters/filterUiToExpressionImpl.d.ts.map +0 -1
  325. package/dist/filters/converters/index.d.ts.map +0 -1
  326. package/dist/filters/distill.d.ts.map +0 -1
  327. package/dist/filters/index.d.ts.map +0 -1
  328. package/dist/filters/traverse.d.ts +0 -27
  329. package/dist/filters/traverse.d.ts.map +0 -1
  330. package/dist/filters/types.d.ts.map +0 -1
  331. package/dist/index.cjs.map +0 -1
  332. package/dist/index.d.ts.map +0 -1
  333. package/dist/index.js.map +0 -1
  334. package/dist/internal.d.ts +0 -36
  335. package/dist/internal.d.ts.map +0 -1
  336. package/dist/package.json.cjs +0 -6
  337. package/dist/package.json.cjs.map +0 -1
  338. package/dist/package.json.js +0 -4
  339. package/dist/package.json.js.map +0 -1
  340. package/dist/pframe.d.ts.map +0 -1
  341. package/dist/pframe_utils/axes.d.ts.map +0 -1
  342. package/dist/pframe_utils/columns.d.ts.map +0 -1
  343. package/dist/pframe_utils/index.d.ts.map +0 -1
  344. package/dist/pframe_utils/querySpec.d.ts +0 -2
  345. package/dist/pframe_utils/querySpec.d.ts.map +0 -1
  346. package/dist/platforma.d.ts.map +0 -1
  347. package/dist/plugin_handle.d.ts.map +0 -1
  348. package/dist/plugin_model.d.ts.map +0 -1
  349. package/dist/raw_globals.d.ts.map +0 -1
  350. package/dist/ref_util.d.ts.map +0 -1
  351. package/dist/render/accessor.d.ts.map +0 -1
  352. package/dist/render/api.d.ts.map +0 -1
  353. package/dist/render/future.d.ts.map +0 -1
  354. package/dist/render/index.d.ts.map +0 -1
  355. package/dist/render/internal.d.ts.map +0 -1
  356. package/dist/render/traversal_ops.d.ts.map +0 -1
  357. package/dist/render/util/axis_filtering.d.ts.map +0 -1
  358. package/dist/render/util/column_collection.d.ts.map +0 -1
  359. package/dist/render/util/index.d.ts.map +0 -1
  360. package/dist/render/util/label.d.ts.map +0 -1
  361. package/dist/render/util/pcolumn_data.d.ts.map +0 -1
  362. package/dist/render/util/pframe_upgraders.d.ts +0 -3
  363. package/dist/render/util/pframe_upgraders.d.ts.map +0 -1
  364. package/dist/render/util/split_selectors.d.ts.map +0 -1
  365. package/dist/version.d.ts.map +0 -1
@@ -12,6 +12,7 @@ import type { PlatformaV3 } from "./platforma";
12
12
  import type { InferRenderFunctionReturn, RenderFunction } from "./render";
13
13
  import { BlockRenderCtx, PluginRenderCtx } from "./render";
14
14
  import type { PluginData, PluginModel, PluginOutputs, PluginParams } from "./plugin_model";
15
+ import { PluginInstance as PluginInstanceClass, CREATE_PLUGIN_MODEL } from "./plugin_model";
15
16
  import { type PluginHandle, pluginOutputKey } from "./plugin_handle";
16
17
  import type { RenderCtxBase } from "./render";
17
18
  import { PlatformaSDKVersion } from "./version";
@@ -79,10 +80,10 @@ function mergeFeatureFlags(
79
80
  }
80
81
 
81
82
  /**
82
- * Registered plugin: model + param derivation lambdas.
83
+ * Plugin record: model + param derivation lambdas.
83
84
  * Type parameters are carried by PluginModel generic.
84
85
  */
85
- export type PluginInstance<
86
+ export type PluginRecord<
86
87
  Data extends PluginData = PluginData,
87
88
  Params extends PluginParams = undefined,
88
89
  Outputs extends PluginOutputs = PluginOutputs,
@@ -94,10 +95,11 @@ export type PluginInstance<
94
95
  interface BlockModelV3Config<
95
96
  OutputsCfg extends Record<string, ConfigRenderLambda>,
96
97
  Data,
97
- Plugins extends Record<string, PluginInstance> = {},
98
+ Plugins extends Record<string, PluginRecord> = {},
99
+ Transfers extends Record<string, unknown> = {},
98
100
  > {
99
101
  renderingMode: BlockRenderingMode;
100
- dataModel: DataModel<Data>;
102
+ dataModel: DataModel<Data, Transfers>;
101
103
  outputs: OutputsCfg;
102
104
  sections: ConfigRenderLambda;
103
105
  title: ConfigRenderLambda | undefined;
@@ -119,9 +121,12 @@ export class BlockModelV3<
119
121
  OutputsCfg extends Record<string, ConfigRenderLambda>,
120
122
  Data extends Record<string, unknown> = Record<string, unknown>,
121
123
  Href extends `/${string}` = "/",
122
- Plugins extends Record<string, PluginInstance> = {},
124
+ Plugins extends Record<string, PluginRecord> = {},
125
+ Transfers extends Record<string, unknown> = {},
123
126
  > {
124
- private constructor(private readonly config: BlockModelV3Config<OutputsCfg, Data, Plugins>) {}
127
+ private constructor(
128
+ private readonly config: BlockModelV3Config<OutputsCfg, Data, Plugins, Transfers>,
129
+ ) {}
125
130
 
126
131
  public static readonly INITIAL_BLOCK_FEATURE_FLAGS: BlockCodeKnownFeatureFlags = {
127
132
  supportsLazyState: true,
@@ -145,10 +150,11 @@ export class BlockModelV3<
145
150
  *
146
151
  * @param dataModel The data model that defines initial data and migrations
147
152
  */
148
- public static create<Data extends Record<string, unknown>>(
149
- dataModel: DataModel<Data>,
150
- ): BlockModelV3<NoOb, {}, Data> {
151
- return new BlockModelV3<NoOb, {}, Data>({
153
+ public static create<
154
+ Data extends Record<string, unknown>,
155
+ Transfers extends Record<string, unknown> = {},
156
+ >(dataModel: DataModel<Data, Transfers>): BlockModelV3<NoOb, {}, Data, "/", {}, Transfers> {
157
+ return new BlockModelV3<NoOb, {}, Data, "/", {}, Transfers>({
152
158
  renderingMode: "Heavy",
153
159
  dataModel,
154
160
  outputs: {},
@@ -185,7 +191,8 @@ export class BlockModelV3<
185
191
  },
186
192
  Data,
187
193
  Href,
188
- Plugins
194
+ Plugins,
195
+ Transfers
189
196
  >;
190
197
  /**
191
198
  * Add output cell to the configuration
@@ -206,13 +213,14 @@ export class BlockModelV3<
206
213
  },
207
214
  Data,
208
215
  Href,
209
- Plugins
216
+ Plugins,
217
+ Transfers
210
218
  >;
211
219
  public output(
212
220
  key: string,
213
221
  cfgOrRf: RenderFunction<Args, Data, unknown>,
214
222
  flags: ConfigRenderLambdaFlags = {},
215
- ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
223
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers> {
216
224
  return new BlockModelV3({
217
225
  ...this.config,
218
226
  outputs: {
@@ -240,7 +248,8 @@ export class BlockModelV3<
240
248
  },
241
249
  Data,
242
250
  Href,
243
- Plugins
251
+ Plugins,
252
+ Transfers
244
253
  > {
245
254
  return this.output(key, rf, { retentive: true });
246
255
  }
@@ -266,10 +275,10 @@ export class BlockModelV3<
266
275
  * return { numbers: data.numbers };
267
276
  * })
268
277
  */
269
- public args<Args>(
270
- lambda: (data: Data) => Args,
271
- ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
272
- return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
278
+ public args<A>(
279
+ lambda: (data: Data) => A,
280
+ ): BlockModelV3<A, OutputsCfg, Data, Href, Plugins, Transfers> {
281
+ return new BlockModelV3<A, OutputsCfg, Data, Href, Plugins, Transfers>({
273
282
  ...this.config,
274
283
  argsFunction: lambda as (data: unknown) => unknown,
275
284
  });
@@ -297,8 +306,8 @@ export class BlockModelV3<
297
306
  */
298
307
  public prerunArgs(
299
308
  fn: (data: Data) => unknown,
300
- ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
301
- return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
309
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers> {
310
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers>({
302
311
  ...this.config,
303
312
  prerunArgsFunction: fn as (data: unknown) => unknown,
304
313
  });
@@ -308,22 +317,24 @@ export class BlockModelV3<
308
317
  public sections<
309
318
  const Ret extends SectionsExpectedType,
310
319
  const RF extends RenderFunction<Args, Data, Ret>,
311
- >(rf: RF): BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>, Plugins> {
312
- return new BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>, Plugins>({
313
- ...this.config,
314
- // Replace the default sections callback with the user-provided one
315
- sections: createAndRegisterRenderLambda(
316
- { handle: "sections", lambda: () => rf(new BlockRenderCtx<Args, Data>()) },
317
- true,
318
- ),
319
- });
320
+ >(rf: RF): BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>, Plugins, Transfers> {
321
+ return new BlockModelV3<Args, OutputsCfg, Data, DeriveHref<ReturnType<RF>>, Plugins, Transfers>(
322
+ {
323
+ ...this.config,
324
+ // Replace the default sections callback with the user-provided one
325
+ sections: createAndRegisterRenderLambda(
326
+ { handle: "sections", lambda: () => rf(new BlockRenderCtx<Args, Data>()) },
327
+ true,
328
+ ),
329
+ },
330
+ );
320
331
  }
321
332
 
322
333
  /** Sets a rendering function to derive block title, shown for the block in the left blocks-overview panel. */
323
334
  public title(
324
335
  rf: RenderFunction<Args, Data, string>,
325
- ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
326
- return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
336
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers> {
337
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers>({
327
338
  ...this.config,
328
339
  title: createAndRegisterRenderLambda({
329
340
  handle: "title",
@@ -334,8 +345,8 @@ export class BlockModelV3<
334
345
 
335
346
  public subtitle(
336
347
  rf: RenderFunction<Args, Data, string>,
337
- ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
338
- return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
348
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers> {
349
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers>({
339
350
  ...this.config,
340
351
  subtitle: createAndRegisterRenderLambda({
341
352
  handle: "subtitle",
@@ -346,8 +357,8 @@ export class BlockModelV3<
346
357
 
347
358
  public tags(
348
359
  rf: RenderFunction<Args, Data, string[]>,
349
- ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
350
- return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
360
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers> {
361
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers>({
351
362
  ...this.config,
352
363
  tags: createAndRegisterRenderLambda({
353
364
  handle: "tags",
@@ -359,8 +370,8 @@ export class BlockModelV3<
359
370
  /** Sets or overrides feature flags for the block. */
360
371
  public withFeatureFlags(
361
372
  flags: Partial<BlockCodeKnownFeatureFlags>,
362
- ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
363
- return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
373
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers> {
374
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers>({
364
375
  ...this.config,
365
376
  featureFlags: { ...this.config.featureFlags, ...flags },
366
377
  });
@@ -372,8 +383,8 @@ export class BlockModelV3<
372
383
  */
373
384
  public enriches(
374
385
  lambda: (args: Args) => PlRef[],
375
- ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins> {
376
- return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins>({
386
+ ): BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers> {
387
+ return new BlockModelV3<Args, OutputsCfg, Data, Href, Plugins, Transfers>({
377
388
  ...this.config,
378
389
  enrichmentTargets: createAndRegisterRenderLambda({
379
390
  handle: "enrichmentTargets",
@@ -384,16 +395,18 @@ export class BlockModelV3<
384
395
 
385
396
  /**
386
397
  * Registers a plugin instance with the block.
398
+ * Consumes a transfer if one was defined for this plugin ID in the migration chain.
387
399
  *
388
- * Plugins are UI components with their own model logic and persistent state.
389
- * Each plugin must have a unique pluginId within the block.
400
+ * Type checks:
401
+ * - If Transfers[Id] exists, verifies it extends PTransferData (transfer type compatibility)
402
+ * - If no Transfers[Id], rejects plugins with transferAt set (missing .transfer() in data model)
403
+ * - Rejects duplicate plugin IDs (Id already in keyof Plugins)
390
404
  *
391
- * @param pluginId - Unique identifier for this plugin instance within the block
392
- * @param plugin - Configured PluginModel instance (created via factory.create(config))
405
+ * @param instance - PluginInstance created via factory.create({ pluginId, ... })
393
406
  * @param params - Per-property lambdas deriving plugin params from block RenderCtx
394
407
  *
395
408
  * @example
396
- * .plugin('mainTable', dataTablePlugin.create({ defaultOps: {...} }), {
409
+ * .plugin(mainTable, {
397
410
  * columns: (ctx) => ctx.outputs?.resolve("data")?.getPColumns(),
398
411
  * sourceId: (ctx) => ctx.data.selectedSource,
399
412
  * })
@@ -403,32 +416,61 @@ export class BlockModelV3<
403
416
  PData extends PluginData,
404
417
  PParams extends PluginParams,
405
418
  POutputs extends PluginOutputs,
419
+ PTransferData,
406
420
  >(
407
- pluginId: PluginId,
408
- plugin: PluginModel<PData, PParams, POutputs>,
421
+ instance: PluginInstanceClass<
422
+ PluginId &
423
+ (PluginId extends keyof Transfers
424
+ ? Transfers[PluginId] extends PTransferData
425
+ ? string
426
+ : never
427
+ : [PTransferData] extends [never]
428
+ ? string
429
+ : never) &
430
+ (PluginId extends keyof Plugins ? never : string),
431
+ PData,
432
+ PParams,
433
+ POutputs,
434
+ PTransferData
435
+ >,
409
436
  params?: ParamsInput<PParams, Args, Data>,
410
437
  ): BlockModelV3<
411
438
  Args,
412
439
  OutputsCfg,
413
440
  Data,
414
441
  Href,
415
- Plugins & { [K in PluginId]: PluginInstance<PData, PParams, POutputs> }
442
+ Plugins & { [K in PluginId]: PluginRecord<PData, PParams, POutputs> },
443
+ Omit<Transfers, PluginId>
444
+ >;
445
+ public plugin(
446
+ instance: PluginInstanceClass,
447
+ params?: ParamsInput<Record<string, unknown>, unknown, unknown>,
448
+ ): BlockModelV3<
449
+ Args,
450
+ OutputsCfg,
451
+ Data,
452
+ Href,
453
+ Record<string, PluginRecord>,
454
+ Record<string, unknown>
416
455
  > {
417
- // Validate pluginId uniqueness
456
+ const pluginId = instance.id;
457
+ const plugin = instance[CREATE_PLUGIN_MODEL]();
458
+ const resolvedParams = (params ?? {}) as ParamsInputErased;
459
+
418
460
  if (pluginId in this.config.plugins) {
419
461
  throw new Error(`Plugin '${pluginId}' already registered`);
420
462
  }
421
463
 
422
- const instance: PluginInstance<PData, PParams, POutputs> = {
464
+ const registered: PluginRecord = {
423
465
  model: plugin,
424
- inputs: (params ?? {}) as ParamsInputErased,
466
+ inputs: resolvedParams,
425
467
  };
426
468
 
427
469
  return new BlockModelV3({
428
470
  ...this.config,
429
471
  plugins: {
430
472
  ...this.config.plugins,
431
- [pluginId]: instance,
473
+ [pluginId]: registered,
432
474
  },
433
475
  featureFlags: mergeFeatureFlags(this.config.featureFlags, plugin.featureFlags ?? {}),
434
476
  });
@@ -436,16 +478,16 @@ export class BlockModelV3<
436
478
 
437
479
  /** Renders all provided block settings into a pre-configured platforma API
438
480
  * instance, that can be used in frontend to interact with block data, and
439
- * other features provided by the platforma to the block. */
440
- public done(): PlatformaExtended<
441
- PlatformaV3<Data, Args, InferOutputsFromLambdas<OutputsCfg>, Href, Plugins>
442
- > {
443
- return this.withFeatureFlags({
444
- ...this.config.featureFlags,
445
- })._done();
446
- }
447
-
448
- public _done(): PlatformaExtended<
481
+ * other features provided by the platforma to the block.
482
+ *
483
+ * Type-level check: if there are unconsumed transfers (from `.transfer()` calls
484
+ * in the migration chain), this method requires an impossible `never` argument,
485
+ * producing a compile error. Register all transferred plugins via `.plugin(instance)`
486
+ * before calling `.done()`.
487
+ */
488
+ public done(
489
+ ..._: keyof Transfers extends never ? [] : [never]
490
+ ): PlatformaExtended<
449
491
  PlatformaV3<Data, Args, InferOutputsFromLambdas<OutputsCfg>, Href, Plugins>
450
492
  > {
451
493
  if (this.config.argsFunction === undefined) throw new Error("Args rendering function not set.");
@@ -462,7 +504,7 @@ export class BlockModelV3<
462
504
 
463
505
  const { dataModel, argsFunction, prerunArgsFunction } = this.config;
464
506
 
465
- function getPlugin(handle: PluginHandle): PluginInstance {
507
+ function getPlugin(handle: PluginHandle): PluginRecord {
466
508
  const plugin = plugins[handle];
467
509
  if (!plugin) throw new Error(`Plugin model not found for '${handle}'`);
468
510
  return plugin;
@@ -477,13 +519,16 @@ export class BlockModelV3<
477
519
  migrateBlockData: (v) => dataModel.migrate(v),
478
520
  getPluginRegistry: () => pluginRegistry,
479
521
  migratePluginData: (handle, v) => getPlugin(handle).model.dataModel.migrate(v),
480
- createPluginData: (handle) => getPlugin(handle).model.dataModel.getDefaultData(),
522
+ createPluginData: (handle, transfer) => {
523
+ if (transfer) return transfer;
524
+ return getPlugin(handle).model.getDefaultData();
525
+ },
481
526
  }),
482
527
  [BlockStorageFacadeCallbacks.StorageInitial]: () =>
483
528
  createInitialStorage({
484
529
  getDefaultBlockData: () => dataModel.getDefaultData(),
485
530
  getPluginRegistry: () => pluginRegistry,
486
- createPluginData: (handle) => getPlugin(handle).model.dataModel.getDefaultData(),
531
+ createPluginData: (handle) => getPlugin(handle).model.getDefaultData(),
487
532
  }),
488
533
  [BlockStorageFacadeCallbacks.ArgsDerive]: (storageJson) =>
489
534
  deriveArgsFromStorage(storageJson, argsFunction),
@@ -600,7 +645,7 @@ type _CreateIsBlockModelV3 =
600
645
  _CreateResult extends BlockModelV3<infer _A, infer _O, infer _S> ? true : false;
601
646
  type _CreateTest = Expect<_CreateIsBlockModelV3>;
602
647
 
603
- // Test: BlockModelV3Config interface structure
648
+ // Test: BlockModelV3Config interface structure (default generics)
604
649
  type _ConfigTest = Expect<
605
650
  Equal<
606
651
  BlockModelV3Config<_TestOutputs, _TestData>,
@@ -608,7 +653,7 @@ type _ConfigTest = Expect<
608
653
  renderingMode: BlockRenderingMode;
609
654
  argsFunction: ((data: unknown) => unknown) | undefined;
610
655
  prerunArgsFunction: ((data: unknown) => unknown) | undefined;
611
- dataModel: DataModel<_TestData>;
656
+ dataModel: DataModel<_TestData, {}>;
612
657
  outputs: _TestOutputs;
613
658
  sections: ConfigRenderLambda;
614
659
  title: ConfigRenderLambda | undefined;
@@ -14,6 +14,7 @@ import {
14
14
  type PluginName,
15
15
  type PluginRegistry,
16
16
  } from "./block_storage";
17
+ import { DataUnrecoverableError } from "./block_migrations";
17
18
  import type { PluginHandle } from "./plugin_handle";
18
19
 
19
20
  describe("BlockStorage", () => {
@@ -284,7 +285,7 @@ describe("BlockStorage", () => {
284
285
  const result = migrateBlockStorage(storage, {
285
286
  migrateBlockData: (versioned) => {
286
287
  const d = versioned.data as { count: number };
287
- return { data: { count: d.count + 1 }, version: "v2" };
288
+ return { data: { count: d.count + 1 }, version: "v2", transfers: {} };
288
289
  },
289
290
  migratePluginData: (_pluginId, _versioned) => ({
290
291
  version: "v2",
@@ -338,6 +339,7 @@ describe("BlockStorage", () => {
338
339
  migrateBlockData: (versioned) => ({
339
340
  data: versioned.data as { count: number },
340
341
  version: "v2",
342
+ transfers: {},
341
343
  }),
342
344
  migratePluginData: (pluginId) => {
343
345
  throw new Error(`Plugin ${pluginId} migration failed`);
@@ -353,7 +355,7 @@ describe("BlockStorage", () => {
353
355
  }
354
356
  });
355
357
 
356
- it("should reset plugin data when plugin type changes", () => {
358
+ it("should pass DATA_MODEL_LEGACY_VERSION when plugin type changes (upgradeLegacy handles it)", () => {
357
359
  const storage = createTestStorage();
358
360
  const newRegistry: PluginRegistry = { plugin1: "typeB" as PluginName }; // Different type
359
361
 
@@ -361,9 +363,43 @@ describe("BlockStorage", () => {
361
363
  migrateBlockData: (versioned) => ({
362
364
  data: versioned.data as { count: number },
363
365
  version: "v2",
366
+ transfers: {},
367
+ }),
368
+ migratePluginData: (_handle, versioned) => {
369
+ // Version should be reset to DATA_MODEL_LEGACY_VERSION
370
+ expect(versioned.version).toBe(DATA_MODEL_LEGACY_VERSION);
371
+ return {
372
+ version: "upgraded",
373
+ data: { upgraded: true, from: versioned.data },
374
+ };
375
+ },
376
+ newPluginRegistry: newRegistry,
377
+ createPluginData: () => {
378
+ throw new Error("Should not be called when upgradeLegacy succeeds");
379
+ },
380
+ });
381
+
382
+ expect(result.success).toBe(true);
383
+ if (result.success) {
384
+ expect(result.storage.__plugins?.plugin1).toEqual({
385
+ __dataVersion: "upgraded",
386
+ __data: { upgraded: true, from: { value: "old" } },
387
+ });
388
+ }
389
+ });
390
+
391
+ it("should fall back to createPluginData when plugin type changes and no upgradeLegacy", () => {
392
+ const storage = createTestStorage();
393
+ const newRegistry: PluginRegistry = { plugin1: "typeB" as PluginName }; // Different type
394
+
395
+ const result = migrateBlockStorage(storage, {
396
+ migrateBlockData: (versioned) => ({
397
+ data: versioned.data as { count: number },
398
+ version: "v2",
399
+ transfers: {},
364
400
  }),
365
401
  migratePluginData: () => {
366
- throw new Error("Should not be called for type change");
402
+ throw new DataUnrecoverableError(DATA_MODEL_LEGACY_VERSION);
367
403
  },
368
404
  newPluginRegistry: newRegistry,
369
405
  createPluginData: (pluginId) => ({
@@ -389,6 +425,7 @@ describe("BlockStorage", () => {
389
425
  migrateBlockData: (versioned) => ({
390
426
  data: versioned.data as { count: number },
391
427
  version: "v2",
428
+ transfers: {},
392
429
  }),
393
430
  migratePluginData: () => {
394
431
  throw new Error("Should not be called for new plugin");
@@ -417,6 +454,7 @@ describe("BlockStorage", () => {
417
454
  migrateBlockData: (versioned) => ({
418
455
  data: versioned.data as { count: number },
419
456
  version: "v2",
457
+ transfers: {},
420
458
  }),
421
459
  migratePluginData: () => {
422
460
  throw new Error("Should not be called for dropped plugin");
@@ -442,6 +480,7 @@ describe("BlockStorage", () => {
442
480
  migrateBlockData: (versioned) => ({
443
481
  data: versioned.data as { count: number },
444
482
  version: "v2",
483
+ transfers: {},
445
484
  }),
446
485
  migratePluginData: () => undefined, // Remove plugin
447
486
  newPluginRegistry: newRegistry,
@@ -453,5 +492,110 @@ describe("BlockStorage", () => {
453
492
  expect(result.storage.__plugins).toEqual({});
454
493
  }
455
494
  });
495
+
496
+ it("should pass transfer data to createPluginData for new plugins", () => {
497
+ const storage = createBlockStorage({ count: 1, tableState: "expanded" }, "v1");
498
+ const newRegistry: PluginRegistry = { table1: "dataTable" as PluginName };
499
+
500
+ const result = migrateBlockStorage(storage, {
501
+ migrateBlockData: (versioned) => ({
502
+ data: { count: (versioned.data as any).count },
503
+ version: "v2",
504
+ transfers: {
505
+ table1: { version: "__legacy__", data: { state: (versioned.data as any).tableState } },
506
+ },
507
+ }),
508
+ migratePluginData: () => {
509
+ throw new Error("Should not be called for new plugin");
510
+ },
511
+ newPluginRegistry: newRegistry,
512
+ createPluginData: (_handle, transfer) => {
513
+ // Transfer should be passed through
514
+ if (transfer) {
515
+ return { version: "v1", data: { upgraded: true, fromState: transfer.data } };
516
+ }
517
+ return { version: "v1", data: { upgraded: false } };
518
+ },
519
+ });
520
+
521
+ expect(result.success).toBe(true);
522
+ if (result.success) {
523
+ expect(result.storage.__plugins?.table1).toEqual({
524
+ __dataVersion: "v1",
525
+ __data: { upgraded: true, fromState: { state: "expanded" } },
526
+ });
527
+ }
528
+ });
529
+
530
+ it("should not pass transfer to existing plugin with same type (uses migration instead)", () => {
531
+ const storage = createTestStorage(); // has plugin1 of typeA
532
+ const newRegistry: PluginRegistry = { plugin1: "typeA" as PluginName };
533
+
534
+ const result = migrateBlockStorage(storage, {
535
+ migrateBlockData: (versioned) => ({
536
+ data: versioned.data,
537
+ version: "v2",
538
+ transfers: {
539
+ plugin1: { version: "__legacy__", data: { should: "be ignored" } },
540
+ },
541
+ }),
542
+ migratePluginData: (_handle, versioned) => ({
543
+ version: "v2",
544
+ data: { migrated: true, from: versioned.data },
545
+ }),
546
+ newPluginRegistry: newRegistry,
547
+ createPluginData: () => {
548
+ throw new Error("Should not be called — existing plugin migrates");
549
+ },
550
+ });
551
+
552
+ expect(result.success).toBe(true);
553
+ if (result.success) {
554
+ expect(result.storage.__plugins?.plugin1).toEqual({
555
+ __dataVersion: "v2",
556
+ __data: { migrated: true, from: { value: "old" } },
557
+ });
558
+ }
559
+ });
560
+
561
+ it("should handle multiple plugins: one transferred, one fresh", () => {
562
+ const storage = createBlockStorage({ count: 1 }, "v1");
563
+ const newRegistry: PluginRegistry = {
564
+ transferred: "typeT" as PluginName,
565
+ fresh: "typeF" as PluginName,
566
+ };
567
+
568
+ const result = migrateBlockStorage(storage, {
569
+ migrateBlockData: (versioned) => ({
570
+ data: versioned.data,
571
+ version: "v2",
572
+ transfers: {
573
+ transferred: { version: "__legacy__", data: { legacy: true } },
574
+ },
575
+ }),
576
+ migratePluginData: () => {
577
+ throw new Error("No existing plugins to migrate");
578
+ },
579
+ newPluginRegistry: newRegistry,
580
+ createPluginData: (_handle, transfer) => {
581
+ if (transfer) {
582
+ return { version: "v1", data: { fromTransfer: transfer.data } };
583
+ }
584
+ return { version: "v1", data: { default: true } };
585
+ },
586
+ });
587
+
588
+ expect(result.success).toBe(true);
589
+ if (result.success) {
590
+ expect(result.storage.__plugins?.transferred).toEqual({
591
+ __dataVersion: "v1",
592
+ __data: { fromTransfer: { legacy: true } },
593
+ });
594
+ expect(result.storage.__plugins?.fresh).toEqual({
595
+ __dataVersion: "v1",
596
+ __data: { default: true },
597
+ });
598
+ }
599
+ });
456
600
  });
457
601
  });
@@ -10,7 +10,11 @@
10
10
  */
11
11
 
12
12
  import type { Branded } from "@milaboratories/pl-model-common";
13
- import type { DataVersioned } from "./block_migrations";
13
+ import {
14
+ type DataVersioned,
15
+ type TransferRecord,
16
+ isDataUnrecoverableError,
17
+ } from "./block_migrations";
14
18
  import type { PluginHandle, PluginFactoryLike, InferFactoryData } from "./plugin_handle";
15
19
 
16
20
  // =============================================================================
@@ -258,8 +262,10 @@ export type MigrationResult<TState> = MigrationSuccess<TState> | MigrationFailur
258
262
  * Conversion to internal VersionedData format is handled by migrateBlockStorage().
259
263
  */
260
264
  export interface MigrateBlockStorageConfig {
261
- /** Migrate block data from any version to latest. Throws on failure. */
262
- migrateBlockData: (versioned: DataVersioned<unknown>) => DataVersioned<unknown>;
265
+ /** Migrate block data from any version to latest. Returns migrated data and transfers. */
266
+ migrateBlockData: (versioned: DataVersioned<unknown>) => DataVersioned<unknown> & {
267
+ transfers: TransferRecord;
268
+ };
263
269
  /** Migrate each plugin's data. Return undefined to remove the plugin. Throws on failure. */
264
270
  migratePluginData: (
265
271
  handle: PluginHandle,
@@ -267,8 +273,12 @@ export interface MigrateBlockStorageConfig {
267
273
  ) => DataVersioned<unknown> | undefined;
268
274
  /** The new plugin registry after migration (pluginId -> pluginName) */
269
275
  newPluginRegistry: PluginRegistry;
270
- /** Factory to create initial data for new plugins */
271
- createPluginData: (handle: PluginHandle) => DataVersioned<unknown>;
276
+ /** Factory to create initial data for new plugins. Transfer is provided when a
277
+ * .transfer() was defined for this plugin in the block's migration chain. */
278
+ createPluginData: (
279
+ handle: PluginHandle,
280
+ transfer?: DataVersioned<unknown>,
281
+ ) => DataVersioned<unknown>;
272
282
  }
273
283
 
274
284
  /**
@@ -317,13 +327,15 @@ export function migrateBlockStorage(
317
327
  ): MigrationResult<unknown> {
318
328
  const { migrateBlockData, migratePluginData, newPluginRegistry, createPluginData } = config;
319
329
 
320
- // Step 1: Migrate block data
330
+ // Step 1: Migrate block data and collect transfers
321
331
  let migratedData: unknown;
322
332
  let newVersion: string;
333
+ let transfers: TransferRecord;
323
334
  try {
324
335
  const result = migrateBlockData({ version: storage.__dataVersion, data: storage.__data });
325
336
  migratedData = result.data;
326
337
  newVersion = result.version;
338
+ transfers = result.transfers;
327
339
  } catch (error) {
328
340
  return {
329
341
  success: false,
@@ -353,9 +365,32 @@ export function migrateBlockStorage(
353
365
  newPlugins[handle] = { __dataVersion: migrated.version, __data: migrated.data };
354
366
  }
355
367
  // If undefined returned, plugin is intentionally removed
368
+ } else if (existingEntry) {
369
+ // Plugin type changed — pass old data with DATA_MODEL_LEGACY_VERSION.
370
+ // If the new plugin has upgradeLegacy(), it migrates the old data.
371
+ // If not, defaultRecover throws DataUnrecoverableError → fall back to init.
372
+ let recovered = false;
373
+ try {
374
+ const migrated = migratePluginData(handle, {
375
+ version: DATA_MODEL_LEGACY_VERSION,
376
+ data: existingEntry.__data,
377
+ });
378
+ if (migrated) {
379
+ newPlugins[handle] = { __dataVersion: migrated.version, __data: migrated.data };
380
+ recovered = true;
381
+ }
382
+ } catch (recoverError) {
383
+ if (!isDataUnrecoverableError(recoverError)) throw recoverError;
384
+ }
385
+ if (!recovered) {
386
+ const transfer = transfers[handle];
387
+ const initial = createPluginData(handle, transfer);
388
+ newPlugins[handle] = { __dataVersion: initial.version, __data: initial.data };
389
+ }
356
390
  } else {
357
- // New plugin or type changed - create with initial data
358
- const initial = createPluginData(handle);
391
+ // New plugin - create with initial data, passing transfer if available
392
+ const transfer = transfers[handle];
393
+ const initial = createPluginData(handle, transfer);
359
394
  newPlugins[handle] = { __dataVersion: initial.version, __data: initial.data };
360
395
  }
361
396
  } catch (error) {