@platforma-sdk/model 1.81.0 → 1.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/block_migrations.cjs +92 -8
  2. package/dist/block_migrations.cjs.map +1 -1
  3. package/dist/block_migrations.d.ts +121 -32
  4. package/dist/block_migrations.d.ts.map +1 -1
  5. package/dist/block_migrations.js +92 -8
  6. package/dist/block_migrations.js.map +1 -1
  7. package/dist/block_model.cjs +69 -15
  8. package/dist/block_model.cjs.map +1 -1
  9. package/dist/block_model.d.ts +58 -22
  10. package/dist/block_model.d.ts.map +1 -1
  11. package/dist/block_model.js +71 -17
  12. package/dist/block_model.js.map +1 -1
  13. package/dist/block_storage_callbacks.cjs +194 -13
  14. package/dist/block_storage_callbacks.cjs.map +1 -1
  15. package/dist/block_storage_callbacks.js +192 -15
  16. package/dist/block_storage_callbacks.js.map +1 -1
  17. package/dist/block_storage_facade.cjs +4 -1
  18. package/dist/block_storage_facade.cjs.map +1 -1
  19. package/dist/block_storage_facade.d.ts +102 -0
  20. package/dist/block_storage_facade.d.ts.map +1 -1
  21. package/dist/block_storage_facade.js +4 -1
  22. package/dist/block_storage_facade.js.map +1 -1
  23. package/dist/components/PlDataTable/createPlDataTable/utils.cjs +1 -1
  24. package/dist/components/PlDataTable/createPlDataTable/utils.cjs.map +1 -1
  25. package/dist/components/PlDataTable/createPlDataTable/utils.js +1 -1
  26. package/dist/components/PlDataTable/createPlDataTable/utils.js.map +1 -1
  27. package/dist/package.cjs +1 -1
  28. package/dist/package.js +1 -1
  29. package/package.json +9 -8
  30. package/src/block_migrations.ts +205 -55
  31. package/src/block_model.ts +190 -59
  32. package/src/block_storage_callbacks.ts +294 -15
  33. package/src/block_storage_facade.ts +95 -0
  34. package/src/components/PlDataTable/createPlDataTable/utils.test.ts +31 -0
  35. package/src/components/PlDataTable/createPlDataTable/utils.ts +4 -1
  36. package/src/kind_reference.test.ts +134 -0
  37. package/src/template_init.test.ts +413 -0
  38. package/src/template_params.test.ts +135 -0
@@ -1,4 +1,5 @@
1
1
  const require_block_storage = require("./block_storage.cjs");
2
+ let _milaboratories_pl_model_common = require("@milaboratories/pl-model-common");
2
3
  //#region src/block_migrations.ts
3
4
  /** Create a DataVersioned wrapper with correct shape */
4
5
  function makeVersionedData(version, data) {
@@ -47,10 +48,16 @@ var MigrationChainBase = class {
47
48
  versionChain;
48
49
  migrationSteps;
49
50
  transferSteps;
51
+ /** Kind reference seeded by the builder, threaded through the chain into init(). */
52
+ kindRef;
53
+ /** The kind's runtime params check, carried for `init()` to hand to the DataModel. */
54
+ parseInitializationParams;
50
55
  constructor(state) {
51
56
  this.versionChain = state.versionChain;
52
57
  this.migrationSteps = state.steps;
53
58
  this.transferSteps = state.transferSteps ?? [];
59
+ this.kindRef = state.kindRef;
60
+ this.parseInitializationParams = state.parseInitializationParams;
54
61
  }
55
62
  /** Appends a migration step and returns the new versionChain and steps arrays. */
56
63
  buildStep(nextVersion, fn) {
@@ -92,6 +99,8 @@ var MigrationChainBase = class {
92
99
  steps: this.migrationSteps,
93
100
  transferSteps: this.transferSteps,
94
101
  initialDataFn: initialData,
102
+ kindRef: this.kindRef,
103
+ parseInitializationParams: this.parseInitializationParams,
95
104
  ...this.recoverState()
96
105
  });
97
106
  }
@@ -130,6 +139,8 @@ var DataModelMigrationChainWithRecover = class DataModelMigrationChainWithRecove
130
139
  versionChain,
131
140
  steps,
132
141
  transferSteps: this.transferSteps,
142
+ kindRef: this.kindRef,
143
+ parseInitializationParams: this.parseInitializationParams,
133
144
  recoverFn: this.recoverFn,
134
145
  recoverFromIndex: this.recoverFromIndex
135
146
  });
@@ -145,6 +156,8 @@ var DataModelMigrationChainWithRecover = class DataModelMigrationChainWithRecove
145
156
  versionChain: this.versionChain,
146
157
  steps: this.migrationSteps,
147
158
  transferSteps,
159
+ kindRef: this.kindRef,
160
+ parseInitializationParams: this.parseInitializationParams,
148
161
  recoverFn: this.recoverFn,
149
162
  recoverFromIndex: this.recoverFromIndex
150
163
  });
@@ -162,11 +175,13 @@ var DataModelMigrationChainWithRecover = class DataModelMigrationChainWithRecove
162
175
  */
