@milaboratories/milaboratories.pool-explorer.model 1.1.4 → 1.1.6

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.
@@ -1,16 +1,16 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @milaboratories/milaboratories.pool-explorer.model@1.1.4 build /home/runner/_work/platforma/platforma/etc/blocks/pool-explorer/model
3
+ > @milaboratories/milaboratories.pool-explorer.model@1.1.6 build /home/runner/_work/platforma/platforma/etc/blocks/pool-explorer/model
4
4
  > ts-builder build --target block-model && block-tools build-model
5
5
 
6
6
  Building block-model project...
7
7
  ↳ rollup -c /configs/rollup.block-model.config.js
8
8
  
9
9
  ./src/index.ts → dist, dist...
10
- created dist, dist in 2.4s
10
+ created dist, dist in 2.8s
11
11
  
12
12
  ./src/index.ts → dist...
13
13
  (!) Circular dependency
14
14
  ../../../../sdk/model/dist/components/PFrameForGraphs.js -> ../../../../sdk/model/dist/pframe_utils/columns.js -> ../../../../sdk/model/dist/components/PFrameForGraphs.js
15
- created dist in 3.7s
15
+ created dist in 4.8s
16
16
  Build completed successfully
@@ -1,5 +1,5 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @milaboratories/milaboratories.pool-explorer.model@1.1.4 lint /home/runner/_work/platforma/platforma/etc/blocks/pool-explorer/model
3
+ > @milaboratories/milaboratories.pool-explorer.model@1.1.6 lint /home/runner/_work/platforma/platforma/etc/blocks/pool-explorer/model
4
4
  > eslint .
5
5
 
@@ -1,6 +1,6 @@
1
1
   WARN  Issue while reading "/home/runner/_work/platforma/platforma/.npmrc". Failed to replace env in config: ${NPMJS_TOKEN}
2
2
 
3
- > @milaboratories/milaboratories.pool-explorer.model@1.1.4 type-check /home/runner/_work/platforma/platforma/etc/blocks/pool-explorer/model
3
+ > @milaboratories/milaboratories.pool-explorer.model@1.1.6 type-check /home/runner/_work/platforma/platforma/etc/blocks/pool-explorer/model
4
4
  > ts-builder types --target block-model
5
5
 
6
6
  ↳ tsc --noEmit --project ./tsconfig.json --customConditions ,
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @milaboratories/milaboratories.pool-explorer.model
2
2
 
3
+ ## 1.1.6
4
+
5
+ ### Patch Changes
6
+
7
+ - f459e5a: Refined DataModel
8
+ - Updated dependencies [f459e5a]
9
+ - @platforma-sdk/model@1.53.3
10
+
11
+ ## 1.1.5
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [57799dd]
16
+ - @platforma-sdk/model@1.53.2
17
+
3
18
  ## 1.1.4
4
19
 
5
20
  ### Patch Changes
package/dist/bundle.js CHANGED
@@ -72,7 +72,13 @@
72
72
  function normalizeBlockStorage(raw) {
73
73
  if (isBlockStorage(raw)) {
74
74
  const storage = raw;
75
- return { ...storage, __dataVersion: String(storage.__dataVersion) };
75
+ return {
76
+ ...storage,
77
+ // Fix for early released version where __dataVersion was a number
78
+ __dataVersion: typeof storage.__dataVersion === 'number'
79
+ ? DATA_MODEL_DEFAULT_VERSION
80
+ : storage.__dataVersion,
81
+ };
76
82
  }
77
83
  // Legacy format: raw is the state directly
78
84
  return createBlockStorage(raw);
@@ -7523,7 +7529,7 @@
7523
7529
  }
7524
7530
  }
7525
7531
 
7526
- var version = "1.53.1";
7532
+ var version = "1.53.3";
7527
7533
 
7528
7534
  const PlatformaSDKVersion = version;
7529
7535
 
@@ -7555,7 +7561,7 @@
7555
7561
  *
7556
7562
  * Callbacks registered by DataModel.registerCallbacks():
7557
7563
  * - `__pl_data_initial`: () => initial data
7558
- * - `__pl_data_migrate`: (versioned) => DataMigrationResult
7564
+ * - `__pl_data_upgrade`: (versioned) => DataMigrationResult
7559
7565
  * - `__pl_storage_initial`: () => initial BlockStorage as JSON string
7560
7566
  *
7561
7567
  * @module block_storage_vm
