@platforma-sdk/model 1.53.1 → 1.53.3

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,12 +1,13 @@
1
1
  import { tryRegisterCallback } from './internal.js';
2
- import { DATA_MODEL_DEFAULT_VERSION, createBlockStorage } from './block_storage.js';
2
+ import { createBlockStorage } from './block_storage.js';
3
3
 
4
4
  /**
5
5
  * Helper to define version keys with literal type inference and runtime validation.
6
6
  * - Validates that all version values are unique
7
+ * - Validates that no version value is empty
7
8
  * - Eliminates need for `as const` assertion
8
9
  *
9
- * @throws Error if duplicate version values are found
10
+ * @throws Error if duplicate or empty version values are found
10
11
  *
11
12
  * @example
12
13
  * const Version = defineDataVersions({
@@ -21,7 +22,8 @@ import { DATA_MODEL_DEFAULT_VERSION, createBlockStorage } from './block_storage.
21
22
  */
22
23
  function defineDataVersions(versions) {
23
24
  const values = Object.values(versions);
24
- const emptyKeys = Object.keys(versions).filter((key) => versions[key] === '');
25
+ const keys = Object.keys(versions);
26
+ const emptyKeys = keys.filter((key) => versions[key] === '');
25
27
  if (emptyKeys.length > 0) {
26
28
  throw new Error(`Version values must be non-empty strings (empty: ${emptyKeys.join(', ')})`);
27
29
  }
@@ -46,56 +48,228 @@ class DataUnrecoverableError extends Error {
46
48
  function isDataUnrecoverableError(error) {
47
49
  return error instanceof Error && error.name === 'DataUnrecoverableError';
48
50
  }
49
- /** Default recover function for unknown versions */
51
+ /**
52
+ * Default recover function for unknown versions.
53
+ * Use as fallback at the end of custom recover functions.
54
+ *
55
+ * @example
56
+ * .recover((version, data) => {
57
+ * if (version === 'legacy') {
58
+ * return transformLegacyData(data);
59
+ * }
60
+ * return defaultRecover(version, data);
61
+ * })
62
+ */
50
63
  const defaultRecover = (version, _data) => {
51
64
  throw new DataUnrecoverableError(version);
52
65
  };
53
- /** Internal builder for chaining migrations */
54
- class DataModelBuilder {
66
+ /** Symbol for internal builder creation method */
67
+ const FROM_BUILDER = Symbol('fromBuilder');
68
+ /**
69
+ * Final builder state after recover() is called.
70
+ * Only allows calling create() to finalize the DataModel.
71
+ *
72
+ * @typeParam VersionedData - Map of version keys to their data types
73
+ * @typeParam CurrentVersion - The current (final) version in the chain
74
+ * @internal
75
+ */
76
+ class DataModelBuilderWithRecover {
55
77
  versionChain;
56
78
  migrationSteps;
57
79
  recoverFn;
58
- constructor(versionChain, steps = [], recoverFn) {
80
+ /** @internal */
81
+ constructor({ versionChain, steps, recoverFn, }) {
59
82
  this.versionChain = versionChain;
60
83
  this.migrationSteps = steps;
61
84
  this.recoverFn = recoverFn;
62
85
  }
63
- /** Start a migration chain from an initial version */
64
- static from(initialVersion) {
65
- return new DataModelBuilder([initialVersion]);
86
+ /**
87
+ * Finalize the DataModel with initial data factory.
88
+ *
89
+ * The initial data factory is called when creating new blocks or when
90
+ * migration/recovery fails and data must be reset.
91
+ *
92
+ * @param initialData - Factory function returning initial state (must exactly match CurrentVersion's data type)
93
+ * @returns Finalized DataModel instance
94
+ *
95
+ * @example
96
+ * .init(() => ({ numbers: [], labels: [], description: '' }))
97
+ */
98
+ init(initialData,
99
+ // Compile-time check: S must have exactly the same keys as VersionedData[CurrentVersion]
100
+ ..._noExtraKeys) {
101
+ return DataModel[FROM_BUILDER]({
102
+ versionChain: this.versionChain,
103
+ steps: this.migrationSteps,
104
+ initialDataFn: initialData,
105
+ recoverFn: this.recoverFn,
106
+ });
66
107
  }
67
- /** Add a migration step to the target version */
108
+ }
109
+ /**
110
+ * Internal builder for constructing DataModel with type-safe migration chains.
111
+ *
112
+ * Tracks the current version through the generic type system, ensuring:
113
+ * - Migration functions receive correctly typed input
114
+ * - Migration functions must return the correct output type
115
+ * - Version keys must exist in the VersionedData map
116
+ * - All versions must be covered before calling init()
117
+ *
118
+ * @typeParam VersionedData - Map of version keys to their data types
119
+ * @typeParam CurrentVersion - The current version in the migration chain
120
+ * @typeParam RemainingVersions - Versions not yet covered by migrations
121
+ * @internal
122
+ */
123
+ class DataModelMigrationChain {
124
+ versionChain;
125
+ migrationSteps;
126
+ /** @internal */
127
+ constructor({ versionChain, steps = [], }) {
128
+ this.versionChain = versionChain;
129
+ this.migrationSteps = steps;
130
+ }
131
+ /**
132
+ * Add a migration step to transform data from current version to next version.
133
+ *
134
+ * Migration functions:
135
+ * - Receive data typed as the current version's data type (readonly)
136
+ * - Must return data matching the target version's data type
137
+ * - Should be pure functions (no side effects)
138
+ * - May throw errors (will result in data reset with warning)
139
+ *
140
+ * @typeParam NextVersion - The target version key (must be in RemainingVersions)
141
+ * @param nextVersion - The version key to migrate to
142
+ * @param fn - Migration function transforming current data to next version
143
+ * @returns Builder with updated current version
144
+ *
145
+ * @example
146
+ * .migrate(Version.V2, (data) => ({ ...data, labels: [] }))
147
+ */
68
148
  migrate(nextVersion, fn) {
69
149
  if (this.versionChain.includes(nextVersion)) {
70
150
  throw new Error(`Duplicate version '${nextVersion}' in migration chain`);
71
151
  }
72
152
  const fromVersion = this.versionChain[this.versionChain.length - 1];
73
- const step = { fromVersion, toVersion: nextVersion, migrate: fn };
74
- return new DataModelBuilder([...this.versionChain, nextVersion], [...this.migrationSteps, step]);
153
+ const step = {
154
+ fromVersion,
155
+ toVersion: nextVersion,
156
+ migrate: fn,
157
+ };
158
+ return new DataModelMigrationChain({
159
+ versionChain: [...this.versionChain, nextVersion],
160
+ steps: [...this.migrationSteps, step],
161
+ });
75
162
  }
76
- /** Set recovery handler for unknown or unsupported versions */
163
+ /**
164
+ * Set a recovery handler for unknown or legacy versions.
165
+ *
166
+ * The recover function is called when data has a version not in the migration chain.
167
+ * It should either:
168
+ * - Transform the data to the current version's format and return it
169
+ * - Call `defaultRecover(version, data)` to signal unrecoverable data
170
+ *
171
+ * Can only be called once. After calling, only `init()` is available.
172
+ *
173
+ * @param fn - Recovery function that transforms unknown data or throws
174
+ * @returns Builder with only init() method available
175
+ *
176
+ * @example
177
+ * .recover((version, data) => {
178
+ * if (version === 'legacy' && isLegacyFormat(data)) {
179
+ * return transformLegacy(data);
180
+ * }
181
+ * return defaultRecover(version, data);
182
+ * })
183
+ */
77
184
  recover(fn) {
78
- return new DataModelBuilder([...this.versionChain], [...this.migrationSteps], fn);
185
+ return new DataModelBuilderWithRecover({
186
+ versionChain: [...this.versionChain],
187
+ steps: [...this.migrationSteps],
188
+ recoverFn: fn,
189
+ });
79
190
  }
80
- /** Finalize with initial data, creating the DataModel */
81
- create(initialData, ..._) {
82
- return DataModel._fromBuilder(this.versionChain, this.migrationSteps, initialData, this.recoverFn);
191
+ /**
192
+ * Finalize the DataModel with initial data factory.
193
+ *
194
+ * Can only be called when all versions in VersionedData have been covered
195
+ * by the migration chain (RemainingVersions is empty).
196
+ *
197
+ * The initial data factory is called when creating new blocks or when
198
+ * migration/recovery fails and data must be reset.
199
+ *
200
+ * @param initialData - Factory function returning initial state (must exactly match CurrentVersion's data type)
201
+ * @returns Finalized DataModel instance
202
+ *
203
+ * @example
204
+ * .init(() => ({ numbers: [], labels: [], description: '' }))
205
+ */
206
+ init(initialData,
207
+ // Compile-time check: S must have exactly the same keys as VersionedData[CurrentVersion]
208
+ ..._noExtraKeys) {
209
+ return DataModel[FROM_BUILDER]({
210
+ versionChain: this.versionChain,
211
+ steps: this.migrationSteps,
212
+ initialDataFn: initialData,
213
+ });
214
+ }
215
+ }
216
+ /**
217
+ * Builder entry point for creating DataModel with type-safe migrations.
218
+ *
219
+ * @typeParam VersionedData - Map of version keys to their data types
220
+ *
221
+ * @example
222
+ * const Version = defineDataVersions({
223
+ * V1: 'v1',
224
+ * V2: 'v2',
225
+ * });
226
+ *
227
+ * type VersionedData = {
228
+ * [Version.V1]: { count: number };
229
+ * [Version.V2]: { count: number; label: string };
230
+ * };
231
+ *
232
+ * const dataModel = new DataModelBuilder<VersionedData>()
233
+ * .from(Version.V1)
234
+ * .migrate(Version.V2, (data) => ({ ...data, label: '' }))
235
+ * .init(() => ({ count: 0, label: '' }));
236
+ */
237
+ class DataModelBuilder {
238
+ /**
239
+ * Start a migration chain from an initial version.
240
+ *
241
+ * @typeParam InitialVersion - The starting version key (inferred from argument)
242
+ * @param initialVersion - The version key to start from
243
+ * @returns Migration chain builder for adding migrations
244
+ *
245
+ * @example
246
+ * new DataModelBuilder<VersionedData>()
247
+ * .from(Version.V1)
248
+ * .migrate(Version.V2, (data) => ({ ...data, newField: '' }))
249
+ */
250
+ from(initialVersion) {
251
+ return new DataModelMigrationChain({ versionChain: [initialVersion] });
83
252
  }
84
253
  }
85
254
  /**
86
255
  * DataModel defines the block's data structure, initial values, and migrations.
87
256
  * Used by BlockModelV3 to manage data state.
88
257
  *
258
+ * Use `new DataModelBuilder<VersionedData>()` to create a DataModel:
259
+ *
260
+ * **Simple (no migrations):**
89
261
  * @example
90
- * // Simple data model (no migrations)
91
- * const dataModel = DataModel.create<BlockData>(() => ({
92
- * numbers: [],
93
- * labels: [],
94
- * }));
262
+ * const Version = defineDataVersions({ V1: DATA_MODEL_DEFAULT_VERSION });
263
+ * type VersionedData = { [Version.V1]: BlockData };
264
+ *
265
+ * const dataModel = new DataModelBuilder<VersionedData>()
266
+ * .from(Version.V1)
267
+ * .init(() => ({ numbers: [], labels: [] }));
95
268
  *
96
- * // Data model with migrations
269
+ * **With migrations:**
270
+ * @example
97
271
  * const Version = defineDataVersions({
98
- * V1: 'v1',
272
+ * V1: DATA_MODEL_DEFAULT_VERSION,
99
273
  * V2: 'v2',
100
274
  * V3: 'v3',
101
275
  * });
@@ -106,8 +280,8 @@ class DataModelBuilder {
106
280
  * [Version.V3]: { numbers: number[]; labels: string[]; description: string };
107
281
  * };
108
282
  *
109
- * const dataModel = DataModel
110
- * .from<VersionedData>(Version.V1)
283
+ * const dataModel = new DataModelBuilder<VersionedData>()
284
+ * .from(Version.V1)
111
285
  * .migrate(Version.V2, (data) => ({ ...data, labels: [] }))
112
286
  * .migrate(Version.V3, (data) => ({ ...data, description: '' }))
113
287
  * .recover((version, data) => {
@@ -116,49 +290,52 @@ class DataModelBuilder {
116
290
  * }
117
291
  * return defaultRecover(version, data);
118
292
  * })
119
- * .create(() => ({ numbers: [], labels: [], description: '' }));
293
+ * .init(() => ({ numbers: [], labels: [], description: '' }));
120
294
  */
121
295
  class DataModel {
122
296
  versionChain;
123
297
  steps;
124
298
  initialDataFn;
125
299
  recoverFn;
126
- constructor(versionChain, steps, initialData, recover = defaultRecover) {
300
+ constructor({ versionChain, steps, initialDataFn, recoverFn = defaultRecover, }) {
127
301
  if (versionChain.length === 0) {
128
302
  throw new Error('DataModel requires at least one version key');
129
303
  }
130
304
  this.versionChain = versionChain;
131
305
  this.steps = steps;
132
- this.initialDataFn = initialData;
133
- this.recoverFn = recover;
134
- }
135
- /** Start a migration chain from an initial type */
136
- static from(initialVersion) {
137
- return DataModelBuilder.from(initialVersion);
138
- }
139
- /** Create a data model with just initial data (no migrations) */
140
- static create(initialData, version = DATA_MODEL_DEFAULT_VERSION) {
141
- return new DataModel([version], [], initialData);
306
+ this.initialDataFn = initialDataFn;
307
+ this.recoverFn = recoverFn;
142
308
  }
143
- /** Create from builder (internal use) */
144
- static _fromBuilder(versionChain, steps, initialData, recover) {
145
- return new DataModel(versionChain, steps, initialData, recover);
309
+ /**
310
+ * Internal method for creating DataModel from builder.
311
+ * Uses Symbol key to prevent external access.
312
+ * @internal
313
+ */
314
+ static [FROM_BUILDER](state) {
315
+ return new DataModel(state);
146
316
  }
147
317
  /**
148
- * Latest version key.
318
+ * The latest (current) version key in the migration chain.
149
319
  */
150
320
  get version() {
151
321
  return this.versionChain[this.versionChain.length - 1];
152
322
  }
153
- /** Number of migration steps */
323
+ /**
324
+ * Number of migration steps defined.
325
+ */
154
326
  get migrationCount() {
155
327
  return this.steps.length;
156
328
  }
157
- /** Get initial data */
329
+ /**
330
+ * Get a fresh copy of the initial data.
331
+ */
158
332
  initialData() {
159
333
  return this.initialDataFn();
160
334
  }
161
- /** Get default data wrapped with current version */
335
+ /**
336
+ * Get initial data wrapped with current version.
337
+ * Used when creating new blocks or resetting to defaults.
338
+ */
162
339
  getDefaultData() {
163
340
  return makeDataVersioned(this.version, this.initialDataFn());
164
341
  }
@@ -179,8 +356,14 @@ class DataModel {
179
356
  }
180
357
  /**
181
358
  * Migrate versioned data from any version to the latest.
182
- * Applies only the migrations needed (skips already-applied ones).
183
- * If a migration fails, returns default data with a warning.
359
+ *
360
+ * - If data is already at latest version, returns as-is
361
+ * - If version is in chain, applies needed migrations
362
+ * - If version is unknown, calls recover function
363
+ * - If migration/recovery fails, returns default data with warning
364
+ *
365
+ * @param versioned - Data with version tag
366
+ * @returns Migration result with data at latest version
184
367
  */
185
368
  migrate(versioned) {
186
369
  const { version: fromVersion, data } = versioned;
@@ -211,14 +394,11 @@ class DataModel {
211
394
  * Register callbacks for use in the VM.
212
395
  * Called by BlockModelV3.create() to set up internal callbacks.
213
396
  *
214
- * All callbacks are prefixed with `__pl_` to indicate internal SDK use:
215
- * - `__pl_data_initial`: returns initial data for new blocks
216
- * - `__pl_data_migrate`: migrates versioned data from any version to latest
217
- * - `__pl_storage_initial`: returns initial BlockStorage as JSON string
397
+ * @internal
218
398
  */
219
399
  registerCallbacks() {
220
400
  tryRegisterCallback('__pl_data_initial', () => this.initialDataFn());
221
- tryRegisterCallback('__pl_data_migrate', (versioned) => this.migrate(versioned));
401
+ tryRegisterCallback('__pl_data_upgrade', (versioned) => this.migrate(versioned));
222
402
  tryRegisterCallback('__pl_storage_initial', () => {
223
403
  const { version, data } = this.getDefaultData();
224
404
  const storage = createBlockStorage(data, version);
@@ -227,5 +407,5 @@ class DataModel {
227
407
  }
228
408
  }
229
409
 
230
- export { DataModel, DataUnrecoverableError, defaultRecover, defineDataVersions, isDataUnrecoverableError, makeDataVersioned };
410
+ export { DataModel, DataModelBuilder, DataUnrecoverableError, defaultRecover, defineDataVersions, isDataUnrecoverableError, makeDataVersioned };
231
411
  //# sourceMappingURL=block_migrations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"block_migrations.js","sources":["../src/block_migrations.ts"],"sourcesContent":["import { tryRegisterCallback } from './internal';\nimport { createBlockStorage, DATA_MODEL_DEFAULT_VERSION } from './block_storage';\n\nexport type DataVersionKey = string;\nexport type DataVersionMap = Record<string, unknown>;\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 * Helper to define version keys with literal type inference and runtime validation.\n * - Validates that all version values are unique\n * - Eliminates need for `as const` assertion\n *\n * @throws Error if duplicate version values are found\n *\n * @example\n * const Version = defineDataVersions({\n * Initial: 'v1',\n * AddedLabels: 'v2',\n * });\n *\n * type VersionedData = {\n * [Version.Initial]: DataV1;\n * [Version.AddedLabels]: DataV2;\n * };\n */\nexport function defineDataVersions<const T extends Record<string, string>>(versions: T): T {\n const values = Object.values(versions);\n const emptyKeys = Object.keys(versions).filter((key) => versions[key] === '');\n if (emptyKeys.length > 0) {\n throw new Error(`Version values must be non-empty strings (empty: ${emptyKeys.join(', ')})`);\n }\n const unique = new Set(values);\n if (unique.size !== values.length) {\n const duplicates = values.filter((v, i) => values.indexOf(v) !== i);\n throw new Error(`Duplicate version values: ${[...new Set(duplicates)].join(', ')}`);\n }\n return versions;\n}\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 makeDataVersioned<T>(version: DataVersionKey, data: T): DataVersioned<T> {\n return { version, data };\n}\n\n/** Result of migration operation, may include warning if migration failed */\nexport type DataMigrationResult<T> = DataVersioned<T> & {\n warning?: string;\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/** Default recover function for unknown versions */\nexport const defaultRecover: DataRecoverFn<never> = (version, _data) => {\n throw new DataUnrecoverableError(version);\n};\n\n/** Internal builder for chaining migrations */\nclass DataModelBuilder<\n VersionedData extends DataVersionMap,\n CurrentVersion extends keyof VersionedData & string,\n> {\n private readonly versionChain: DataVersionKey[];\n private readonly migrationSteps: MigrationStep[];\n private readonly recoverFn?: DataRecoverFn<VersionedData[CurrentVersion]>;\n\n private constructor(\n versionChain: DataVersionKey[],\n steps: MigrationStep[] = [],\n recoverFn?: DataRecoverFn<VersionedData[CurrentVersion]>,\n ) {\n this.versionChain = versionChain;\n this.migrationSteps = steps;\n this.recoverFn = recoverFn;\n }\n\n /** Start a migration chain from an initial version */\n static from<\n VersionedData extends DataVersionMap,\n InitialVersion extends keyof VersionedData & string = keyof VersionedData & string,\n >(initialVersion: InitialVersion): DataModelBuilder<VersionedData, InitialVersion> {\n return new DataModelBuilder<VersionedData, InitialVersion>([initialVersion]);\n }\n\n /** Add a migration step to the target version */\n migrate<NextVersion extends keyof VersionedData & string>(\n nextVersion: NextVersion,\n fn: DataMigrateFn<VersionedData[CurrentVersion], VersionedData[NextVersion]>,\n ): DataModelBuilder<VersionedData, NextVersion> {\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 = { fromVersion, toVersion: nextVersion, migrate: fn as (data: unknown) => unknown };\n return new DataModelBuilder<VersionedData, NextVersion>(\n [...this.versionChain, nextVersion],\n [...this.migrationSteps, step],\n );\n }\n\n /** Set recovery handler for unknown or unsupported versions */\n recover(\n fn: DataRecoverFn<VersionedData[CurrentVersion]>,\n ): DataModelBuilder<VersionedData, CurrentVersion> {\n return new DataModelBuilder<VersionedData, CurrentVersion>(\n [...this.versionChain],\n [...this.migrationSteps],\n fn,\n );\n }\n\n /** Finalize with initial data, creating the DataModel */\n create<S extends VersionedData[CurrentVersion]>(\n initialData: DataCreateFn<S>,\n ..._: [VersionedData[CurrentVersion]] extends [S] ? [] : [never]\n ): DataModel<S> {\n return DataModel._fromBuilder<S>(\n this.versionChain,\n this.migrationSteps,\n initialData,\n this.recoverFn as DataRecoverFn<S> | 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 * @example\n * // Simple data model (no migrations)\n * const dataModel = DataModel.create<BlockData>(() => ({\n * numbers: [],\n * labels: [],\n * }));\n *\n * // Data model with migrations\n * const Version = defineDataVersions({\n * V1: 'v1',\n * V2: 'v2',\n * V3: 'v3',\n * });\n *\n * type VersionedData = {\n * [Version.V1]: { numbers: number[] };\n * [Version.V2]: { numbers: number[]; labels: string[] };\n * [Version.V3]: { numbers: number[]; labels: string[]; description: string };\n * };\n *\n * const dataModel = DataModel\n * .from<VersionedData>(Version.V1)\n * .migrate(Version.V2, (data) => ({ ...data, labels: [] }))\n * .migrate(Version.V3, (data) => ({ ...data, description: '' }))\n * .recover((version, data) => {\n * if (version === 'legacy' && typeof data === 'object' && data !== null && 'numbers' in data) {\n * return { numbers: (data as { numbers: number[] }).numbers, labels: [], description: '' };\n * }\n * return defaultRecover(version, data);\n * })\n * .create(() => ({ numbers: [], labels: [], description: '' }));\n */\nexport class DataModel<State> {\n private readonly versionChain: DataVersionKey[];\n private readonly steps: MigrationStep[];\n private readonly initialDataFn: () => State;\n private readonly recoverFn: DataRecoverFn<State>;\n\n private constructor(\n versionChain: DataVersionKey[],\n steps: MigrationStep[],\n initialData: () => State,\n recover: DataRecoverFn<State> = defaultRecover as DataRecoverFn<State>,\n ) {\n if (versionChain.length === 0) {\n throw new Error('DataModel requires at least one version key');\n }\n this.versionChain = versionChain;\n this.steps = steps;\n this.initialDataFn = initialData;\n this.recoverFn = recover;\n }\n\n /** Start a migration chain from an initial type */\n static from<\n VersionedData extends DataVersionMap,\n InitialVersion extends keyof VersionedData & string = keyof VersionedData & string,\n >(initialVersion: InitialVersion): DataModelBuilder<VersionedData, InitialVersion> {\n return DataModelBuilder.from<VersionedData, InitialVersion>(initialVersion);\n }\n\n /** Create a data model with just initial data (no migrations) */\n static create<S>(initialData: () => S, version: DataVersionKey = DATA_MODEL_DEFAULT_VERSION): DataModel<S> {\n return new DataModel<S>([version], [], initialData);\n }\n\n /** Create from builder (internal use) */\n static _fromBuilder<S>(\n versionChain: DataVersionKey[],\n steps: MigrationStep[],\n initialData: () => S,\n recover?: DataRecoverFn<S>,\n ): DataModel<S> {\n return new DataModel<S>(versionChain, steps, initialData, recover);\n }\n\n /**\n * Latest version key.\n */\n get version(): DataVersionKey {\n return this.versionChain[this.versionChain.length - 1];\n }\n\n /** Number of migration steps */\n get migrationCount(): number {\n return this.steps.length;\n }\n\n /** Get initial data */\n initialData(): State {\n return this.initialDataFn();\n }\n\n /** Get default data wrapped with current version */\n getDefaultData(): DataVersioned<State> {\n return makeDataVersioned(this.version, this.initialDataFn());\n }\n\n private recoverFrom(data: unknown, version: DataVersionKey): DataMigrationResult<State> {\n try {\n return { version: this.version, data: this.recoverFn(version, data) };\n } catch (error) {\n if (isDataUnrecoverableError(error)) {\n return { ...this.getDefaultData(), warning: error.message };\n }\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n ...this.getDefaultData(),\n warning: `Recover failed for version '${version}': ${errorMessage}`,\n };\n }\n }\n\n /**\n * Migrate versioned data from any version to the latest.\n * Applies only the migrations needed (skips already-applied ones).\n * If a migration fails, returns default data with a warning.\n */\n migrate(versioned: DataVersioned<unknown>): DataMigrationResult<State> {\n const { version: fromVersion, data } = versioned;\n\n if (fromVersion === this.version) {\n return { version: this.version, data: data as State };\n }\n\n const startIndex = this.versionChain.indexOf(fromVersion);\n if (startIndex < 0) {\n return this.recoverFrom(data, fromVersion);\n }\n\n let currentData: unknown = data;\n for (let i = startIndex; i < this.steps.length; i++) {\n const step = this.steps[i];\n try {\n currentData = step.migrate(currentData);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n ...this.getDefaultData(),\n warning: `Migration ${step.fromVersion}→${step.toVersion} failed: ${errorMessage}`,\n };\n }\n }\n\n return { version: this.version, data: currentData as State };\n }\n\n /**\n * Register callbacks for use in the VM.\n * Called by BlockModelV3.create() to set up internal callbacks.\n *\n * All callbacks are prefixed with `__pl_` to indicate internal SDK use:\n * - `__pl_data_initial`: returns initial data for new blocks\n * - `__pl_data_migrate`: migrates versioned data from any version to latest\n * - `__pl_storage_initial`: returns initial BlockStorage as JSON string\n */\n registerCallbacks(): void {\n tryRegisterCallback('__pl_data_initial', () => this.initialDataFn());\n tryRegisterCallback('__pl_data_migrate', (versioned: DataVersioned<unknown>) => this.migrate(versioned));\n tryRegisterCallback('__pl_storage_initial', () => {\n const { version, data } = this.getDefaultData();\n const storage = createBlockStorage(data, version);\n return JSON.stringify(storage);\n });\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"block_migrations.js","sources":["../src/block_migrations.ts"],"sourcesContent":["import { tryRegisterCallback } from './internal';\nimport { createBlockStorage } from './block_storage';\n\nexport type DataVersionKey = string;\nexport type DataVersionMap = Record<string, unknown>;\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 * Helper to define version keys with literal type inference and runtime validation.\n * - Validates that all version values are unique\n * - Validates that no version value is empty\n * - Eliminates need for `as const` assertion\n *\n * @throws Error if duplicate or empty version values are found\n *\n * @example\n * const Version = defineDataVersions({\n * Initial: 'v1',\n * AddedLabels: 'v2',\n * });\n *\n * type VersionedData = {\n * [Version.Initial]: DataV1;\n * [Version.AddedLabels]: DataV2;\n * };\n */\nexport function defineDataVersions<const T extends Record<string, string>>(versions: T): T {\n const values = Object.values(versions) as (string & keyof T)[];\n const keys = Object.keys(versions) as (keyof T)[];\n const emptyKeys = keys.filter((key) => versions[key] === '');\n if (emptyKeys.length > 0) {\n throw new Error(`Version values must be non-empty strings (empty: ${emptyKeys.join(', ')})`);\n }\n const unique = new Set(values);\n if (unique.size !== values.length) {\n const duplicates = values.filter((v, i) => values.indexOf(v) !== i);\n throw new Error(`Duplicate version values: ${[...new Set(duplicates)].join(', ')}`);\n }\n return versions;\n}\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 makeDataVersioned<T>(version: DataVersionKey, data: T): DataVersioned<T> {\n return { version, data };\n}\n\n/** Result of migration operation, may include warning if migration failed */\nexport type DataMigrationResult<T> = DataVersioned<T> & {\n warning?: string;\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/** Internal state passed from builder to DataModel */\ntype BuilderState<S> = {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n initialDataFn: () => S;\n recoverFn?: DataRecoverFn<S>;\n};\n\n/**\n * Final builder state after recover() is called.\n * Only allows calling create() to finalize the DataModel.\n *\n * @typeParam VersionedData - Map of version keys to their data types\n * @typeParam CurrentVersion - The current (final) version in the chain\n * @internal\n */\nclass DataModelBuilderWithRecover<\n VersionedData extends DataVersionMap,\n CurrentVersion extends keyof VersionedData & string,\n> {\n private readonly versionChain: DataVersionKey[];\n private readonly migrationSteps: MigrationStep[];\n private readonly recoverFn: DataRecoverFn<VersionedData[CurrentVersion]>;\n\n /** @internal */\n constructor({\n versionChain,\n steps,\n recoverFn,\n }: {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n recoverFn: DataRecoverFn<VersionedData[CurrentVersion]>;\n }) {\n this.versionChain = versionChain;\n this.migrationSteps = steps;\n this.recoverFn = recoverFn;\n }\n\n /**\n * Finalize the DataModel with initial data factory.\n *\n * The initial data factory is called when creating new blocks or when\n * migration/recovery fails and data must be reset.\n *\n * @param initialData - Factory function returning initial state (must exactly match CurrentVersion's data type)\n * @returns Finalized DataModel instance\n *\n * @example\n * .init(() => ({ numbers: [], labels: [], description: '' }))\n */\n init<S extends VersionedData[CurrentVersion]>(\n initialData: DataCreateFn<S>,\n // Compile-time check: S must have exactly the same keys as VersionedData[CurrentVersion]\n ..._noExtraKeys: Exclude<keyof S, keyof VersionedData[CurrentVersion]> extends never\n ? []\n : [never]\n ): DataModel<VersionedData[CurrentVersion]> {\n return DataModel[FROM_BUILDER]<VersionedData[CurrentVersion]>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n initialDataFn: initialData as DataCreateFn<VersionedData[CurrentVersion]>,\n recoverFn: this.recoverFn,\n });\n }\n}\n\n/**\n * Internal builder for constructing DataModel with type-safe migration chains.\n *\n * Tracks the current version through the generic type system, ensuring:\n * - Migration functions receive correctly typed input\n * - Migration functions must return the correct output type\n * - Version keys must exist in the VersionedData map\n * - All versions must be covered before calling init()\n *\n * @typeParam VersionedData - Map of version keys to their data types\n * @typeParam CurrentVersion - The current version in the migration chain\n * @typeParam RemainingVersions - Versions not yet covered by migrations\n * @internal\n */\nclass DataModelMigrationChain<\n VersionedData extends DataVersionMap,\n CurrentVersion extends keyof VersionedData & string,\n RemainingVersions extends keyof VersionedData & string = Exclude<\n keyof VersionedData & string,\n CurrentVersion\n >,\n> {\n private readonly versionChain: DataVersionKey[];\n private readonly migrationSteps: MigrationStep[];\n\n /** @internal */\n constructor({\n versionChain,\n steps = [],\n }: {\n versionChain: DataVersionKey[];\n steps?: MigrationStep[];\n }) {\n this.versionChain = versionChain;\n this.migrationSteps = steps;\n }\n\n /**\n * Add a migration step to transform data from current version to next version.\n *\n * Migration functions:\n * - Receive data typed as the current version's data type (readonly)\n * - Must return data matching the target version's data type\n * - Should be pure functions (no side effects)\n * - May throw errors (will result in data reset with warning)\n *\n * @typeParam NextVersion - The target version key (must be in RemainingVersions)\n * @param nextVersion - The version key to migrate to\n * @param fn - Migration function transforming current data to next version\n * @returns Builder with updated current version\n *\n * @example\n * .migrate(Version.V2, (data) => ({ ...data, labels: [] }))\n */\n migrate<NextVersion extends RemainingVersions>(\n nextVersion: NextVersion,\n fn: DataMigrateFn<VersionedData[CurrentVersion], VersionedData[NextVersion]>,\n ): DataModelMigrationChain<VersionedData, NextVersion, Exclude<RemainingVersions, NextVersion>> {\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 new DataModelMigrationChain<\n VersionedData,\n NextVersion,\n Exclude<RemainingVersions, NextVersion>\n >({\n versionChain: [...this.versionChain, nextVersion],\n steps: [...this.migrationSteps, step],\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 should either:\n * - Transform the data to the current version's format and return it\n * - Call `defaultRecover(version, data)` to signal unrecoverable data\n *\n * Can only be called once. After calling, only `init()` is available.\n *\n * @param fn - Recovery function that transforms unknown data or throws\n * @returns Builder with only init() method available\n *\n * @example\n * .recover((version, data) => {\n * if (version === 'legacy' && isLegacyFormat(data)) {\n * return transformLegacy(data);\n * }\n * return defaultRecover(version, data);\n * })\n */\n recover(\n fn: DataRecoverFn<VersionedData[CurrentVersion]>,\n ): DataModelBuilderWithRecover<VersionedData, CurrentVersion> {\n return new DataModelBuilderWithRecover<VersionedData, CurrentVersion>({\n versionChain: [...this.versionChain],\n steps: [...this.migrationSteps],\n recoverFn: fn,\n });\n }\n\n /**\n * Finalize the DataModel with initial data factory.\n *\n * Can only be called when all versions in VersionedData have been covered\n * by the migration chain (RemainingVersions is empty).\n *\n * The initial data factory is called when creating new blocks or when\n * migration/recovery fails and data must be reset.\n *\n * @param initialData - Factory function returning initial state (must exactly match CurrentVersion's data type)\n * @returns Finalized DataModel instance\n *\n * @example\n * .init(() => ({ numbers: [], labels: [], description: '' }))\n */\n init<S extends VersionedData[CurrentVersion]>(\n // Compile-time check: RemainingVersions must be empty (all versions covered)\n this: DataModelMigrationChain<VersionedData, CurrentVersion, never>,\n initialData: DataCreateFn<S>,\n // Compile-time check: S must have exactly the same keys as VersionedData[CurrentVersion]\n ..._noExtraKeys: Exclude<keyof S, keyof VersionedData[CurrentVersion]> extends never\n ? []\n : [never]\n ): DataModel<VersionedData[CurrentVersion]> {\n return DataModel[FROM_BUILDER]<VersionedData[CurrentVersion]>({\n versionChain: this.versionChain,\n steps: this.migrationSteps,\n initialDataFn: initialData as DataCreateFn<VersionedData[CurrentVersion]>,\n });\n }\n}\n\n/**\n * Builder entry point for creating DataModel with type-safe migrations.\n *\n * @typeParam VersionedData - Map of version keys to their data types\n *\n * @example\n * const Version = defineDataVersions({\n * V1: 'v1',\n * V2: 'v2',\n * });\n *\n * type VersionedData = {\n * [Version.V1]: { count: number };\n * [Version.V2]: { count: number; label: string };\n * };\n *\n * const dataModel = new DataModelBuilder<VersionedData>()\n * .from(Version.V1)\n * .migrate(Version.V2, (data) => ({ ...data, label: '' }))\n * .init(() => ({ count: 0, label: '' }));\n */\nexport class DataModelBuilder<VersionedData extends DataVersionMap> {\n /**\n * Start a migration chain from an initial version.\n *\n * @typeParam InitialVersion - The starting version key (inferred from argument)\n * @param initialVersion - The version key to start from\n * @returns Migration chain builder for adding migrations\n *\n * @example\n * new DataModelBuilder<VersionedData>()\n * .from(Version.V1)\n * .migrate(Version.V2, (data) => ({ ...data, newField: '' }))\n */\n from<InitialVersion extends keyof VersionedData & string>(\n initialVersion: InitialVersion,\n ): DataModelMigrationChain<\n VersionedData,\n InitialVersion,\n Exclude<keyof VersionedData & string, InitialVersion>\n > {\n return new DataModelMigrationChain<\n VersionedData,\n InitialVersion,\n Exclude<keyof VersionedData & string, InitialVersion>\n >({ 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<VersionedData>()` to create a DataModel:\n *\n * **Simple (no migrations):**\n * @example\n * const Version = defineDataVersions({ V1: DATA_MODEL_DEFAULT_VERSION });\n * type VersionedData = { [Version.V1]: BlockData };\n *\n * const dataModel = new DataModelBuilder<VersionedData>()\n * .from(Version.V1)\n * .init(() => ({ numbers: [], labels: [] }));\n *\n * **With migrations:**\n * @example\n * const Version = defineDataVersions({\n * V1: DATA_MODEL_DEFAULT_VERSION,\n * V2: 'v2',\n * V3: 'v3',\n * });\n *\n * type VersionedData = {\n * [Version.V1]: { numbers: number[] };\n * [Version.V2]: { numbers: number[]; labels: string[] };\n * [Version.V3]: { numbers: number[]; labels: string[]; description: string };\n * };\n *\n * const dataModel = new DataModelBuilder<VersionedData>()\n * .from(Version.V1)\n * .migrate(Version.V2, (data) => ({ ...data, labels: [] }))\n * .migrate(Version.V3, (data) => ({ ...data, description: '' }))\n * .recover((version, data) => {\n * if (version === 'legacy' && typeof data === 'object' && data !== null && 'numbers' in data) {\n * return { numbers: (data as { numbers: number[] }).numbers, labels: [], description: '' };\n * }\n * return defaultRecover(version, data);\n * })\n * .init(() => ({ numbers: [], labels: [], description: '' }));\n */\nexport class DataModel<State> {\n private readonly versionChain: DataVersionKey[];\n private readonly steps: MigrationStep[];\n private readonly initialDataFn: () => State;\n private readonly recoverFn: DataRecoverFn<State>;\n\n private constructor({\n versionChain,\n steps,\n initialDataFn,\n recoverFn = defaultRecover as DataRecoverFn<State>,\n }: {\n versionChain: DataVersionKey[];\n steps: MigrationStep[];\n initialDataFn: () => State;\n recoverFn?: DataRecoverFn<State>;\n }) {\n if (versionChain.length === 0) {\n throw new Error('DataModel requires at least one version key');\n }\n this.versionChain = versionChain;\n this.steps = steps;\n this.initialDataFn = initialDataFn;\n this.recoverFn = recoverFn;\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>(state: BuilderState<S>): DataModel<S> {\n return new DataModel<S>(state);\n }\n\n /**\n * The latest (current) version key in the migration chain.\n */\n get version(): DataVersionKey {\n return this.versionChain[this.versionChain.length - 1];\n }\n\n /**\n * Number of migration steps defined.\n */\n get migrationCount(): number {\n return this.steps.length;\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 makeDataVersioned(this.version, this.initialDataFn());\n }\n\n private recoverFrom(data: unknown, version: DataVersionKey): DataMigrationResult<State> {\n try {\n return { version: this.version, data: this.recoverFn(version, data) };\n } catch (error) {\n if (isDataUnrecoverableError(error)) {\n return { ...this.getDefaultData(), warning: error.message };\n }\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n ...this.getDefaultData(),\n warning: `Recover failed for version '${version}': ${errorMessage}`,\n };\n }\n }\n\n /**\n * Migrate versioned data from any version to the latest.\n *\n * - If data is already at latest version, returns as-is\n * - If version is in chain, applies needed migrations\n * - If version is unknown, calls recover function\n * - If migration/recovery fails, returns default data with warning\n *\n * @param versioned - Data with version tag\n * @returns Migration result with data at latest version\n */\n migrate(versioned: DataVersioned<unknown>): DataMigrationResult<State> {\n const { version: fromVersion, data } = versioned;\n\n if (fromVersion === this.version) {\n return { version: this.version, data: data as State };\n }\n\n const startIndex = this.versionChain.indexOf(fromVersion);\n if (startIndex < 0) {\n return this.recoverFrom(data, fromVersion);\n }\n\n let currentData: unknown = data;\n for (let i = startIndex; i < this.steps.length; i++) {\n const step = this.steps[i];\n try {\n currentData = step.migrate(currentData);\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n return {\n ...this.getDefaultData(),\n warning: `Migration ${step.fromVersion}→${step.toVersion} failed: ${errorMessage}`,\n };\n }\n }\n\n return { version: this.version, data: currentData as State };\n }\n\n /**\n * Register callbacks for use in the VM.\n * Called by BlockModelV3.create() to set up internal callbacks.\n *\n * @internal\n */\n registerCallbacks(): void {\n tryRegisterCallback('__pl_data_initial', () => this.initialDataFn());\n tryRegisterCallback('__pl_data_upgrade', (versioned: DataVersioned<unknown>) =>\n this.migrate(versioned),\n );\n tryRegisterCallback('__pl_storage_initial', () => {\n const { version, data } = this.getDefaultData();\n const storage = createBlockStorage(data, version);\n return JSON.stringify(storage);\n });\n }\n}\n"],"names":[],"mappings":"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"}
@@ -28,7 +28,14 @@ class BlockModelV3 {
28
28
  * Creates a new BlockModelV3 builder with the specified data model and options.
29
29
  *
30
30
  * @example
31
- * const dataModel = DataModel.create<BlockData>(() => ({ numbers: [], labels: [] }));
31
+ * const Version = defineDataVersions({ V1: DATA_MODEL_DEFAULT_VERSION });
32
+ *
33
+ * type VersionedData = { [Version.V1]: BlockData };
34
+ *
35
+ * const dataModel = new DataModelBuilder<VersionedData>()
36
+ * .from(Version.V1)
37
+ * .init(() => ({ numbers: [], labels: [] }));
38
+ *
32
39
  * BlockModelV3.create({ dataModel })
33
40
  * .args((data) => ({ numbers: data.numbers }))
34
41
  * .sections(() => [{ type: 'link', href: '/', label: 'Main' }])
@@ -60,7 +67,11 @@ class BlockModelV3 {
60
67
  ...this.config,
61
68
  outputs: {
62
69
  ...this.config.outputs,
63
- [key]: internal.createAndRegisterRenderLambda({ handle: `output#${key}`, lambda: () => cfgOrRf(new api.RenderCtx()), ...flags }),
70
+ [key]: internal.createAndRegisterRenderLambda({
71
+ handle: `output#${key}`,
72
+ lambda: () => cfgOrRf(new api.RenderCtx()),
73
+ ...flags,
74
+ }),
64
75
  },
65
76
  });
66
77
  }
@@ -114,7 +125,10 @@ class BlockModelV3 {
114
125
  prerunArgs(fn) {
115
126
  return new BlockModelV3({
116
127
  ...this.config,
117
- prerunArgs: internal.createAndRegisterRenderLambda({ handle: 'prerunArgs', lambda: fn }),
128
+ prerunArgs: internal.createAndRegisterRenderLambda({
129
+ handle: 'prerunArgs',
130
+ lambda: fn,
131
+ }),
118
132
  });
119
133
  }
120
134
  /** Sets the lambda to generate list of sections in the left block overviews panel. */
@@ -129,19 +143,28 @@ class BlockModelV3 {
129
143
  title(rf) {
130
144
  return new BlockModelV3({
131
145
  ...this.config,
132
- title: internal.createAndRegisterRenderLambda({ handle: 'title', lambda: () => rf(new api.RenderCtx()) }),
146
+ title: internal.createAndRegisterRenderLambda({
147
+ handle: 'title',
148
+ lambda: () => rf(new api.RenderCtx()),
149
+ }),
133
150
  });
134
151
  }
135
152
  subtitle(rf) {
136
153
  return new BlockModelV3({
137
154
  ...this.config,
138
- subtitle: internal.createAndRegisterRenderLambda({ handle: 'subtitle', lambda: () => rf(new api.RenderCtx()) }),
155
+ subtitle: internal.createAndRegisterRenderLambda({
156
+ handle: 'subtitle',
157
+ lambda: () => rf(new api.RenderCtx()),
158
+ }),
139
159
  });
140
160
  }
141
161
  tags(rf) {
142
162
  return new BlockModelV3({
143
163
  ...this.config,
144
- tags: internal.createAndRegisterRenderLambda({ handle: 'tags', lambda: () => rf(new api.RenderCtx()) }),
164
+ tags: internal.createAndRegisterRenderLambda({
165
+ handle: 'tags',
166
+ lambda: () => rf(new api.RenderCtx()),
167
+ }),
145
168
  });
146
169
  }
147
170
  /** Sets or overrides feature flags for the block. */
@@ -158,7 +181,10 @@ class BlockModelV3 {
158
181
  enriches(lambda) {
159
182
  return new BlockModelV3({
160
183
  ...this.config,
161
- enrichmentTargets: internal.createAndRegisterRenderLambda({ handle: 'enrichmentTargets', lambda: lambda }),
184
+ enrichmentTargets: internal.createAndRegisterRenderLambda({
185
+ handle: 'enrichmentTargets',
186
+ lambda: lambda,
187
+ }),
162
188
  });
163
189
  }
164
190
  /** Renders all provided block settings into a pre-configured platforma API
@@ -198,7 +224,10 @@ class BlockModelV3 {
198
224
  sdkVersion: version.PlatformaSDKVersion,
199
225
  renderingMode: this.config.renderingMode,
200
226
  sections: this.config.sections,
201
- outputs: Object.fromEntries(Object.entries(this.config.outputs).map(([key, value]) => [key, normalization.downgradeCfgOrLambda(value)])),
227
+ outputs: Object.fromEntries(Object.entries(this.config.outputs).map(([key, value]) => [
228
+ key,
229
+ normalization.downgradeCfgOrLambda(value),
230
+ ])),
202
231
  };
203
232
  globalThis.platformaApiVersion = apiVersion;
204
233
  if (!internal.isInUI())
@@ -207,12 +236,17 @@ class BlockModelV3 {
207
236
  // normal operation inside the UI
208
237
  else
209
238
  return {
210
- ...internal.getPlatformaInstance({ sdkVersion: version.PlatformaSDKVersion, apiVersion }),
239
+ ...internal.getPlatformaInstance({
240
+ sdkVersion: version.PlatformaSDKVersion,
241
+ apiVersion,
242
+ }),
211
243
  blockModelInfo: {
212
- outputs: Object.fromEntries(Object.entries(this.config.outputs)
213
- .map(([key, value]) => [key, {
244
+ outputs: Object.fromEntries(Object.entries(this.config.outputs).map(([key, value]) => [
245
+ key,
246
+ {
214
247
  withStatus: Boolean(types.isConfigLambda(value) && value.withStatus),
215
- }])),
248
+ },
249
+ ])),
216
250
  },
217
251
  };
218
252
  }