163
176
  var DataModelMigrationChain = class DataModelMigrationChain extends MigrationChainBase {
164
177
  /** @internal */
165
- constructor({ versionChain, steps = [], transferSteps = [] }) {
178
+ constructor({ versionChain, steps = [], transferSteps = [], kindRef, parseInitializationParams }) {
166
179
  super({
167
180
  versionChain,
168
181
  steps,
169
- transferSteps
182
+ transferSteps,
183
+ kindRef,
184
+ parseInitializationParams
170
185
  });
171
186
  }
172
187
  /**
@@ -185,7 +200,9 @@ var DataModelMigrationChain = class DataModelMigrationChain extends MigrationCha
185
200
  return new DataModelMigrationChain({
186
201
  versionChain,
187
202
  steps,
188
- transferSteps: this.transferSteps
203
+ transferSteps: this.transferSteps,
204
+ kindRef: this.kindRef,
205
+ parseInitializationParams: this.parseInitializationParams
189
206
  });
190
207
  }
191
208
  /**
@@ -206,7 +223,9 @@ var DataModelMigrationChain = class DataModelMigrationChain extends MigrationCha
206
223
  return new DataModelMigrationChain({
207
224
  versionChain: this.versionChain,
208
225
  steps: this.migrationSteps,
209
- transferSteps
226
+ transferSteps,
227
+ kindRef: this.kindRef,
228
+ parseInitializationParams: this.parseInitializationParams
210
229
  });
211
230
  }
212
231
  /**
@@ -237,6 +256,8 @@ var DataModelMigrationChain = class DataModelMigrationChain extends MigrationCha
237
256
  versionChain: this.versionChain,
238
257
  steps: this.migrationSteps,
239
258
  transferSteps: this.transferSteps,
259
+ kindRef: this.kindRef,
260
+ parseInitializationParams: this.parseInitializationParams,
240
261
  recoverFn: fn,
241
262
  recoverFromIndex: this.migrationSteps.length
242
263
  });
@@ -299,6 +320,8 @@ var DataModelInitialChain = class extends DataModelMigrationChain {
299
320
  return new DataModelMigrationChainWithRecover({
300
321
  versionChain: [require_block_storage.DATA_MODEL_LEGACY_VERSION, ...this.versionChain],
301
322
  steps: [step, ...this.migrationSteps],
323
+ kindRef: this.kindRef,
324
+ parseInitializationParams: this.parseInitializationParams,
302
325
  transferSteps: this.transferSteps.map((t) => ({
303
326
  ...t,
304
327
  beforeStepIndex: t.beforeStepIndex + 1
@@ -353,6 +376,21 @@ var DataModelInitialChain = class extends DataModelMigrationChain {
353
376
  * .init(() => ({ inputFile: '', selectedTab: 'main' }));
354
377
  */
355
378
  var DataModelBuilder = class {
379
+ #kindRef;
380
+ #parseInitializationParams;
381
+ /**
382
+ * @param opts.kind - The block kind this data model implements. Its reference
383
+ * is captured and baked into the config so the manifest can advertise which
384
+ * kind the block satisfies, and its `Params` type flows into `.init()`.
385
+ * Optional during the transition window while existing V3 blocks are
386
+ * migrated to kind-carrying builders; a kind-less builder simply carries no
387
+ * reference and the reconciler can't project it yet. Object form mirrors
388
+ * `BlockModelV3.create({ dataModel, kind })`.
389
+ */
390
+ constructor(opts) {
391
+ this.#kindRef = opts?.kind ? (0, _milaboratories_pl_model_common.formatKindRef)(opts.kind) : void 0;
392
+ this.#parseInitializationParams = opts?.kind?.parseInitializationParams;
393
+ }
356
394
  /**
357
395
  * Start the migration chain with the given initial data type and version key.
358
396
  *
@@ -361,7 +399,11 @@ var DataModelBuilder = class {
361
399
  * @returns Migration chain builder
362
400
  */
363
401
  from(initialVersion) {
364
- return new DataModelInitialChain({ versionChain: [initialVersion] });
402
+ return new DataModelInitialChain({
403
+ versionChain: [initialVersion],
404
+ kindRef: this.#kindRef,
405
+ parseInitializationParams: this.#parseInitializationParams
406
+ });
365
407
  }
366
408
  };
367
409
  /**
@@ -393,13 +435,19 @@ var DataModel = class DataModel {
393
435
  initialDataFn;
394
436
  recoverFn;
395
437
  recoverFromIndex;
396
- constructor({ versionChain, steps, transferSteps = [], initialDataFn, recoverFn = defaultRecover, recoverFromIndex }) {
438
+ /** Reference to the block kind this data model was built for, if any. */
439
+ _kindRef;
440
+ /** The kind's runtime params check, if it declares one. */
441
+ _parseInitializationParams;
442
+ constructor({ versionChain, steps, transferSteps = [], initialDataFn, kindRef, parseInitializationParams, recoverFn = defaultRecover, recoverFromIndex }) {
397
443
  if (versionChain.length === 0) throw new Error("DataModel requires at least one version key");
398
444
  this.latestVersion = versionChain[versionChain.length - 1];
399
445
  this.stepsByFromVersion = new Map(versionChain.map((v, i) => [v, i]));
400
446
  this.steps = steps;
401
447
  this.transferSteps = transferSteps;
402
448
  this.initialDataFn = initialDataFn;
449
+ this._kindRef = kindRef;
450
+ this._parseInitializationParams = parseInitializationParams;
403
451
  this.recoverFn = recoverFn;
404
452
  this.recoverFromIndex = recoverFromIndex ?? steps.length;
405
453
  }
@@ -412,6 +460,25 @@ var DataModel = class DataModel {
412
460
  return new DataModel(state);
413
461
  }
414
462
  /**
463
+ * Reference to the block kind this data model was built for, or `undefined`
464
+ * for a kind-less builder. Used by `BlockModelV3.create` to cross-check that
465
+ * the kind handed to the builder matches the kind handed to `create`.
466
+ * @internal
467
+ */
468
+ get kindRef() {
469
+ return this._kindRef;
470
+ }
471
+ /**
472
+ * The kind's runtime params check, or `undefined` if the kind declares none (or
473
+ * there is no kind). Read by `BlockModelV3.done()` to register the check, and
474
+ * carried here rather than only on the model because `init` — the one place params
475
+ * are consumed — lives on this side.
476
+ * @internal
477
+ */
478
+ get templateParamsParser() {
479
+ return this._parseInitializationParams;
480
+ }
481
+ /**
415
482
  * The latest (current) version key in the migration chain.
416
483
  */
417
484
  get version() {
@@ -421,14 +488,31 @@ var DataModel = class DataModel {
421
488
  * Get a fresh copy of the initial data.
422
489
  */
423
490
  initialData() {
424
- return this.initialDataFn();
491
+ return this.initialDataFn({});
425
492
  }
426
493
  /**
427
494
  * Get initial data wrapped with current version.
428
495
  * Used when creating new blocks or resetting to defaults.
429
496
  */
430
497
  getDefaultData() {
431
- return makeVersionedData(this.latestVersion, this.initialDataFn());
498
+ return makeVersionedData(this.latestVersion, this.initialDataFn({}));
499
+ }
500
+ /**
501
+ * Get initial data built from params, wrapped with current version.
502
+ *
503
+ * The counterpart of {@link getDefaultData} for a block created from a template
504
+ * entry: the factory receives the entry's params instead of nothing. A factory
505
+ * that ignores its argument produces the same result as `getDefaultData`, which
506
+ * is why the two are separate methods rather than one optional argument — the
507
+ * caller decides which contract it is asking for, and a block that cannot honour
508
+ * params must not silently look like one that can.
509
+ *
510
+ * References inside `params` are already resolved to the target project's
511
+ * concrete ids by the time they get here; the factory never sees a
512
+ * template-local one.
513
+ */
514
+ getDataFromParams(params) {
515
+ return makeVersionedData(this.latestVersion, this.initialDataFn({ params }));
432
516
  }
433
517
  recoverFrom(data, version) {
434
518
  let currentData = this.recoverFn(version, data);
@@ -1 +1 @@
1
- {"version":3,"file":"block_migrations.cjs","names":["DATA_MODEL_LEGACY_VERSION"],"sources":["../src/block_migrations.ts"],"sourcesContent":["import { DATA_MODEL_LEGACY_VERSION } from \"./block_storage\";\n\nexport type DataVersionKey = string;\nexport type DataMigrateFn<From, To> = (prev: Readonly<From>) => To;\nexport type DataCreateFn<T> = () => T;\nexport type DataRecoverFn<T> = (version: DataVersionKey, data: unknown) => T;\n\n/**\n * Minimal interface that .transfer() accepts. PluginInstance implements this.\n * Defined here to avoid circular dependency with plugin_model.ts.\n */\nexport interface TransferTarget<Id extends string = string, TransferData = never> {\n readonly id: Id;\n /** Version key in the plugin's data model chain where transferred data enters. */\n readonly transferVersion: string;\n /** @internal Phantom field for TransferData type extraction */\n readonly __transferBrand?: TransferData;\n}\n\n/** Internal record of a single transfer step in the migration chain. */\nexport type TransferStep = {\n pluginId: string;\n /** Capture data before this step index executes. */\n beforeStepIndex: number;\n extract: (data: unknown) => unknown;\n /** Version key in the plugin's data model chain where the transferred data enters. */\n targetVersion: string;\n};\n\n/** Map of plugin ID → versioned data extracted during migration. */\nexport type TransferRecord = Record<string, DataVersioned<unknown>>;\n\n/** Versioned data wrapper for persistence */\nexport type DataVersioned<T> = {\n version: DataVersionKey;\n data: T;\n};\n\n/** Create a DataVersioned wrapper with correct shape */\nexport function makeVersionedData<T>(version: DataVersionKey, data: T): DataVersioned<T> {\n return { version, data };\n}\n\n/** Thrown when a migration step fails. */\nexport class DataMigrationError extends Error {\n name = \"DataMigrationError\";\n constructor(message: string) {\n super(message);\n }\n}\n\n/** Thrown by recover() to signal unrecoverable data. */\nexport class DataUnrecoverableError extends Error {\n name = \"DataUnrecoverableError\";\n constructor(dataVersion: DataVersionKey) {\n super(`Unknown version '${dataVersion}'`);\n }\n}\n\nexport function isDataUnrecoverableError(error: unknown): error is DataUnrecoverableError {\n return error instanceof Error && error.name === \"DataUnrecoverableError\";\n}\n\ntype MigrationStep = {\n fromVersion: DataVersionKey;\n toVersion: DataVersionKey;\n migrate: (data: unknown) => unknown;\n};\n\n/**\n * Default recover function for unknown versions.\n * Use as fallback at the end of custom recover functions.\n *\n * @example\n * .recover((version, data) => {\n * if (version === 'legacy') {\n * return transformLegacyData(data);\n * }\n * return defaultRecover(version, data);\n * })\n */\nexport const defaultRecover: DataRecoverFn<never> = (version, _data) => {\n throw new DataUnrecoverableError(version);\n};\n\n/** Symbol for internal builder creation method */\nconst FROM_BUILDER = Symbol(\"fromBuilder\");\n\n/** Legacy V1 model state shape: { args, uiState } */\nexport type LegacyV1State<Args, UiState> = { args: Args; uiState: UiState };\n\n/** Internal state passed from builder to DataModel */\ntype BuilderState<S> = {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n transferSteps: TransferStep[];\n initialDataFn: () => S;\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n /** Index of the first step to run after recovery. Equals the number of steps\n * present at the time recover() was called. */\n recoverFromIndex?: number;\n};\n\ntype RecoverState = {\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n recoverFromIndex?: number;\n};\n\n/**\n * Abstract base for both migration chain types.\n * Holds shared state, buildStep() helper, and init().\n * migrate() cannot be shared due to a TypeScript limitation: when the base class\n * migrate() return type is abstract, subclasses cannot narrow it without losing type safety.\n * Each subclass therefore owns its migrate() with the correct concrete return type.\n *\n * @internal\n */\nabstract class MigrationChainBase<Current, Transfers extends Record<string, unknown> = {}> {\n protected readonly versionChain: DataVersionKey[];\n protected readonly migrationSteps: MigrationStep[];\n protected readonly transferSteps: TransferStep[];\n\n protected constructor(state: {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n transferSteps?: TransferStep[];\n }) {\n this.versionChain = state.versionChain;\n this.migrationSteps = state.steps;\n this.transferSteps = state.transferSteps ?? [];\n }\n\n /** Appends a migration step and returns the new versionChain and steps arrays. */\n protected buildStep<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): { versionChain: DataVersionKey[]; steps: MigrationStep[] } {\n if (this.versionChain.includes(nextVersion)) {\n throw new Error(`Duplicate version '${nextVersion}' in migration chain`);\n }\n const fromVersion = this.versionChain[this.versionChain.length - 1];\n const step: MigrationStep = {\n fromVersion,\n toVersion: nextVersion,\n migrate: fn as (data: unknown) => unknown,\n };\n return {\n versionChain: [...this.versionChain, nextVersion],\n steps: [...this.migrationSteps, step],\n };\n }\n\n /** Validates uniqueness and records a TransferStep. */\n protected buildTransfer<Id extends string, L>(\n target: TransferTarget<Id, L>,\n extract: (data: Current) => L,\n ): { transferSteps: TransferStep[] } {\n if (this.transferSteps.some((t) => t.pluginId === target.id)) {\n throw new Error(`Duplicate transfer for plugin '${target.id}'`);\n }\n const entry: TransferStep = {\n pluginId: target.id,\n beforeStepIndex: this.migrationSteps.length,\n extract: extract as (data: unknown) => unknown,\n targetVersion: target.transferVersion,\n };\n return { transferSteps: [...this.transferSteps, entry] };\n }\n\n /** Returns recover-specific fields for DataModel construction. Overridden by WithRecover. */\n protected recoverState(): RecoverState {\n return {};\n }\n\n /**\n * Finalize the DataModel with initial data factory.\n *\n * @param initialData - Factory function returning the initial state\n * @returns Finalized DataModel instance\n */\n init(initialData: DataCreateFn<Current>): DataModel<Current, Transfers> {\n return DataModel[FROM_BUILDER]<Current, Transfers>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n transferSteps: this.transferSteps,\n initialDataFn: initialData,\n ...this.recoverState(),\n });\n }\n}\n\n/**\n * Migration chain after recover() or upgradeLegacy() has been called.\n * Further migrate() and transfer() calls are allowed; recover() and upgradeLegacy() are not\n * (enforced by type — no such methods on this class).\n *\n * @typeParam Current - Data type at the current point in the chain\n * @typeParam Transfers - Accumulated transfer types keyed by plugin ID\n * @internal\n */\nclass DataModelMigrationChainWithRecover<\n Current,\n Transfers extends Record<string, unknown> = {},\n> extends MigrationChainBase<Current, Transfers> {\n private readonly recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n private readonly recoverFromIndex?: number;\n\n /** @internal */\n constructor(state: {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n transferSteps?: TransferStep[];\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n recoverFromIndex?: number;\n }) {\n super(state);\n this.recoverFn = state.recoverFn;\n this.recoverFromIndex = state.recoverFromIndex;\n }\n\n protected override recoverState(): RecoverState {\n return {\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n };\n }\n\n /**\n * Add a migration step. Same semantics as on the base chain.\n * recover() and upgradeLegacy() are not available — one has already been called.\n */\n migrate<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): DataModelMigrationChainWithRecover<Next, Transfers> {\n const { versionChain, steps } = this.buildStep(nextVersion, fn);\n return new DataModelMigrationChainWithRecover<Next, Transfers>({\n versionChain,\n steps,\n transferSteps: this.transferSteps,\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n });\n }\n\n /**\n * Extract data at the current chain position for seeding a new plugin.\n * The extract function's return type must match the plugin's transfer data type.\n * Duplicate plugin IDs are rejected at both type and runtime level.\n */\n transfer<Id extends string, L>(\n target: TransferTarget<Id & (Id extends keyof Transfers ? never : string), L>,\n extract: (data: Current) => L,\n ): DataModelMigrationChainWithRecover<Current, Transfers & Record<Id, L>> {\n const { transferSteps } = this.buildTransfer(target, extract);\n return new DataModelMigrationChainWithRecover<Current, Transfers & Record<Id, L>>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n transferSteps,\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n });\n }\n}\n\n/**\n * Migration chain builder.\n * Each migrate() call advances the current data type. recover() can be called once\n * at any point — it removes itself from the returned chain so it cannot be called again.\n * Duplicate version keys throw at runtime.\n *\n * @typeParam Current - Data type at the current point in the migration chain\n * @typeParam Transfers - Accumulated transfer types keyed by plugin ID\n * @internal\n */\nclass DataModelMigrationChain<\n Current,\n Transfers extends Record<string, unknown> = {},\n> extends MigrationChainBase<Current, Transfers> {\n /** @internal */\n constructor({\n versionChain,\n steps = [],\n transferSteps = [],\n }: {\n versionChain: DataVersionKey[];\n steps?: MigrationStep[];\n transferSteps?: TransferStep[];\n }) {\n super({ versionChain, steps, transferSteps });\n }\n\n /**\n * Add a migration step transforming data from the current version to the next.\n *\n * @typeParam Next - Data type of the next version\n * @param nextVersion - Version key to migrate to (must be unique in the chain)\n * @param fn - Migration function\n * @returns Builder with the next version as current\n *\n * @example\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }))\n */\n migrate<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): DataModelMigrationChain<Next, Transfers> {\n const { versionChain, steps } = this.buildStep(nextVersion, fn);\n return new DataModelMigrationChain<Next, Transfers>({\n versionChain,\n steps,\n transferSteps: this.transferSteps,\n });\n }\n\n /**\n * Extract data at the current chain position for seeding a new plugin.\n * The extract function's return type must match the plugin's transfer data type.\n * Duplicate plugin IDs are rejected at both type and runtime level.\n *\n * Calling .transfer() on DataModelInitialChain returns DataModelMigrationChain,\n * which removes .upgradeLegacy() from the chain (preventing a problematic combination).\n *\n * @example\n * .from<V1>(\"v1\")\n * .transfer(tablePlugin, (v1) => ({ state: v1.tableState }))\n * .migrate<V2>(\"v2\", ({ tableState: _, ...rest }) => rest)\n */\n transfer<Id extends string, L>(\n target: TransferTarget<Id & (Id extends keyof Transfers ? never : string), L>,\n extract: (data: Current) => L,\n ): DataModelMigrationChain<Current, Transfers & Record<Id, L>> {\n const { transferSteps } = this.buildTransfer(target, extract);\n return new DataModelMigrationChain<Current, Transfers & Record<Id, L>>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n transferSteps,\n });\n }\n\n /**\n * Set a recovery handler for unknown or legacy versions.\n *\n * The recover function is called when data has a version not in the migration chain.\n * It must return data of the type at this point in the chain (Current). Any migrate()\n * steps added after recover() will then run on the recovered data.\n *\n * Can only be called once — the returned chain has no recover() method.\n *\n * @param fn - Recovery function returning Current (the type at this chain position)\n * @returns Builder with migrate() and init() but without recover()\n *\n * @example\n * // Recover between migrations — recovered data goes through v3 migration\n * new DataModelBuilder<V1>(\"v1\")\n * .migrate<V2>(\"v2\", (v1) => ({ ...v1, label: \"\" }))\n * .recover((version, data) => {\n * if (version === 'legacy') return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<V3>(\"v3\", (v2) => ({ ...v2, description: \"\" }))\n * .init(() => ({ count: 0, label: \"\", description: \"\" }));\n */\n recover(fn: DataRecoverFn<Current>): DataModelMigrationChainWithRecover<Current, Transfers> {\n return new DataModelMigrationChainWithRecover<Current, Transfers>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n transferSteps: this.transferSteps,\n recoverFn: fn as (version: DataVersionKey, data: unknown) => unknown,\n recoverFromIndex: this.migrationSteps.length,\n });\n }\n}\n\n/**\n * Initial migration chain returned by `.from()`.\n * Extends DataModelMigrationChain with `upgradeLegacy()` — available only before\n * any `.migrate()` calls, since legacy data always arrives at the initial version.\n *\n * @typeParam Current - Data type at the initial version\n * @typeParam Transfers - Accumulated transfer types keyed by plugin ID\n * @internal\n */\nclass DataModelInitialChain<\n Current,\n Transfers extends Record<string, unknown> = {},\n> extends DataModelMigrationChain<Current, Transfers> {\n /**\n * Handle legacy V1 model state ({ args, uiState }) when upgrading a block from\n * BlockModel V1 to BlockModelV3.\n *\n * When a V1 block is upgraded, its stored state `{ args, uiState }` is normalized\n * to the internal default version. This method inserts a migration step from that\n * internal version to the version specified in `.from()`, using the provided typed\n * callback to transform the legacy shape. Non-legacy data passes through unchanged.\n *\n * Must be called right after `.from()` — not available after `.migrate()` calls.\n * Any `.migrate()` steps added after `upgradeLegacy()` will run on the transformed result.\n *\n * Can only be called once — the returned chain has no upgradeLegacy() method.\n * Mutually exclusive with recover().\n *\n * @typeParam Args - Type of the legacy block args\n * @typeParam UiState - Type of the legacy block uiState\n * @param fn - Typed transform from { args, uiState } to Current\n * @returns Builder with migrate() and init() but without recover() or upgradeLegacy()\n *\n * @example\n * type OldArgs = { inputFile: string; threshold: number };\n * type OldUiState = { selectedTab: string };\n * type BlockData = { inputFile: string; threshold: number; selectedTab: string };\n *\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(\"v1\")\n * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({\n * inputFile: args.inputFile,\n * threshold: args.threshold,\n * selectedTab: uiState.selectedTab,\n * }))\n * .init(() => ({ inputFile: '', threshold: 0, selectedTab: 'main' }));\n */\n upgradeLegacy<Args, UiState = unknown>(\n fn: (legacy: LegacyV1State<Args, UiState>) => Current,\n ): DataModelMigrationChainWithRecover<Current, Transfers> {\n const wrappedFn = (data: unknown): unknown => {\n if (data !== null && typeof data === \"object\" && \"args\" in data) {\n return fn(data as LegacyV1State<Args, UiState>);\n }\n return data;\n };\n\n // Insert DATA_MODEL_LEGACY_VERSION as the true first version\n // with a migration step that transforms legacy data to the user's initial version.\n const initialVersion = this.versionChain[0];\n const step: MigrationStep = {\n fromVersion: DATA_MODEL_LEGACY_VERSION,\n toVersion: initialVersion,\n migrate: wrappedFn,\n };\n return new DataModelMigrationChainWithRecover<Current, Transfers>({\n versionChain: [DATA_MODEL_LEGACY_VERSION, ...this.versionChain],\n steps: [step, ...this.migrationSteps],\n // Shift transfer indices to account for the prepended legacy step\n transferSteps: this.transferSteps.map((t) => ({\n ...t,\n beforeStepIndex: t.beforeStepIndex + 1,\n })),\n });\n }\n}\n\n/**\n * Builder entry point for creating DataModel with type-safe migrations.\n *\n * @example\n * // Simple (no migrations):\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(\"v1\")\n * .init(() => ({ numbers: [] }));\n *\n * @example\n * // With migrations:\n * const dataModel = new DataModelBuilder()\n * .from<BlockDataV1>(\"v1\")\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }))\n * .migrate<BlockDataV3>(\"v3\", (v2) => ({ ...v2, description: '' }))\n * .init(() => ({ numbers: [], labels: [], description: '' }));\n *\n * @example\n * // With recover() between migrations — recovered data goes through remaining migrations:\n * const dataModelChain = new DataModelBuilder()\n * .from<BlockDataV1>(\"v1\")\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }));\n *\n * // recover() placed before the v3 migration: recovered data goes through v3\n * const dataModel = dataModelChain\n * .recover((version, data) => {\n * if (version === 'legacy' && isLegacyData(data)) return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<BlockDataV3>(\"v3\", (v2) => ({ ...v2, description: '' }))\n * .init(() => ({ numbers: [], labels: [], description: '' }));\n *\n * @example\n * // With upgradeLegacy() — typed upgrade from BlockModel V1 state:\n * type OldArgs = { inputFile: string };\n * type OldUiState = { selectedTab: string };\n * type BlockData = { inputFile: string; selectedTab: string };\n *\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(\"v1\")\n * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({\n * inputFile: args.inputFile,\n * selectedTab: uiState.selectedTab,\n * }))\n * .init(() => ({ inputFile: '', selectedTab: 'main' }));\n */\nexport class DataModelBuilder {\n /**\n * Start the migration chain with the given initial data type and version key.\n *\n * @typeParam T - Data type for the initial version\n * @param initialVersion - Version key string (e.g. \"v1\")\n * @returns Migration chain builder\n */\n from<T>(initialVersion: string): DataModelInitialChain<T> {\n return new DataModelInitialChain<T>({ versionChain: [initialVersion] });\n }\n}\n\n/**\n * DataModel defines the block's data structure, initial values, and migrations.\n * Used by BlockModelV3 to manage data state.\n *\n * Use `new DataModelBuilder()` to create a DataModel.\n *\n * @example\n * // With recover() between migrations:\n * // Recovered data (V2) goes through the v2→v3 migration automatically.\n * const dataModel = new DataModelBuilder()\n * .from<V1>(\"v1\")\n * .migrate<V2>(\"v2\", (v1) => ({ ...v1, label: \"\" }))\n * .recover((version, data) => {\n * if (version === \"legacy\") return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<V3>(\"v3\", (v2) => ({ ...v2, description: \"\" }))\n * .init(() => ({ count: 0, label: \"\", description: \"\" }));\n */\nexport class DataModel<State, Transfers extends Record<string, unknown> = {}> {\n /** @internal Phantom field to anchor the Transfers type parameter. */\n declare readonly __transfers?: Transfers;\n\n /** Latest version key — O(1) access for the common \"already current\" check. */\n private readonly latestVersion: DataVersionKey;\n /** Maps each known version key to the index of the first step to run from it. O(1) lookup. */\n private readonly stepsByFromVersion: ReadonlyMap<DataVersionKey, number>;\n private readonly steps: MigrationStep[];\n private readonly transferSteps: TransferStep[];\n private readonly initialDataFn: () => State;\n private readonly recoverFn: (version: DataVersionKey, data: unknown) => unknown;\n private readonly recoverFromIndex: number;\n\n private constructor({\n versionChain,\n steps,\n transferSteps = [],\n initialDataFn,\n recoverFn = defaultRecover,\n recoverFromIndex,\n }: BuilderState<State>) {\n if (versionChain.length === 0) {\n throw new Error(\"DataModel requires at least one version key\");\n }\n this.latestVersion = versionChain[versionChain.length - 1];\n this.stepsByFromVersion = new Map(versionChain.map((v, i) => [v, i]));\n this.steps = steps;\n this.transferSteps = transferSteps;\n this.initialDataFn = initialDataFn;\n this.recoverFn = recoverFn;\n this.recoverFromIndex = recoverFromIndex ?? steps.length;\n }\n\n /**\n * Internal method for creating DataModel from builder.\n * Uses Symbol key to prevent external access.\n * @internal\n */\n static [FROM_BUILDER]<S, T extends Record<string, unknown> = {}>(\n state: BuilderState<S>,\n ): DataModel<S, T> {\n return new DataModel<S, T>(state);\n }\n\n /**\n * The latest (current) version key in the migration chain.\n */\n get version(): DataVersionKey {\n return this.latestVersion;\n }\n\n /**\n * Get a fresh copy of the initial data.\n */\n initialData(): State {\n return this.initialDataFn();\n }\n\n /**\n * Get initial data wrapped with current version.\n * Used when creating new blocks or resetting to defaults.\n */\n getDefaultData(): DataVersioned<State> {\n return makeVersionedData(this.latestVersion, this.initialDataFn());\n }\n\n private recoverFrom(data: unknown, version: DataVersionKey): DataVersioned<State> {\n // Step 1: call the recover function to get data at the recover point\n // Let errors (including DataUnrecoverableError) propagate to the caller.\n let currentData: unknown = this.recoverFn(version, data);\n\n // Step 2: run any migrations that were added after recover() in the chain\n for (let i = this.recoverFromIndex; i < this.steps.length; i++) {\n const step = this.steps[i];\n currentData = step.migrate(currentData);\n }\n\n return { version: this.latestVersion, data: currentData as State };\n }\n\n /**\n * Migrate versioned data from any version to the latest.\n * Collects transfer extractions at their designated chain positions.\n *\n * - If version is in chain, applies needed migrations (O(1) lookup)\n * - If version is unknown, attempts recovery; falls back to initial data\n * - If a migration step fails, throws so the caller can preserve original data\n *\n * Transfers only fire during normal step-by-step migration:\n * - Recovery path: returns empty transfers\n * - Fast-path (already at latest): returns empty transfers\n *\n * @param versioned - Data with version tag\n * @returns Migrated data at the latest version with transfer record\n * @throws If a migration step from a known version fails\n */\n migrate(versioned: DataVersioned<unknown>): DataVersioned<State> & { transfers: TransferRecord } {\n const { version: fromVersion, data } = versioned;\n\n // Fast path: already at latest version\n if (fromVersion === this.latestVersion) {\n return { version: this.latestVersion, data: data as State, transfers: {} };\n }\n\n // Unknown version: recovery path — empty transfers\n const startIndex = this.stepsByFromVersion.get(fromVersion);\n if (startIndex === undefined) {\n try {\n return { ...this.recoverFrom(data, fromVersion), transfers: {} };\n } catch {\n // Recovery failed (unknown version, recover fn threw, or post-recover\n // migration failed) — reset to initial data rather than blocking the update.\n return { ...this.getDefaultData(), transfers: {} };\n }\n }\n\n let currentData: unknown = data;\n const transfers: TransferRecord = {};\n\n // Run steps and check transfer entries before each step\n for (let i = startIndex; i < this.steps.length; i++) {\n for (const t of this.transferSteps) {\n if (t.beforeStepIndex === i) {\n transfers[t.pluginId] = {\n version: t.targetVersion,\n data: t.extract(currentData),\n };\n }\n }\n currentData = this.steps[i].migrate(currentData);\n }\n\n // Check for transfers positioned at or past the end of the steps array\n // (e.g., .transfer() was the last call before .init(), after all .migrate() calls)\n for (const t of this.transferSteps) {\n if (t.beforeStepIndex >= this.steps.length && t.beforeStepIndex >= startIndex) {\n transfers[t.pluginId] = {\n version: t.targetVersion,\n data: t.extract(currentData),\n };\n }\n }\n\n return { version: this.latestVersion, data: currentData as State, transfers };\n }\n}\n"],"mappings":";;;AAuCA,SAAgB,kBAAqB,SAAyB,MAA2B;CACvF,OAAO;EAAE;EAAS;CAAK;AACzB;;AAWA,IAAa,yBAAb,cAA4C,MAAM;CAChD,OAAO;CACP,YAAY,aAA6B;EACvC,MAAM,oBAAoB,YAAY,EAAE;CAC1C;AACF;AAEA,SAAgB,yBAAyB,OAAiD;CACxF,OAAO,iBAAiB,SAAS,MAAM,SAAS;AAClD;;;;;;;;;;;;;AAoBA,MAAa,kBAAwC,SAAS,UAAU;CACtE,MAAM,IAAI,uBAAuB,OAAO;AAC1C;;AAGA,MAAM,eAAe,OAAO,aAAa;;;;;;;;;;AA+BzC,IAAe,qBAAf,MAA2F;CACzF;CACA;CACA;CAEA,YAAsB,OAInB;EACD,KAAK,eAAe,MAAM;EAC1B,KAAK,iBAAiB,MAAM;EAC5B,KAAK,gBAAgB,MAAM,iBAAiB,CAAC;CAC/C;;CAGA,UACE,aACA,IAC4D;EAC5D,IAAI,KAAK,aAAa,SAAS,WAAW,GACxC,MAAM,IAAI,MAAM,sBAAsB,YAAY,qBAAqB;EAGzE,MAAM,OAAsB;GAC1B,aAFkB,KAAK,aAAa,KAAK,aAAa,SAAS;GAG/D,WAAW;GACX,SAAS;EACX;EACA,OAAO;GACL,cAAc,CAAC,GAAG,KAAK,cAAc,WAAW;GAChD,OAAO,CAAC,GAAG,KAAK,gBAAgB,IAAI;EACtC;CACF;;CAGA,cACE,QACA,SACmC;EACnC,IAAI,KAAK,cAAc,MAAM,MAAM,EAAE,aAAa,OAAO,EAAE,GACzD,MAAM,IAAI,MAAM,kCAAkC,OAAO,GAAG,EAAE;EAEhE,MAAM,QAAsB;GAC1B,UAAU,OAAO;GACjB,iBAAiB,KAAK,eAAe;GAC5B;GACT,eAAe,OAAO;EACxB;EACA,OAAO,EAAE,eAAe,CAAC,GAAG,KAAK,eAAe,KAAK,EAAE;CACzD;;CAGA,eAAuC;EACrC,OAAO,CAAC;CACV;;;;;;;CAQA,KAAK,aAAmE;EACtE,OAAO,UAAU,aAAa,CAAqB;GACjD,cAAc,KAAK;GACnB,OAAO,KAAK;GACZ,eAAe,KAAK;GACpB,eAAe;GACf,GAAG,KAAK,aAAa;EACvB,CAAC;CACH;AACF;;;;;;;;;;AAWA,IAAM,qCAAN,MAAM,2CAGI,mBAAuC;CAC/C;CACA;;CAGA,YAAY,OAMT;EACD,MAAM,KAAK;EACX,KAAK,YAAY,MAAM;EACvB,KAAK,mBAAmB,MAAM;CAChC;CAEA,eAAgD;EAC9C,OAAO;GACL,WAAW,KAAK;GAChB,kBAAkB,KAAK;EACzB;CACF;;;;;CAMA,QACE,aACA,IACqD;EACrD,MAAM,EAAE,cAAc,UAAU,KAAK,UAAU,aAAa,EAAE;EAC9D,OAAO,IAAI,mCAAoD;GAC7D;GACA;GACA,eAAe,KAAK;GACpB,WAAW,KAAK;GAChB,kBAAkB,KAAK;EACzB,CAAC;CACH;;;;;;CAOA,SACE,QACA,SACwE;EACxE,MAAM,EAAE,kBAAkB,KAAK,cAAc,QAAQ,OAAO;EAC5D,OAAO,IAAI,mCAAuE;GAChF,cAAc,KAAK;GACnB,OAAO,KAAK;GACZ;GACA,WAAW,KAAK;GAChB,kBAAkB,KAAK;EACzB,CAAC;CACH;AACF;;;;;;;;;;;AAYA,IAAM,0BAAN,MAAM,gCAGI,mBAAuC;;CAE/C,YAAY,EACV,cACA,QAAQ,CAAC,GACT,gBAAgB,CAAC,KAKhB;EACD,MAAM;GAAE;GAAc;GAAO;EAAc,CAAC;CAC9C;;;;;;;;;;;;CAaA,QACE,aACA,IAC0C;EAC1C,MAAM,EAAE,cAAc,UAAU,KAAK,UAAU,aAAa,EAAE;EAC9D,OAAO,IAAI,wBAAyC;GAClD;GACA;GACA,eAAe,KAAK;EACtB,CAAC;CACH;;;;;;;;;;;;;;CAeA,SACE,QACA,SAC6D;EAC7D,MAAM,EAAE,kBAAkB,KAAK,cAAc,QAAQ,OAAO;EAC5D,OAAO,IAAI,wBAA4D;GACrE,cAAc,KAAK;GACnB,OAAO,KAAK;GACZ;EACF,CAAC;CACH;;;;;;;;;;;;;;;;;;;;;;;;CAyBA,QAAQ,IAAoF;EAC1F,OAAO,IAAI,mCAAuD;GAChE,cAAc,KAAK;GACnB,OAAO,KAAK;GACZ,eAAe,KAAK;GACpB,WAAW;GACX,kBAAkB,KAAK,eAAe;EACxC,CAAC;CACH;AACF;;;;;;;;;;AAWA,IAAM,wBAAN,cAGU,wBAA4C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCpD,cACE,IACwD;EACxD,MAAM,aAAa,SAA2B;GAC5C,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,UAAU,MACzD,OAAO,GAAG,IAAoC;GAEhD,OAAO;EACT;EAKA,MAAM,OAAsB;GAC1B,aAAaA,sBAAAA;GACb,WAHqB,KAAK,aAAa;GAIvC,SAAS;EACX;EACA,OAAO,IAAI,mCAAuD;GAChE,cAAc,CAACA,sBAAAA,2BAA2B,GAAG,KAAK,YAAY;GAC9D,OAAO,CAAC,MAAM,GAAG,KAAK,cAAc;GAEpC,eAAe,KAAK,cAAc,KAAK,OAAO;IAC5C,GAAG;IACH,iBAAiB,EAAE,kBAAkB;GACvC,EAAE;EACJ,CAAC;CACH;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,IAAa,mBAAb,MAA8B;;;;;;;;CAQ5B,KAAQ,gBAAkD;EACxD,OAAO,IAAI,sBAAyB,EAAE,cAAc,CAAC,cAAc,EAAE,CAAC;CACxE;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,IAAa,YAAb,MAAa,UAAiE;;CAK5E;;CAEA;CACA;CACA;CACA;CACA;CACA;CAEA,YAAoB,EAClB,cACA,OACA,gBAAgB,CAAC,GACjB,eACA,YAAY,gBACZ,oBACsB;EACtB,IAAI,aAAa,WAAW,GAC1B,MAAM,IAAI,MAAM,6CAA6C;EAE/D,KAAK,gBAAgB,aAAa,aAAa,SAAS;EACxD,KAAK,qBAAqB,IAAI,IAAI,aAAa,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;EACpE,KAAK,QAAQ;EACb,KAAK,gBAAgB;EACrB,KAAK,gBAAgB;EACrB,KAAK,YAAY;EACjB,KAAK,mBAAmB,oBAAoB,MAAM;CACpD;;;;;;CAOA,QAAQ,cACN,OACiB;EACjB,OAAO,IAAI,UAAgB,KAAK;CAClC;;;;CAKA,IAAI,UAA0B;EAC5B,OAAO,KAAK;CACd;;;;CAKA,cAAqB;EACnB,OAAO,KAAK,cAAc;CAC5B;;;;;CAMA,iBAAuC;EACrC,OAAO,kBAAkB,KAAK,eAAe,KAAK,cAAc,CAAC;CACnE;CAEA,YAAoB,MAAe,SAA+C;EAGhF,IAAI,cAAuB,KAAK,UAAU,SAAS,IAAI;EAGvD,KAAK,IAAI,IAAI,KAAK,kBAAkB,IAAI,KAAK,MAAM,QAAQ,KAEzD,cADa,KAAK,MAAM,EACN,CAAC,QAAQ,WAAW;EAGxC,OAAO;GAAE,SAAS,KAAK;GAAe,MAAM;EAAqB;CACnE;;;;;;;;;;;;;;;;;CAkBA,QAAQ,WAAyF;EAC/F,MAAM,EAAE,SAAS,aAAa,SAAS;EAGvC,IAAI,gBAAgB,KAAK,eACvB,OAAO;GAAE,SAAS,KAAK;GAAqB;GAAe,WAAW,CAAC;EAAE;EAI3E,MAAM,aAAa,KAAK,mBAAmB,IAAI,WAAW;EAC1D,IAAI,eAAe,KAAA,GACjB,IAAI;GACF,OAAO;IAAE,GAAG,KAAK,YAAY,MAAM,WAAW;IAAG,WAAW,CAAC;GAAE;EACjE,QAAQ;GAGN,OAAO;IAAE,GAAG,KAAK,eAAe;IAAG,WAAW,CAAC;GAAE;EACnD;EAGF,IAAI,cAAuB;EAC3B,MAAM,YAA4B,CAAC;EAGnC,KAAK,IAAI,IAAI,YAAY,IAAI,KAAK,MAAM,QAAQ,KAAK;GACnD,KAAK,MAAM,KAAK,KAAK,eACnB,IAAI,EAAE,oBAAoB,GACxB,UAAU,EAAE,YAAY;IACtB,SAAS,EAAE;IACX,MAAM,EAAE,QAAQ,WAAW;GAC7B;GAGJ,cAAc,KAAK,MAAM,EAAE,CAAC,QAAQ,WAAW;EACjD;EAIA,KAAK,MAAM,KAAK,KAAK,eACnB,IAAI,EAAE,mBAAmB,KAAK,MAAM,UAAU,EAAE,mBAAmB,YACjE,UAAU,EAAE,YAAY;GACtB,SAAS,EAAE;GACX,MAAM,EAAE,QAAQ,WAAW;EAC7B;EAIJ,OAAO;GAAE,SAAS,KAAK;GAAe,MAAM;GAAsB;EAAU;CAC9E;AACF"}
1
+ {"version":3,"file":"block_migrations.cjs","names":["DATA_MODEL_LEGACY_VERSION","#kindRef","#parseInitializationParams"],"sources":["../src/block_migrations.ts"],"sourcesContent":["import type { BlockKindReference } from \"@milaboratories/pl-model-common\";\nimport { formatKindRef } from \"@milaboratories/pl-model-common\";\nimport type { CompiledBlockKind } from \"@platforma-sdk/block-kind\";\nimport { DATA_MODEL_LEGACY_VERSION } from \"./block_storage\";\n\nexport type DataVersionKey = string;\nexport type DataMigrateFn<From, To> = (prev: Readonly<From>) => To;\n/**\n * Initial-data factory. Object-arg so future inputs (services, resolved refs)\n * can extend it without a signature break. `Params` carries the block's\n * kind-declared params type; `never` means the block reads no params yet.\n */\nexport type DataCreateFn<T, Params = never> = (args: { params?: Params }) => T;\nexport type DataRecoverFn<T> = (version: DataVersionKey, data: unknown) => T;\n\n/**\n * The compiled block-kind object a block declares — exactly the output of\n * `defineBlockKind` from `@platforma-sdk/block-kind`, consumed type-only.\n *\n * The kind object carries no reference field: the `{name}@{version}` reference\n * is derived from it internally (via `formatKindRef`) at the point the builder\n * and `BlockModelV3.create` need it. Aliased here so `DataModelBuilder` /\n * `create` signatures directly accept `defineBlockKind`'s output.\n */\nexport type BlockKind<Params = never> = CompiledBlockKind<Params>;\n\n/**\n * Minimal interface that .transfer() accepts. PluginInstance implements this.\n * Defined here to avoid circular dependency with plugin_model.ts.\n */\nexport interface TransferTarget<Id extends string = string, TransferData = never> {\n readonly id: Id;\n /** Version key in the plugin's data model chain where transferred data enters. */\n readonly transferVersion: string;\n /** @internal Phantom field for TransferData type extraction */\n readonly __transferBrand?: TransferData;\n}\n\n/** Internal record of a single transfer step in the migration chain. */\nexport type TransferStep = {\n pluginId: string;\n /** Capture data before this step index executes. */\n beforeStepIndex: number;\n extract: (data: unknown) => unknown;\n /** Version key in the plugin's data model chain where the transferred data enters. */\n targetVersion: string;\n};\n\n/** Map of plugin ID → versioned data extracted during migration. */\nexport type TransferRecord = Record<string, DataVersioned<unknown>>;\n\n/** Versioned data wrapper for persistence */\nexport type DataVersioned<T> = {\n version: DataVersionKey;\n data: T;\n};\n\n/** Create a DataVersioned wrapper with correct shape */\nexport function makeVersionedData<T>(version: DataVersionKey, data: T): DataVersioned<T> {\n return { version, data };\n}\n\n/** Thrown when a migration step fails. */\nexport class DataMigrationError extends Error {\n name = \"DataMigrationError\";\n constructor(message: string) {\n super(message);\n }\n}\n\n/** Thrown by recover() to signal unrecoverable data. */\nexport class DataUnrecoverableError extends Error {\n name = \"DataUnrecoverableError\";\n constructor(dataVersion: DataVersionKey) {\n super(`Unknown version '${dataVersion}'`);\n }\n}\n\nexport function isDataUnrecoverableError(error: unknown): error is DataUnrecoverableError {\n return error instanceof Error && error.name === \"DataUnrecoverableError\";\n}\n\ntype MigrationStep = {\n fromVersion: DataVersionKey;\n toVersion: DataVersionKey;\n migrate: (data: unknown) => unknown;\n};\n\n/**\n * Default recover function for unknown versions.\n * Use as fallback at the end of custom recover functions.\n *\n * @example\n * .recover((version, data) => {\n * if (version === 'legacy') {\n * return transformLegacyData(data);\n * }\n * return defaultRecover(version, data);\n * })\n */\nexport const defaultRecover: DataRecoverFn<never> = (version, _data) => {\n throw new DataUnrecoverableError(version);\n};\n\n/** Symbol for internal builder creation method */\nconst FROM_BUILDER = Symbol(\"fromBuilder\");\n\n/** Legacy V1 model state shape: { args, uiState } */\nexport type LegacyV1State<Args, UiState> = { args: Args; uiState: UiState };\n\n/**\n * The kind a chain belongs to, threaded unchanged from the builder into `DataModel`.\n *\n * Named because every constructor in this file carries exactly this pair, and they travel\n * together by construction: a reference with no parser could not check the params it names,\n * and a parser with no reference would have nothing to check them against.\n */\ntype KindWiring = {\n /** Reference to the block kind this data model belongs to, if declared. */\n kindRef?: BlockKindReference;\n /** The kind's runtime params check, threaded with {@link KindWiring.kindRef}. */\n parseInitializationParams?: (value: unknown) => unknown;\n};\n\ntype RecoverState = {\n recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n /** Index of the first step to run after recovery. Equals the number of steps\n * present at the time recover() was called. */\n recoverFromIndex?: number;\n};\n\n/** Internal state passed from builder to DataModel */\ntype BuilderState<S> = KindWiring &\n RecoverState & {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n transferSteps: TransferStep[];\n /**\n * The params type is erased to `unknown` here: the builder checks the block's\n * factory against its kind's params, but a caller that supplies params carries them as\n * data it did not type — parsed file content, today — and cannot state the type. The\n * kind's own parser is what recovers it.\n */\n initialDataFn: DataCreateFn<S, unknown>;\n };\n\n/**\n * Abstract base for both migration chain types.\n * Holds shared state, buildStep() helper, and init().\n * migrate() cannot be shared due to a TypeScript limitation: when the base class\n * migrate() return type is abstract, subclasses cannot narrow it without losing type safety.\n * Each subclass therefore owns its migrate() with the correct concrete return type.\n *\n * @internal\n */\nabstract class MigrationChainBase<\n Current,\n Transfers extends Record<string, unknown> = {},\n Params = never,\n> {\n protected readonly versionChain: DataVersionKey[];\n protected readonly migrationSteps: MigrationStep[];\n protected readonly transferSteps: TransferStep[];\n /** Kind reference seeded by the builder, threaded through the chain into init(). */\n protected readonly kindRef?: BlockKindReference;\n /** The kind's runtime params check, carried for `init()` to hand to the DataModel. */\n protected readonly parseInitializationParams?: (value: unknown) => unknown;\n\n protected constructor(\n state: KindWiring & {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n transferSteps?: TransferStep[];\n },\n ) {\n this.versionChain = state.versionChain;\n this.migrationSteps = state.steps;\n this.transferSteps = state.transferSteps ?? [];\n this.kindRef = state.kindRef;\n this.parseInitializationParams = state.parseInitializationParams;\n }\n\n /** Appends a migration step and returns the new versionChain and steps arrays. */\n protected buildStep<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): { versionChain: DataVersionKey[]; steps: MigrationStep[] } {\n if (this.versionChain.includes(nextVersion)) {\n throw new Error(`Duplicate version '${nextVersion}' in migration chain`);\n }\n const fromVersion = this.versionChain[this.versionChain.length - 1];\n const step: MigrationStep = {\n fromVersion,\n toVersion: nextVersion,\n migrate: fn as (data: unknown) => unknown,\n };\n return {\n versionChain: [...this.versionChain, nextVersion],\n steps: [...this.migrationSteps, step],\n };\n }\n\n /** Validates uniqueness and records a TransferStep. */\n protected buildTransfer<Id extends string, L>(\n target: TransferTarget<Id, L>,\n extract: (data: Current) => L,\n ): { transferSteps: TransferStep[] } {\n if (this.transferSteps.some((t) => t.pluginId === target.id)) {\n throw new Error(`Duplicate transfer for plugin '${target.id}'`);\n }\n const entry: TransferStep = {\n pluginId: target.id,\n beforeStepIndex: this.migrationSteps.length,\n extract: extract as (data: unknown) => unknown,\n targetVersion: target.transferVersion,\n };\n return { transferSteps: [...this.transferSteps, entry] };\n }\n\n /** Returns recover-specific fields for DataModel construction. Overridden by WithRecover. */\n protected recoverState(): RecoverState {\n return {};\n }\n\n /**\n * Finalize the DataModel with initial data factory.\n *\n * @param initialData - Factory function returning the initial state\n * @returns Finalized DataModel instance\n */\n init(initialData: DataCreateFn<Current, Params>): DataModel<Current, Params, Transfers> {\n return DataModel[FROM_BUILDER]<Current, Params, Transfers>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n transferSteps: this.transferSteps,\n initialDataFn: initialData as DataCreateFn<Current, unknown>,\n kindRef: this.kindRef,\n parseInitializationParams: this.parseInitializationParams,\n ...this.recoverState(),\n });\n }\n}\n\n/**\n * Migration chain after recover() or upgradeLegacy() has been called.\n * Further migrate() and transfer() calls are allowed; recover() and upgradeLegacy() are not\n * (enforced by type — no such methods on this class).\n *\n * @typeParam Current - Data type at the current point in the chain\n * @typeParam Transfers - Accumulated transfer types keyed by plugin ID\n * @internal\n */\nclass DataModelMigrationChainWithRecover<\n Current,\n Transfers extends Record<string, unknown> = {},\n Params = never,\n> extends MigrationChainBase<Current, Transfers, Params> {\n private readonly recoverFn?: (version: DataVersionKey, data: unknown) => unknown;\n private readonly recoverFromIndex?: number;\n\n /** @internal */\n constructor(\n state: KindWiring &\n RecoverState & {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n transferSteps?: TransferStep[];\n },\n ) {\n super(state);\n this.recoverFn = state.recoverFn;\n this.recoverFromIndex = state.recoverFromIndex;\n }\n\n protected override recoverState(): RecoverState {\n return {\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n };\n }\n\n /**\n * Add a migration step. Same semantics as on the base chain.\n * recover() and upgradeLegacy() are not available — one has already been called.\n */\n migrate<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): DataModelMigrationChainWithRecover<Next, Transfers, Params> {\n const { versionChain, steps } = this.buildStep(nextVersion, fn);\n return new DataModelMigrationChainWithRecover<Next, Transfers, Params>({\n versionChain,\n steps,\n transferSteps: this.transferSteps,\n kindRef: this.kindRef,\n parseInitializationParams: this.parseInitializationParams,\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n });\n }\n\n /**\n * Extract data at the current chain position for seeding a new plugin.\n * The extract function's return type must match the plugin's transfer data type.\n * Duplicate plugin IDs are rejected at both type and runtime level.\n */\n transfer<Id extends string, L>(\n target: TransferTarget<Id & (Id extends keyof Transfers ? never : string), L>,\n extract: (data: Current) => L,\n ): DataModelMigrationChainWithRecover<Current, Transfers & Record<Id, L>, Params> {\n const { transferSteps } = this.buildTransfer(target, extract);\n return new DataModelMigrationChainWithRecover<Current, Transfers & Record<Id, L>, Params>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n transferSteps,\n kindRef: this.kindRef,\n parseInitializationParams: this.parseInitializationParams,\n recoverFn: this.recoverFn,\n recoverFromIndex: this.recoverFromIndex,\n });\n }\n}\n\n/**\n * Migration chain builder.\n * Each migrate() call advances the current data type. recover() can be called once\n * at any point — it removes itself from the returned chain so it cannot be called again.\n * Duplicate version keys throw at runtime.\n *\n * @typeParam Current - Data type at the current point in the migration chain\n * @typeParam Transfers - Accumulated transfer types keyed by plugin ID\n * @internal\n */\nclass DataModelMigrationChain<\n Current,\n Transfers extends Record<string, unknown> = {},\n Params = never,\n> extends MigrationChainBase<Current, Transfers, Params> {\n /** @internal */\n constructor({\n versionChain,\n steps = [],\n transferSteps = [],\n kindRef,\n parseInitializationParams,\n }: KindWiring & {\n versionChain: DataVersionKey[];\n steps?: MigrationStep[];\n transferSteps?: TransferStep[];\n }) {\n super({ versionChain, steps, transferSteps, kindRef, parseInitializationParams });\n }\n\n /**\n * Add a migration step transforming data from the current version to the next.\n *\n * @typeParam Next - Data type of the next version\n * @param nextVersion - Version key to migrate to (must be unique in the chain)\n * @param fn - Migration function\n * @returns Builder with the next version as current\n *\n * @example\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }))\n */\n migrate<Next>(\n nextVersion: string,\n fn: DataMigrateFn<Current, Next>,\n ): DataModelMigrationChain<Next, Transfers, Params> {\n const { versionChain, steps } = this.buildStep(nextVersion, fn);\n return new DataModelMigrationChain<Next, Transfers, Params>({\n versionChain,\n steps,\n transferSteps: this.transferSteps,\n kindRef: this.kindRef,\n parseInitializationParams: this.parseInitializationParams,\n });\n }\n\n /**\n * Extract data at the current chain position for seeding a new plugin.\n * The extract function's return type must match the plugin's transfer data type.\n * Duplicate plugin IDs are rejected at both type and runtime level.\n *\n * Calling .transfer() on DataModelInitialChain returns DataModelMigrationChain,\n * which removes .upgradeLegacy() from the chain (preventing a problematic combination).\n *\n * @example\n * .from<V1>(\"v1\")\n * .transfer(tablePlugin, (v1) => ({ state: v1.tableState }))\n * .migrate<V2>(\"v2\", ({ tableState: _, ...rest }) => rest)\n */\n transfer<Id extends string, L>(\n target: TransferTarget<Id & (Id extends keyof Transfers ? never : string), L>,\n extract: (data: Current) => L,\n ): DataModelMigrationChain<Current, Transfers & Record<Id, L>, Params> {\n const { transferSteps } = this.buildTransfer(target, extract);\n return new DataModelMigrationChain<Current, Transfers & Record<Id, L>, Params>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n transferSteps,\n kindRef: this.kindRef,\n parseInitializationParams: this.parseInitializationParams,\n });\n }\n\n /**\n * Set a recovery handler for unknown or legacy versions.\n *\n * The recover function is called when data has a version not in the migration chain.\n * It must return data of the type at this point in the chain (Current). Any migrate()\n * steps added after recover() will then run on the recovered data.\n *\n * Can only be called once — the returned chain has no recover() method.\n *\n * @param fn - Recovery function returning Current (the type at this chain position)\n * @returns Builder with migrate() and init() but without recover()\n *\n * @example\n * // Recover between migrations — recovered data goes through v3 migration\n * new DataModelBuilder<V1>(\"v1\")\n * .migrate<V2>(\"v2\", (v1) => ({ ...v1, label: \"\" }))\n * .recover((version, data) => {\n * if (version === 'legacy') return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<V3>(\"v3\", (v2) => ({ ...v2, description: \"\" }))\n * .init(() => ({ count: 0, label: \"\", description: \"\" }));\n */\n recover(\n fn: DataRecoverFn<Current>,\n ): DataModelMigrationChainWithRecover<Current, Transfers, Params> {\n return new DataModelMigrationChainWithRecover<Current, Transfers, Params>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n transferSteps: this.transferSteps,\n kindRef: this.kindRef,\n parseInitializationParams: this.parseInitializationParams,\n recoverFn: fn as (version: DataVersionKey, data: unknown) => unknown,\n recoverFromIndex: this.migrationSteps.length,\n });\n }\n}\n\n/**\n * Initial migration chain returned by `.from()`.\n * Extends DataModelMigrationChain with `upgradeLegacy()` — available only before\n * any `.migrate()` calls, since legacy data always arrives at the initial version.\n *\n * @typeParam Current - Data type at the initial version\n * @typeParam Transfers - Accumulated transfer types keyed by plugin ID\n * @internal\n */\nclass DataModelInitialChain<\n Current,\n Transfers extends Record<string, unknown> = {},\n Params = never,\n> extends DataModelMigrationChain<Current, Transfers, Params> {\n /**\n * Handle legacy V1 model state ({ args, uiState }) when upgrading a block from\n * BlockModel V1 to BlockModelV3.\n *\n * When a V1 block is upgraded, its stored state `{ args, uiState }` is normalized\n * to the internal default version. This method inserts a migration step from that\n * internal version to the version specified in `.from()`, using the provided typed\n * callback to transform the legacy shape. Non-legacy data passes through unchanged.\n *\n * Must be called right after `.from()` — not available after `.migrate()` calls.\n * Any `.migrate()` steps added after `upgradeLegacy()` will run on the transformed result.\n *\n * Can only be called once — the returned chain has no upgradeLegacy() method.\n * Mutually exclusive with recover().\n *\n * @typeParam Args - Type of the legacy block args\n * @typeParam UiState - Type of the legacy block uiState\n * @param fn - Typed transform from { args, uiState } to Current\n * @returns Builder with migrate() and init() but without recover() or upgradeLegacy()\n *\n * @example\n * type OldArgs = { inputFile: string; threshold: number };\n * type OldUiState = { selectedTab: string };\n * type BlockData = { inputFile: string; threshold: number; selectedTab: string };\n *\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(\"v1\")\n * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({\n * inputFile: args.inputFile,\n * threshold: args.threshold,\n * selectedTab: uiState.selectedTab,\n * }))\n * .init(() => ({ inputFile: '', threshold: 0, selectedTab: 'main' }));\n */\n upgradeLegacy<Args, UiState = unknown>(\n fn: (legacy: LegacyV1State<Args, UiState>) => Current,\n ): DataModelMigrationChainWithRecover<Current, Transfers, Params> {\n const wrappedFn = (data: unknown): unknown => {\n if (data !== null && typeof data === \"object\" && \"args\" in data) {\n return fn(data as LegacyV1State<Args, UiState>);\n }\n return data;\n };\n\n // Insert DATA_MODEL_LEGACY_VERSION as the true first version\n // with a migration step that transforms legacy data to the user's initial version.\n const initialVersion = this.versionChain[0];\n const step: MigrationStep = {\n fromVersion: DATA_MODEL_LEGACY_VERSION,\n toVersion: initialVersion,\n migrate: wrappedFn,\n };\n return new DataModelMigrationChainWithRecover<Current, Transfers, Params>({\n versionChain: [DATA_MODEL_LEGACY_VERSION, ...this.versionChain],\n steps: [step, ...this.migrationSteps],\n kindRef: this.kindRef,\n parseInitializationParams: this.parseInitializationParams,\n // Shift transfer indices to account for the prepended legacy step\n transferSteps: this.transferSteps.map((t) => ({\n ...t,\n beforeStepIndex: t.beforeStepIndex + 1,\n })),\n });\n }\n}\n\n/**\n * Builder entry point for creating DataModel with type-safe migrations.\n *\n * @example\n * // Simple (no migrations):\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(\"v1\")\n * .init(() => ({ numbers: [] }));\n *\n * @example\n * // With migrations:\n * const dataModel = new DataModelBuilder()\n * .from<BlockDataV1>(\"v1\")\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }))\n * .migrate<BlockDataV3>(\"v3\", (v2) => ({ ...v2, description: '' }))\n * .init(() => ({ numbers: [], labels: [], description: '' }));\n *\n * @example\n * // With recover() between migrations — recovered data goes through remaining migrations:\n * const dataModelChain = new DataModelBuilder()\n * .from<BlockDataV1>(\"v1\")\n * .migrate<BlockDataV2>(\"v2\", (v1) => ({ ...v1, labels: [] }));\n *\n * // recover() placed before the v3 migration: recovered data goes through v3\n * const dataModel = dataModelChain\n * .recover((version, data) => {\n * if (version === 'legacy' && isLegacyData(data)) return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<BlockDataV3>(\"v3\", (v2) => ({ ...v2, description: '' }))\n * .init(() => ({ numbers: [], labels: [], description: '' }));\n *\n * @example\n * // With upgradeLegacy() — typed upgrade from BlockModel V1 state:\n * type OldArgs = { inputFile: string };\n * type OldUiState = { selectedTab: string };\n * type BlockData = { inputFile: string; selectedTab: string };\n *\n * const dataModel = new DataModelBuilder()\n * .from<BlockData>(\"v1\")\n * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({\n * inputFile: args.inputFile,\n * selectedTab: uiState.selectedTab,\n * }))\n * .init(() => ({ inputFile: '', selectedTab: 'main' }));\n */\nexport class DataModelBuilder<Params = never> {\n readonly #kindRef?: BlockKindReference;\n readonly #parseInitializationParams?: (value: unknown) => Params;\n\n /**\n * @param opts.kind - The block kind this data model implements. Its reference\n * is captured and baked into the config so the manifest can advertise which\n * kind the block satisfies, and its `Params` type flows into `.init()`.\n * Optional during the transition window while existing V3 blocks are\n * migrated to kind-carrying builders; a kind-less builder simply carries no\n * reference and the reconciler can't project it yet. Object form mirrors\n * `BlockModelV3.create({ dataModel, kind })`.\n */\n constructor(opts?: { kind?: BlockKind<Params> }) {\n // Derive the on-wire `{name}@{version}` reference from the compiled kind;\n // the kind object itself has no reference field.\n this.#kindRef = opts?.kind ? formatKindRef(opts.kind) : undefined;\n // Carried alongside the reference, and for the same reason: the kind object is\n // not kept, but two things off it are needed later — how to name the kind, and\n // how to check params claimed to be of it.\n this.#parseInitializationParams = opts?.kind?.parseInitializationParams;\n }\n\n /**\n * Start the migration chain with the given initial data type and version key.\n *\n * @typeParam T - Data type for the initial version\n * @param initialVersion - Version key string (e.g. \"v1\")\n * @returns Migration chain builder\n */\n from<T>(initialVersion: string): DataModelInitialChain<T, {}, Params> {\n return new DataModelInitialChain<T, {}, Params>({\n versionChain: [initialVersion],\n kindRef: this.#kindRef,\n parseInitializationParams: this.#parseInitializationParams as\n | ((value: unknown) => unknown)\n | undefined,\n });\n }\n}\n\n/**\n * DataModel defines the block's data structure, initial values, and migrations.\n * Used by BlockModelV3 to manage data state.\n *\n * Use `new DataModelBuilder()` to create a DataModel.\n *\n * @example\n * // With recover() between migrations:\n * // Recovered data (V2) goes through the v2→v3 migration automatically.\n * const dataModel = new DataModelBuilder()\n * .from<V1>(\"v1\")\n * .migrate<V2>(\"v2\", (v1) => ({ ...v1, label: \"\" }))\n * .recover((version, data) => {\n * if (version === \"legacy\") return transformLegacy(data); // returns V2\n * return defaultRecover(version, data);\n * })\n * .migrate<V3>(\"v3\", (v2) => ({ ...v2, description: \"\" }))\n * .init(() => ({ count: 0, label: \"\", description: \"\" }));\n */\nexport class DataModel<State, Params = never, Transfers extends Record<string, unknown> = {}> {\n /** @internal Phantom field to anchor the Transfers type parameter. */\n declare readonly __transfers?: Transfers;\n /** @internal Phantom field to anchor the Params type parameter (drives the\n * compile-time kind cross-check in `BlockModelV3.create`). */\n declare readonly __params?: Params;\n\n /** Latest version key — O(1) access for the common \"already current\" check. */\n private readonly latestVersion: DataVersionKey;\n /** Maps each known version key to the index of the first step to run from it. O(1) lookup. */\n private readonly stepsByFromVersion: ReadonlyMap<DataVersionKey, number>;\n private readonly steps: MigrationStep[];\n private readonly transferSteps: TransferStep[];\n private readonly initialDataFn: DataCreateFn<State, unknown>;\n private readonly recoverFn: (version: DataVersionKey, data: unknown) => unknown;\n private readonly recoverFromIndex: number;\n /** Reference to the block kind this data model was built for, if any. */\n private readonly _kindRef?: BlockKindReference;\n /** The kind's runtime params check, if it declares one. */\n private readonly _parseInitializationParams?: (value: unknown) => unknown;\n\n private constructor({\n versionChain,\n steps,\n transferSteps = [],\n initialDataFn,\n kindRef,\n parseInitializationParams,\n recoverFn = defaultRecover,\n recoverFromIndex,\n }: BuilderState<State>) {\n if (versionChain.length === 0) {\n throw new Error(\"DataModel requires at least one version key\");\n }\n this.latestVersion = versionChain[versionChain.length - 1];\n this.stepsByFromVersion = new Map(versionChain.map((v, i) => [v, i]));\n this.steps = steps;\n this.transferSteps = transferSteps;\n this.initialDataFn = initialDataFn;\n this._kindRef = kindRef;\n this._parseInitializationParams = parseInitializationParams;\n this.recoverFn = recoverFn;\n this.recoverFromIndex = recoverFromIndex ?? steps.length;\n }\n\n /**\n * Internal method for creating DataModel from builder.\n * Uses Symbol key to prevent external access.\n * @internal\n */\n static [FROM_BUILDER]<S, P = never, T extends Record<string, unknown> = {}>(\n state: BuilderState<S>,\n ): DataModel<S, P, T> {\n return new DataModel<S, P, T>(state);\n }\n\n /**\n * Reference to the block kind this data model was built for, or `undefined`\n * for a kind-less builder. Used by `BlockModelV3.create` to cross-check that\n * the kind handed to the builder matches the kind handed to `create`.\n * @internal\n */\n get kindRef(): BlockKindReference | undefined {\n return this._kindRef;\n }\n\n /**\n * The kind's runtime params check, or `undefined` if the kind declares none (or\n * there is no kind). Read by `BlockModelV3.done()` to register the check, and\n * carried here rather than only on the model because `init` — the one place params\n * are consumed — lives on this side.\n * @internal\n */\n get templateParamsParser(): ((value: unknown) => unknown) | undefined {\n return this._parseInitializationParams;\n }\n\n /**\n * The latest (current) version key in the migration chain.\n */\n get version(): DataVersionKey {\n return this.latestVersion;\n }\n\n /**\n * Get a fresh copy of the initial data.\n */\n initialData(): State {\n return this.initialDataFn({});\n }\n\n /**\n * Get initial data wrapped with current version.\n * Used when creating new blocks or resetting to defaults.\n */\n getDefaultData(): DataVersioned<State> {\n return makeVersionedData(this.latestVersion, this.initialDataFn({}));\n }\n\n /**\n * Get initial data built from params, wrapped with current version.\n *\n * The counterpart of {@link getDefaultData} for a block created from a template\n * entry: the factory receives the entry's params instead of nothing. A factory\n * that ignores its argument produces the same result as `getDefaultData`, which\n * is why the two are separate methods rather than one optional argument — the\n * caller decides which contract it is asking for, and a block that cannot honour\n * params must not silently look like one that can.\n *\n * References inside `params` are already resolved to the target project's\n * concrete ids by the time they get here; the factory never sees a\n * template-local one.\n */\n getDataFromParams(params: unknown): DataVersioned<State> {\n return makeVersionedData(this.latestVersion, this.initialDataFn({ params }));\n }\n\n private recoverFrom(data: unknown, version: DataVersionKey): DataVersioned<State> {\n // Step 1: call the recover function to get data at the recover point\n // Let errors (including DataUnrecoverableError) propagate to the caller.\n let currentData: unknown = this.recoverFn(version, data);\n\n // Step 2: run any migrations that were added after recover() in the chain\n for (let i = this.recoverFromIndex; i < this.steps.length; i++) {\n const step = this.steps[i];\n currentData = step.migrate(currentData);\n }\n\n return { version: this.latestVersion, data: currentData as State };\n }\n\n /**\n * Migrate versioned data from any version to the latest.\n * Collects transfer extractions at their designated chain positions.\n *\n * - If version is in chain, applies needed migrations (O(1) lookup)\n * - If version is unknown, attempts recovery; falls back to initial data\n * - If a migration step fails, throws so the caller can preserve original data\n *\n * Transfers only fire during normal step-by-step migration:\n * - Recovery path: returns empty transfers\n * - Fast-path (already at latest): returns empty transfers\n *\n * @param versioned - Data with version tag\n * @returns Migrated data at the latest version with transfer record\n * @throws If a migration step from a known version fails\n */\n migrate(versioned: DataVersioned<unknown>): DataVersioned<State> & { transfers: TransferRecord } {\n const { version: fromVersion, data } = versioned;\n\n // Fast path: already at latest version\n if (fromVersion === this.latestVersion) {\n return { version: this.latestVersion, data: data as State, transfers: {} };\n }\n\n // Unknown version: recovery path — empty transfers\n const startIndex = this.stepsByFromVersion.get(fromVersion);\n if (startIndex === undefined) {\n try {\n return { ...this.recoverFrom(data, fromVersion), transfers: {} };\n } catch {\n // Recovery failed (unknown version, recover fn threw, or post-recover\n // migration failed) — reset to initial data rather than blocking the update.\n return { ...this.getDefaultData(), transfers: {} };\n }\n }\n\n let currentData: unknown = data;\n const transfers: TransferRecord = {};\n\n // Run steps and check transfer entries before each step\n for (let i = startIndex; i < this.steps.length; i++) {\n for (const t of this.transferSteps) {\n if (t.beforeStepIndex === i) {\n transfers[t.pluginId] = {\n version: t.targetVersion,\n data: t.extract(currentData),\n };\n }\n }\n currentData = this.steps[i].migrate(currentData);\n }\n\n // Check for transfers positioned at or past the end of the steps array\n // (e.g., .transfer() was the last call before .init(), after all .migrate() calls)\n for (const t of this.transferSteps) {\n if (t.beforeStepIndex >= this.steps.length && t.beforeStepIndex >= startIndex) {\n transfers[t.pluginId] = {\n version: t.targetVersion,\n data: t.extract(currentData),\n };\n }\n }\n\n return { version: this.latestVersion, data: currentData as State, transfers };\n }\n}\n"],"mappings":"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"}