@@ -7660,7 +7666,7 @@
7660
7666
  * Runs storage migration using the DataModel's migrate callback.
7661
7667
  * This is the main entry point for the middle layer to trigger migrations.
7662
7668
  *
7663
- * Uses the '__pl_data_migrate' callback registered by DataModel.registerCallbacks() which:
7669
+ * Uses the '__pl_data_upgrade' callback registered by DataModel.registerCallbacks() which:
7664
7670
  * - Handles all migration logic internally
7665
7671
  * - Returns { version, data, warning? } - warning present if reset to initial data
7666
7672
  *
@@ -7685,9 +7691,9 @@
7685
7691
  });
7686
7692
  };
7687
7693
  // Get the migrate callback (registered by DataModel.registerCallbacks())
7688
- const migrateCallback = ctx.callbackRegistry['__pl_data_migrate'];
7694
+ const migrateCallback = ctx.callbackRegistry['__pl_data_upgrade'];
7689
7695
  if (typeof migrateCallback !== 'function') {
7690
- return { error: '__pl_data_migrate callback not found (DataModel not registered)' };
7696
+ return { error: '__pl_data_upgrade callback not found (DataModel not registered)' };
7691
7697
  }
7692
7698
  // Call the migrator's migrate function
7693
7699
  let result;
@@ -7810,7 +7816,14 @@
7810
7816
  * Creates a new BlockModelV3 builder with the specified data model and options.
7811
7817
  *
7812
7818
  * @example
7813
- * const dataModel = DataModel.create<BlockData>(() => ({ numbers: [], labels: [] }));
7819
+ * const Version = defineDataVersions({ V1: DATA_MODEL_DEFAULT_VERSION });
7820
+ *
7821
+ * type VersionedData = { [Version.V1]: BlockData };
7822
+ *
7823
+ * const dataModel = new DataModelBuilder<VersionedData>()
7824
+ * .from(Version.V1)
7825
+ * .init(() => ({ numbers: [], labels: [] }));
7826
+ *
7814
7827
  * BlockModelV3.create({ dataModel })
7815
7828
  * .args((data) => ({ numbers: data.numbers }))
7816
7829
  * .sections(() => [{ type: 'link', href: '/', label: 'Main' }])
@@ -7842,7 +7855,11 @@
7842
7855
  ...this.config,
7843
7856
  outputs: {
7844
7857
  ...this.config.outputs,
7845
- [key]: createAndRegisterRenderLambda({ handle: `output#${key}`, lambda: () => cfgOrRf(new RenderCtx()), ...flags }),
7858
+ [key]: createAndRegisterRenderLambda({
7859
+ handle: `output#${key}`,
7860
+ lambda: () => cfgOrRf(new RenderCtx()),
7861
+ ...flags,
7862
+ }),
7846
7863
  },
7847
7864
  });
7848
7865
  }
@@ -7896,7 +7913,10 @@
7896
7913
  prerunArgs(fn) {
7897
7914
  return new BlockModelV3({
7898
7915
  ...this.config,
7899
- prerunArgs: createAndRegisterRenderLambda({ handle: 'prerunArgs', lambda: fn }),
7916
+ prerunArgs: createAndRegisterRenderLambda({
7917
+ handle: 'prerunArgs',
7918
+ lambda: fn,
7919
+ }),
7900
7920
  });
7901
7921
  }
7902
7922
  /** Sets the lambda to generate list of sections in the left block overviews panel. */
@@ -7911,19 +7931,28 @@
7911
7931
  title(rf) {
7912
7932
  return new BlockModelV3({
7913
7933
  ...this.config,
7914
- title: createAndRegisterRenderLambda({ handle: 'title', lambda: () => rf(new RenderCtx()) }),
7934
+ title: createAndRegisterRenderLambda({
7935
+ handle: 'title',
7936
+ lambda: () => rf(new RenderCtx()),
7937
+ }),
7915
7938
  });
7916
7939
  }
7917
7940
  subtitle(rf) {
7918
7941
  return new BlockModelV3({
7919
7942
  ...this.config,
7920
- subtitle: createAndRegisterRenderLambda({ handle: 'subtitle', lambda: () => rf(new RenderCtx()) }),
7943
+ subtitle: createAndRegisterRenderLambda({
7944
+ handle: 'subtitle',
7945
+ lambda: () => rf(new RenderCtx()),
7946
+ }),
7921
7947
  });
7922
7948
  }
7923
7949
  tags(rf) {
7924
7950
  return new BlockModelV3({
7925
7951
  ...this.config,
7926
- tags: createAndRegisterRenderLambda({ handle: 'tags', lambda: () => rf(new RenderCtx()) }),
7952
+ tags: createAndRegisterRenderLambda({
7953
+ handle: 'tags',
7954
+ lambda: () => rf(new RenderCtx()),
7955
+ }),
7927
7956
  });
7928
7957
  }
7929
7958
  /** Sets or overrides feature flags for the block. */
@@ -7940,7 +7969,10 @@
7940
7969
  enriches(lambda) {
7941
7970
  return new BlockModelV3({
7942
7971
  ...this.config,
7943
- enrichmentTargets: createAndRegisterRenderLambda({ handle: 'enrichmentTargets', lambda: lambda }),
7972
+ enrichmentTargets: createAndRegisterRenderLambda({
7973
+ handle: 'enrichmentTargets',
7974
+ lambda: lambda,
7975
+ }),
7944
7976
  });
7945
7977
  }
7946
7978
  /** Renders all provided block settings into a pre-configured platforma API
@@ -7980,7 +8012,10 @@
7980
8012
  sdkVersion: PlatformaSDKVersion,
7981
8013
  renderingMode: this.config.renderingMode,
7982
8014
  sections: this.config.sections,
7983
- outputs: Object.fromEntries(Object.entries(this.config.outputs).map(([key, value]) => [key, downgradeCfgOrLambda(value)])),
8015
+ outputs: Object.fromEntries(Object.entries(this.config.outputs).map(([key, value]) => [
8016
+ key,
8017
+ downgradeCfgOrLambda(value),
8018
+ ])),
7984
8019
  };
7985
8020
  globalThis.platformaApiVersion = apiVersion;
7986
8021
  if (!isInUI())
@@ -7989,17 +8024,55 @@
7989
8024
  // normal operation inside the UI
7990
8025
  else
7991
8026
  return {
7992
- ...getPlatformaInstance({ sdkVersion: PlatformaSDKVersion, apiVersion }),
8027
+ ...getPlatformaInstance({
8028
+ sdkVersion: PlatformaSDKVersion,
8029
+ apiVersion,
8030
+ }),
7993
8031
  blockModelInfo: {
7994
- outputs: Object.fromEntries(Object.entries(this.config.outputs)
7995
- .map(([key, value]) => [key, {
8032
+ outputs: Object.fromEntries(Object.entries(this.config.outputs).map(([key, value]) => [
8033
+ key,
8034
+ {
7996
8035
  withStatus: Boolean(isConfigLambda(value) && value.withStatus),
7997
- }])),
8036
+ },
8037
+ ])),
7998
8038
  },
7999
8039
  };
8000
8040
  }
8001
8041
  }
8002
8042
 
8043
+ /**
8044
+ * Helper to define version keys with literal type inference and runtime validation.
8045
+ * - Validates that all version values are unique
8046
+ * - Validates that no version value is empty
8047
+ * - Eliminates need for `as const` assertion
8048
+ *
8049
+ * @throws Error if duplicate or empty version values are found
8050
+ *
8051
+ * @example
8052
+ * const Version = defineDataVersions({
8053
+ * Initial: 'v1',
8054
+ * AddedLabels: 'v2',
8055
+ * });
8056
+ *
8057
+ * type VersionedData = {
8058
+ * [Version.Initial]: DataV1;
8059
+ * [Version.AddedLabels]: DataV2;
8060
+ * };
8061
+ */
8062
+ function defineDataVersions(versions) {
8063
+ const values = Object.values(versions);
8064
+ const keys = Object.keys(versions);
8065
+ const emptyKeys = keys.filter((key) => versions[key] === '');
8066
+ if (emptyKeys.length > 0) {
8067
+ throw new Error(`Version values must be non-empty strings (empty: ${emptyKeys.join(', ')})`);
8068
+ }
8069
+ const unique = new Set(values);
8070
+ if (unique.size !== values.length) {
8071
+ const duplicates = values.filter((v, i) => values.indexOf(v) !== i);
8072
+ throw new Error(`Duplicate version values: ${[...new Set(duplicates)].join(', ')}`);
8073
+ }
8074
+ return versions;
8075
+ }
8003
8076
  /** Create a DataVersioned wrapper with correct shape */
8004
8077
  function makeDataVersioned(version, data) {
8005
8078
  return { version, data };
@@ -8014,56 +8087,228 @@
8014
8087
  function isDataUnrecoverableError(error) {
8015
8088
  return error instanceof Error && error.name === 'DataUnrecoverableError';
8016
8089
  }
8017
- /** Default recover function for unknown versions */
8090
+ /**
8091
+ * Default recover function for unknown versions.
8092
+ * Use as fallback at the end of custom recover functions.
8093
+ *
8094
+ * @example
8095
+ * .recover((version, data) => {
8096
+ * if (version === 'legacy') {
8097
+ * return transformLegacyData(data);
8098
+ * }
8099
+ * return defaultRecover(version, data);
8100
+ * })
8101
+ */
8018
8102
  const defaultRecover = (version, _data) => {
8019
8103
  throw new DataUnrecoverableError(version);
8020
8104
  };
8021
- /** Internal builder for chaining migrations */
8022
- class DataModelBuilder {
8105
+ /** Symbol for internal builder creation method */
8106
+ const FROM_BUILDER = Symbol('fromBuilder');
8107
+ /**
8108
+ * Final builder state after recover() is called.
8109
+ * Only allows calling create() to finalize the DataModel.
8110
+ *
8111
+ * @typeParam VersionedData - Map of version keys to their data types
8112
+ * @typeParam CurrentVersion - The current (final) version in the chain
8113
+ * @internal
8114
+ */
8115
+ class DataModelBuilderWithRecover {
8023
8116
  versionChain;
8024
8117
  migrationSteps;
8025
8118
  recoverFn;
8026
- constructor(versionChain, steps = [], recoverFn) {
8119
+ /** @internal */
8120
+ constructor({ versionChain, steps, recoverFn, }) {
8027
8121
  this.versionChain = versionChain;
8028
8122
  this.migrationSteps = steps;
8029
8123
  this.recoverFn = recoverFn;
8030
8124
  }
8031
- /** Start a migration chain from an initial version */
8032
- static from(initialVersion) {
8033
- return new DataModelBuilder([initialVersion]);
8125
+ /**
8126
+ * Finalize the DataModel with initial data factory.
8127
+ *
8128
+ * The initial data factory is called when creating new blocks or when
8129
+ * migration/recovery fails and data must be reset.
8130
+ *
8131
+ * @param initialData - Factory function returning initial state (must exactly match CurrentVersion's data type)
8132
+ * @returns Finalized DataModel instance
8133
+ *
8134
+ * @example
8135
+ * .init(() => ({ numbers: [], labels: [], description: '' }))
8136
+ */
8137
+ init(initialData,
8138
+ // Compile-time check: S must have exactly the same keys as VersionedData[CurrentVersion]
8139
+ ..._noExtraKeys) {
8140
+ return DataModel[FROM_BUILDER]({
8141
+ versionChain: this.versionChain,
8142
+ steps: this.migrationSteps,
8143
+ initialDataFn: initialData,
8144
+ recoverFn: this.recoverFn,
8145
+ });
8034
8146
  }
8035
- /** Add a migration step to the target version */
8147
+ }
8148
+ /**
8149
+ * Internal builder for constructing DataModel with type-safe migration chains.
8150
+ *
8151
+ * Tracks the current version through the generic type system, ensuring:
8152
+ * - Migration functions receive correctly typed input
8153
+ * - Migration functions must return the correct output type
8154
+ * - Version keys must exist in the VersionedData map
8155
+ * - All versions must be covered before calling init()
8156
+ *
8157
+ * @typeParam VersionedData - Map of version keys to their data types
8158
+ * @typeParam CurrentVersion - The current version in the migration chain
8159
+ * @typeParam RemainingVersions - Versions not yet covered by migrations
8160
+ * @internal
8161
+ */
8162
+ class DataModelMigrationChain {
8163
+ versionChain;
8164
+ migrationSteps;
8165
+ /** @internal */
8166
+ constructor({ versionChain, steps = [], }) {
8167
+ this.versionChain = versionChain;
8168
+ this.migrationSteps = steps;
8169
+ }
8170
+ /**
8171
+ * Add a migration step to transform data from current version to next version.
8172
+ *
8173
+ * Migration functions:
8174
+ * - Receive data typed as the current version's data type (readonly)
8175
+ * - Must return data matching the target version's data type
8176
+ * - Should be pure functions (no side effects)
8177
+ * - May throw errors (will result in data reset with warning)
8178
+ *
8179
+ * @typeParam NextVersion - The target version key (must be in RemainingVersions)
8180
+ * @param nextVersion - The version key to migrate to
8181
+ * @param fn - Migration function transforming current data to next version
8182
+ * @returns Builder with updated current version
8183
+ *
8184
+ * @example
8185
+ * .migrate(Version.V2, (data) => ({ ...data, labels: [] }))
8186
+ */
8036
8187
  migrate(nextVersion, fn) {
8037
8188
  if (this.versionChain.includes(nextVersion)) {
8038
8189
  throw new Error(`Duplicate version '${nextVersion}' in migration chain`);
8039
8190
  }
8040
8191
  const fromVersion = this.versionChain[this.versionChain.length - 1];
8041
- const step = { fromVersion, toVersion: nextVersion, migrate: fn };
8042
- return new DataModelBuilder([...this.versionChain, nextVersion], [...this.migrationSteps, step]);
8192
+ const step = {
8193
+ fromVersion,
8194
+ toVersion: nextVersion,
8195
+ migrate: fn,
8196
+ };
8197
+ return new DataModelMigrationChain({
8198
+ versionChain: [...this.versionChain, nextVersion],
8199
+ steps: [...this.migrationSteps, step],
8200
+ });
8043
8201
  }
8044
- /** Set recovery handler for unknown or unsupported versions */
8202
+ /**
8203
+ * Set a recovery handler for unknown or legacy versions.
8204
+ *
8205
+ * The recover function is called when data has a version not in the migration chain.
8206
+ * It should either:
8207
+ * - Transform the data to the current version's format and return it
8208
+ * - Call `defaultRecover(version, data)` to signal unrecoverable data
8209
+ *
8210
+ * Can only be called once. After calling, only `init()` is available.
8211
+ *
8212
+ * @param fn - Recovery function that transforms unknown data or throws
8213
+ * @returns Builder with only init() method available
8214
+ *
8215
+ * @example
8216
+ * .recover((version, data) => {
8217
+ * if (version === 'legacy' && isLegacyFormat(data)) {
8218
+ * return transformLegacy(data);
8219
+ * }
8220
+ * return defaultRecover(version, data);
8221
+ * })
8222
+ */
8045
8223
  recover(fn) {
8046
- return new DataModelBuilder([...this.versionChain], [...this.migrationSteps], fn);
8224
+ return new DataModelBuilderWithRecover({
8225
+ versionChain: [...this.versionChain],
8226
+ steps: [...this.migrationSteps],
8227
+ recoverFn: fn,
8228
+ });
8047
8229
  }
8048
- /** Finalize with initial data, creating the DataModel */
8049
- create(initialData, ..._) {
8050
- return DataModel._fromBuilder(this.versionChain, this.migrationSteps, initialData, this.recoverFn);
8230
+ /**
8231
+ * Finalize the DataModel with initial data factory.
8232
+ *
8233
+ * Can only be called when all versions in VersionedData have been covered
8234
+ * by the migration chain (RemainingVersions is empty).
8235
+ *
8236
+ * The initial data factory is called when creating new blocks or when
8237
+ * migration/recovery fails and data must be reset.
8238
+ *
8239
+ * @param initialData - Factory function returning initial state (must exactly match CurrentVersion's data type)
8240
+ * @returns Finalized DataModel instance
8241
+ *
8242
+ * @example
8243
+ * .init(() => ({ numbers: [], labels: [], description: '' }))
8244
+ */
8245
+ init(initialData,
8246
+ // Compile-time check: S must have exactly the same keys as VersionedData[CurrentVersion]
8247
+ ..._noExtraKeys) {
8248
+ return DataModel[FROM_BUILDER]({
8249
+ versionChain: this.versionChain,
8250
+ steps: this.migrationSteps,
8251
+ initialDataFn: initialData,
8252
+ });
8253
+ }
8254
+ }
8255
+ /**
8256
+ * Builder entry point for creating DataModel with type-safe migrations.
8257
+ *
8258
+ * @typeParam VersionedData - Map of version keys to their data types
8259
+ *
8260
+ * @example
8261
+ * const Version = defineDataVersions({
8262
+ * V1: 'v1',
8263
+ * V2: 'v2',
8264
+ * });
8265
+ *
8266
+ * type VersionedData = {
8267
+ * [Version.V1]: { count: number };
8268
+ * [Version.V2]: { count: number; label: string };
8269
+ * };
8270
+ *
8271
+ * const dataModel = new DataModelBuilder<VersionedData>()
8272
+ * .from(Version.V1)
8273
+ * .migrate(Version.V2, (data) => ({ ...data, label: '' }))
8274
+ * .init(() => ({ count: 0, label: '' }));
8275
+ */
8276
+ class DataModelBuilder {
8277
+ /**
8278
+ * Start a migration chain from an initial version.
8279
+ *
8280
+ * @typeParam InitialVersion - The starting version key (inferred from argument)
8281
+ * @param initialVersion - The version key to start from
8282
+ * @returns Migration chain builder for adding migrations
8283
+ *
8284
+ * @example
8285
+ * new DataModelBuilder<VersionedData>()
8286
+ * .from(Version.V1)
8287
+ * .migrate(Version.V2, (data) => ({ ...data, newField: '' }))
8288
+ */
8289
+ from(initialVersion) {
8290
+ return new DataModelMigrationChain({ versionChain: [initialVersion] });
8051
8291
  }
8052
8292
  }
8053
8293
  /**
8054
8294
  * DataModel defines the block's data structure, initial values, and migrations.
8055
8295
  * Used by BlockModelV3 to manage data state.
8056
8296
  *
8297
+ * Use `new DataModelBuilder<VersionedData>()` to create a DataModel:
8298
+ *
8299
+ * **Simple (no migrations):**
8057
8300
  * @example
8058
- * // Simple data model (no migrations)
8059
- * const dataModel = DataModel.create<BlockData>(() => ({
8060
- * numbers: [],
8061
- * labels: [],
8062
- * }));
8301
+ * const Version = defineDataVersions({ V1: DATA_MODEL_DEFAULT_VERSION });
8302
+ * type VersionedData = { [Version.V1]: BlockData };
8303
+ *
8304
+ * const dataModel = new DataModelBuilder<VersionedData>()
8305
+ * .from(Version.V1)
8306
+ * .init(() => ({ numbers: [], labels: [] }));
8063
8307
  *
8064
- * // Data model with migrations
8308
+ * **With migrations:**
8309
+ * @example
8065
8310
  * const Version = defineDataVersions({
8066
- * V1: 'v1',
8311
+ * V1: DATA_MODEL_DEFAULT_VERSION,
8067
8312
  * V2: 'v2',
8068
8313
  * V3: 'v3',
8069
8314
  * });
@@ -8074,8 +8319,8 @@
8074
8319
  * [Version.V3]: { numbers: number[]; labels: string[]; description: string };
8075
8320
  * };
8076
8321
  *
8077
- * const dataModel = DataModel
8078
- * .from<VersionedData>(Version.V1)
8322
+ * const dataModel = new DataModelBuilder<VersionedData>()
8323
+ * .from(Version.V1)
8079
8324
  * .migrate(Version.V2, (data) => ({ ...data, labels: [] }))
8080
8325
  * .migrate(Version.V3, (data) => ({ ...data, description: '' }))
8081
8326
  * .recover((version, data) => {
@@ -8084,49 +8329,52 @@
8084
8329
  * }
8085
8330
  * return defaultRecover(version, data);
8086
8331
  * })
8087
- * .create(() => ({ numbers: [], labels: [], description: '' }));
8332
+ * .init(() => ({ numbers: [], labels: [], description: '' }));
8088
8333
  */
8089
8334
  class DataModel {
8090
8335
  versionChain;
8091
8336
  steps;
8092
8337
  initialDataFn;
8093
8338
  recoverFn;
8094
- constructor(versionChain, steps, initialData, recover = defaultRecover) {
8339
+ constructor({ versionChain, steps, initialDataFn, recoverFn = defaultRecover, }) {
8095
8340
  if (versionChain.length === 0) {
8096
8341
  throw new Error('DataModel requires at least one version key');
8097
8342
  }
8098
8343
  this.versionChain = versionChain;
8099
8344
  this.steps = steps;
8100
- this.initialDataFn = initialData;
8101
- this.recoverFn = recover;
8102
- }
8103
- /** Start a migration chain from an initial type */
8104
- static from(initialVersion) {
8105
- return DataModelBuilder.from(initialVersion);
8106
- }
8107
- /** Create a data model with just initial data (no migrations) */
8108
- static create(initialData, version = DATA_MODEL_DEFAULT_VERSION) {
8109
- return new DataModel([version], [], initialData);
8345
+ this.initialDataFn = initialDataFn;
8346
+ this.recoverFn = recoverFn;
8110
8347
  }
8111
- /** Create from builder (internal use) */
8112
- static _fromBuilder(versionChain, steps, initialData, recover) {
8113
- return new DataModel(versionChain, steps, initialData, recover);
8348
+ /**
8349
+ * Internal method for creating DataModel from builder.
8350
+ * Uses Symbol key to prevent external access.
8351
+ * @internal
8352
+ */
8353
+ static [FROM_BUILDER](state) {
8354
+ return new DataModel(state);
8114
8355
  }
8115
8356
  /**
8116
- * Latest version key.
8357
+ * The latest (current) version key in the migration chain.
8117
8358
  */
8118
8359
  get version() {
8119
8360
  return this.versionChain[this.versionChain.length - 1];
8120
8361
  }
8121
- /** Number of migration steps */
8362
+ /**
8363
+ * Number of migration steps defined.
8364
+ */
8122
8365
  get migrationCount() {
8123
8366
  return this.steps.length;
8124
8367
  }
8125
- /** Get initial data */
8368
+ /**
8369
+ * Get a fresh copy of the initial data.
8370
+ */
8126
8371
  initialData() {
8127
8372
  return this.initialDataFn();
8128
8373
  }
8129
- /** Get default data wrapped with current version */
8374
+ /**
8375
+ * Get initial data wrapped with current version.
8376
+ * Used when creating new blocks or resetting to defaults.
8377
+ */
8130
8378
  getDefaultData() {
8131
8379
  return makeDataVersioned(this.version, this.initialDataFn());
8132
8380
  }
@@ -8147,8 +8395,14 @@
8147
8395
  }
8148
8396
  /**
8149
8397
  * Migrate versioned data from any version to the latest.
8150
- * Applies only the migrations needed (skips already-applied ones).
8151
- * If a migration fails, returns default data with a warning.
8398
+ *
8399
+ * - If data is already at latest version, returns as-is
8400
+ * - If version is in chain, applies needed migrations
8401
+ * - If version is unknown, calls recover function
8402
+ * - If migration/recovery fails, returns default data with warning
8403
+ *
8404
+ * @param versioned - Data with version tag
8405
+ * @returns Migration result with data at latest version
8152
8406
  */
8153
8407
  migrate(versioned) {
8154
8408
  const { version: fromVersion, data } = versioned;
@@ -8179,14 +8433,11 @@
8179
8433
  * Register callbacks for use in the VM.
8180
8434
  * Called by BlockModelV3.create() to set up internal callbacks.
8181
8435
  *
8182
- * All callbacks are prefixed with `__pl_` to indicate internal SDK use:
8183
- * - `__pl_data_initial`: returns initial data for new blocks
8184
- * - `__pl_data_migrate`: migrates versioned data from any version to latest
8185
- * - `__pl_storage_initial`: returns initial BlockStorage as JSON string
8436
+ * @internal
8186
8437
  */
8187
8438
  registerCallbacks() {
8188
8439
  tryRegisterCallback('__pl_data_initial', () => this.initialDataFn());
8189
- tryRegisterCallback('__pl_data_migrate', (versioned) => this.migrate(versioned));
8440
+ tryRegisterCallback('__pl_data_upgrade', (versioned) => this.migrate(versioned));
8190
8441
  tryRegisterCallback('__pl_storage_initial', () => {
8191
8442
  const { version, data } = this.getDefaultData();
8192
8443
  const storage = createBlockStorage(data, version);
@@ -8275,7 +8526,10 @@
8275
8526
  errors: z.lazy(() => ErrorShape.array()).optional(),
8276
8527
  });
8277
8528
 
8278
- const dataModel = DataModel.create(() => ({ titleArgs: 'The title' }));
8529
+ const Version = defineDataVersions({ V1: DATA_MODEL_DEFAULT_VERSION });
8530
+ const dataModel = new DataModelBuilder()
8531
+ .from(Version.V1)
8532
+ .init(() => ({ titleArgs: 'The title' }));
8279
8533
  const platforma = BlockModelV3.create({ dataModel, renderingMode: 'Heavy' })
8280
8534
  .args((data) => {
8281
8535
  return { titleArgs: data.titleArgs };