@platforma-sdk/model 1.58.0 → 1.58.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/block_migrations.cjs +60 -77
  2. package/dist/block_migrations.cjs.map +1 -1
  3. package/dist/block_migrations.d.ts +35 -32
  4. package/dist/block_migrations.d.ts.map +1 -1
  5. package/dist/block_migrations.js +60 -78
  6. package/dist/block_migrations.js.map +1 -1
  7. package/dist/block_model.cjs +1 -1
  8. package/dist/block_model.cjs.map +1 -1
  9. package/dist/block_model.d.ts +1 -1
  10. package/dist/block_model.js +1 -1
  11. package/dist/block_model.js.map +1 -1
  12. package/dist/block_storage.cjs +6 -6
  13. package/dist/block_storage.cjs.map +1 -1
  14. package/dist/block_storage.d.ts +7 -7
  15. package/dist/block_storage.d.ts.map +1 -1
  16. package/dist/block_storage.js +6 -6
  17. package/dist/block_storage.js.map +1 -1
  18. package/dist/block_storage_callbacks.cjs +1 -0
  19. package/dist/block_storage_callbacks.cjs.map +1 -1
  20. package/dist/block_storage_callbacks.d.ts +4 -3
  21. package/dist/block_storage_callbacks.d.ts.map +1 -1
  22. package/dist/block_storage_callbacks.js +1 -0
  23. package/dist/block_storage_callbacks.js.map +1 -1
  24. package/dist/index.cjs +1 -3
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -2
  29. package/dist/package.json.cjs +1 -1
  30. package/dist/package.json.js +1 -1
  31. package/dist/plugin_model.cjs +2 -2
  32. package/dist/plugin_model.cjs.map +1 -1
  33. package/dist/plugin_model.d.ts +2 -2
  34. package/dist/plugin_model.js +2 -2
  35. package/dist/plugin_model.js.map +1 -1
  36. package/package.json +5 -5
  37. package/src/block_migrations.test.ts +109 -12
  38. package/src/block_migrations.ts +63 -87
  39. package/src/block_model.ts +1 -1
  40. package/src/block_storage.test.ts +8 -8
  41. package/src/block_storage.ts +10 -10
  42. package/src/block_storage_callbacks.ts +4 -3
  43. package/src/index.ts +22 -1
  44. package/src/plugin_model.test.ts +2 -2
  45. package/src/plugin_model.ts +2 -2
@@ -1,3 +1,5 @@
1
+ import { DATA_MODEL_LEGACY_VERSION } from "./block_storage";
2
+
1
3
  export type DataVersionKey = string;
2
4
  export type DataMigrateFn<From, To> = (prev: Readonly<From>) => To;
3
5
  export type DataCreateFn<T> = () => T;
@@ -14,10 +16,13 @@ export function makeDataVersioned<T>(version: DataVersionKey, data: T): DataVers
14
16
  return { version, data };
15
17
  }
16
18
 
17
- /** Result of migration operation, may include warning if migration failed */
18
- export type DataMigrationResult<T> = DataVersioned<T> & {
19
- warning?: string;
20
- };
19
+ /** Thrown when a migration step fails. */
20
+ export class DataMigrationError extends Error {
21
+ name = "DataMigrationError";
22
+ constructor(message: string) {
23
+ super(message);
24
+ }
25
+ }
21
26
 
22
27
  /** Thrown by recover() to signal unrecoverable data. */
23
28
  export class DataUnrecoverableError extends Error {
@@ -68,14 +73,11 @@ type BuilderState<S> = {
68
73
  /** Index of the first step to run after recovery. Equals the number of steps
69
74
  * present at the time recover() was called. */
70
75
  recoverFromIndex?: number;
71
- /** Transforms legacy V1 model data ({ args, uiState }) at the initial version. */
72
- upgradeLegacyFn?: (data: unknown) => unknown;
73
76
  };
74
77
 
75
78
  type RecoverState = {
76
79
  recoverFn?: (version: DataVersionKey, data: unknown) => unknown;
77
80
  recoverFromIndex?: number;
78
- upgradeLegacyFn?: (data: unknown) => unknown;
79
81
  };
80
82
 
81
83
  /**
@@ -148,7 +150,6 @@ abstract class MigrationChainBase<Current> {
148
150
  class DataModelMigrationChainWithRecover<Current> extends MigrationChainBase<Current> {
149
151
  private readonly recoverFn?: (version: DataVersionKey, data: unknown) => unknown;
150
152
  private readonly recoverFromIndex?: number;
151
- private readonly upgradeLegacyFn?: (data: unknown) => unknown;
152
153
 
153
154
  /** @internal */
154
155
  constructor(state: {
@@ -156,19 +157,16 @@ class DataModelMigrationChainWithRecover<Current> extends MigrationChainBase<Cur
156
157
  steps: MigrationStep[];
157
158
  recoverFn?: (version: DataVersionKey, data: unknown) => unknown;
158
159
  recoverFromIndex?: number;
159
- upgradeLegacyFn?: (data: unknown) => unknown;
160
160
  }) {
161
161
  super(state);
162
162
  this.recoverFn = state.recoverFn;
163
163
  this.recoverFromIndex = state.recoverFromIndex;
164
- this.upgradeLegacyFn = state.upgradeLegacyFn;
165
164
  }
166
165
 
167
166
  protected override recoverState(): RecoverState {
168
167
  return {
169
168
  recoverFn: this.recoverFn,
170
169
  recoverFromIndex: this.recoverFromIndex,
171
- upgradeLegacyFn: this.upgradeLegacyFn,
172
170
  };
173
171
  }
174
172
 
@@ -186,7 +184,6 @@ class DataModelMigrationChainWithRecover<Current> extends MigrationChainBase<Cur
186
184
  steps,
187
185
  recoverFn: this.recoverFn,
188
186
  recoverFromIndex: this.recoverFromIndex,
189
- upgradeLegacyFn: this.upgradeLegacyFn,
190
187
  });
191
188
  }
192
189
  }
@@ -239,10 +236,9 @@ class DataModelMigrationChain<Current> extends MigrationChainBase<Current> {
239
236
  * steps added after recover() will then run on the recovered data.
240
237
  *
241
238
  * Can only be called once — the returned chain has no recover() method.
242
- * Mutually exclusive with upgradeLegacy().
243
239
  *
244
240
  * @param fn - Recovery function returning Current (the type at this chain position)
245
- * @returns Builder with migrate() and init() but without recover() or upgradeLegacy()
241
+ * @returns Builder with migrate() and init() but without recover()
246
242
  *
247
243
  * @example
248
244
  * // Recover between migrations — recovered data goes through v3 migration
@@ -263,19 +259,28 @@ class DataModelMigrationChain<Current> extends MigrationChainBase<Current> {
263
259
  recoverFromIndex: this.migrationSteps.length,
264
260
  });
265
261
  }
262
+ }
266
263
 
264
+ /**
265
+ * Initial migration chain returned by `.from()`.
266
+ * Extends DataModelMigrationChain with `upgradeLegacy()` — available only before
267
+ * any `.migrate()` calls, since legacy data always arrives at the initial version.
268
+ *
269
+ * @typeParam Current - Data type at the initial version
270
+ * @internal
271
+ */
272
+ class DataModelInitialChain<Current> extends DataModelMigrationChain<Current> {
267
273
  /**
268
274
  * Handle legacy V1 model state ({ args, uiState }) when upgrading a block from
269
275
  * BlockModel V1 to BlockModelV3.
270
276
  *
271
- * When a V1 block is upgraded, its stored state `{ args, uiState }` arrives at the
272
- * initial version (DATA_MODEL_DEFAULT_VERSION) in the migration chain. This method
273
- * detects the legacy shape and transforms it to the current chain type using the
274
- * provided typed callback. Non-legacy data passes through unchanged.
277
+ * When a V1 block is upgraded, its stored state `{ args, uiState }` is normalized
278
+ * to the internal default version. This method inserts a migration step from that
279
+ * internal version to the version specified in `.from()`, using the provided typed
280
+ * callback to transform the legacy shape. Non-legacy data passes through unchanged.
275
281
  *
276
- * Should be called right after `.from()` (before any `.migrate()` calls), since legacy
277
- * data always arrives at the initial version. Any `.migrate()` steps added after
278
- * `upgradeLegacy()` will run on the transformed result.
282
+ * Must be called right after `.from()` not available after `.migrate()` calls.
283
+ * Any `.migrate()` steps added after `upgradeLegacy()` will run on the transformed result.
279
284
  *
280
285
  * Can only be called once — the returned chain has no upgradeLegacy() method.
281
286
  * Mutually exclusive with recover().
@@ -291,7 +296,7 @@ class DataModelMigrationChain<Current> extends MigrationChainBase<Current> {
291
296
  * type BlockData = { inputFile: string; threshold: number; selectedTab: string };
292
297
  *
293
298
  * const dataModel = new DataModelBuilder()
294
- * .from<BlockData>(DATA_MODEL_DEFAULT_VERSION)
299
+ * .from<BlockData>("v1")
295
300
  * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({
296
301
  * inputFile: args.inputFile,
297
302
  * threshold: args.threshold,
@@ -308,10 +313,18 @@ class DataModelMigrationChain<Current> extends MigrationChainBase<Current> {
308
313
  }
309
314
  return data;
310
315
  };
316
+
317
+ // Insert DATA_MODEL_LEGACY_VERSION as the true first version
318
+ // with a migration step that transforms legacy data to the user's initial version.
319
+ const initialVersion = this.versionChain[0];
320
+ const step: MigrationStep = {
321
+ fromVersion: DATA_MODEL_LEGACY_VERSION,
322
+ toVersion: initialVersion,
323
+ migrate: wrappedFn,
324
+ };
311
325
  return new DataModelMigrationChainWithRecover<Current>({
312
- versionChain: this.versionChain,
313
- steps: this.migrationSteps,
314
- upgradeLegacyFn: wrappedFn,
326
+ versionChain: [DATA_MODEL_LEGACY_VERSION, ...this.versionChain],
327
+ steps: [step, ...this.migrationSteps],
315
328
  });
316
329
  }
317
330
  }
@@ -322,13 +335,13 @@ class DataModelMigrationChain<Current> extends MigrationChainBase<Current> {
322
335
  * @example
323
336
  * // Simple (no migrations):
324
337
  * const dataModel = new DataModelBuilder()
325
- * .from<BlockData>(DATA_MODEL_DEFAULT_VERSION)
338
+ * .from<BlockData>("v1")
326
339
  * .init(() => ({ numbers: [] }));
327
340
  *
328
341
  * @example
329
342
  * // With migrations:
330
343
  * const dataModel = new DataModelBuilder()
331
- * .from<BlockDataV1>(DATA_MODEL_DEFAULT_VERSION)
344
+ * .from<BlockDataV1>("v1")
332
345
  * .migrate<BlockDataV2>("v2", (v1) => ({ ...v1, labels: [] }))
333
346
  * .migrate<BlockDataV3>("v3", (v2) => ({ ...v2, description: '' }))
334
347
  * .init(() => ({ numbers: [], labels: [], description: '' }));
@@ -336,7 +349,7 @@ class DataModelMigrationChain<Current> extends MigrationChainBase<Current> {
336
349
  * @example
337
350
  * // With recover() between migrations — recovered data goes through remaining migrations:
338
351
  * const dataModelChain = new DataModelBuilder()
339
- * .from<BlockDataV1>(DATA_MODEL_DEFAULT_VERSION)
352
+ * .from<BlockDataV1>("v1")
340
353
  * .migrate<BlockDataV2>("v2", (v1) => ({ ...v1, labels: [] }));
341
354
  *
342
355
  * // recover() placed before the v3 migration: recovered data goes through v3
@@ -355,7 +368,7 @@ class DataModelMigrationChain<Current> extends MigrationChainBase<Current> {
355
368
  * type BlockData = { inputFile: string; selectedTab: string };
356
369
  *
357
370
  * const dataModel = new DataModelBuilder()
358
- * .from<BlockData>(DATA_MODEL_DEFAULT_VERSION)
371
+ * .from<BlockData>("v1")
359
372
  * .upgradeLegacy<OldArgs, OldUiState>(({ args, uiState }) => ({
360
373
  * inputFile: args.inputFile,
361
374
  * selectedTab: uiState.selectedTab,
@@ -367,11 +380,11 @@ export class DataModelBuilder {
367
380
  * Start the migration chain with the given initial data type and version key.
368
381
  *
369
382
  * @typeParam T - Data type for the initial version
370
- * @param initialVersion - Version key string (e.g. DATA_MODEL_DEFAULT_VERSION or "v1")
383
+ * @param initialVersion - Version key string (e.g. "v1")
371
384
  * @returns Migration chain builder
372
385
  */
373
- from<T>(initialVersion: string): DataModelMigrationChain<T> {
374
- return new DataModelMigrationChain<T>({ versionChain: [initialVersion] });
386
+ from<T>(initialVersion: string): DataModelInitialChain<T> {
387
+ return new DataModelInitialChain<T>({ versionChain: [initialVersion] });
375
388
  }
376
389
  }
377
390
 
@@ -385,7 +398,7 @@ export class DataModelBuilder {
385
398
  * // With recover() between migrations:
386
399
  * // Recovered data (V2) goes through the v2→v3 migration automatically.
387
400
  * const dataModel = new DataModelBuilder()
388
- * .from<V1>(DATA_MODEL_DEFAULT_VERSION)
401
+ * .from<V1>("v1")
389
402
  * .migrate<V2>("v2", (v1) => ({ ...v1, label: "" }))
390
403
  * .recover((version, data) => {
391
404
  * if (version === "legacy") return transformLegacy(data); // returns V2
@@ -403,8 +416,6 @@ export class DataModel<State> {
403
416
  private readonly initialDataFn: () => State;
404
417
  private readonly recoverFn: (version: DataVersionKey, data: unknown) => unknown;
405
418
  private readonly recoverFromIndex: number;
406
- /** Transforms legacy V1 model data at the initial version before running migrations. */
407
- private readonly upgradeLegacyFn?: (data: unknown) => unknown;
408
419
 
409
420
  private constructor({
410
421
  versionChain,
@@ -412,7 +423,6 @@ export class DataModel<State> {
412
423
  initialDataFn,
413
424
  recoverFn = defaultRecover,
414
425
  recoverFromIndex,
415
- upgradeLegacyFn,
416
426
  }: BuilderState<State>) {
417
427
  if (versionChain.length === 0) {
418
428
  throw new Error("DataModel requires at least one version key");
@@ -423,7 +433,6 @@ export class DataModel<State> {
423
433
  this.initialDataFn = initialDataFn;
424
434
  this.recoverFn = recoverFn;
425
435
  this.recoverFromIndex = recoverFromIndex ?? steps.length;
426
- this.upgradeLegacyFn = upgradeLegacyFn;
427
436
  }
428
437
 
429
438
  /**
@@ -457,34 +466,15 @@ export class DataModel<State> {
457
466
  return makeDataVersioned(this.latestVersion, this.initialDataFn());
458
467
  }
459
468
 
460
- private recoverFrom(data: unknown, version: DataVersionKey): DataMigrationResult<State> {
469
+ private recoverFrom(data: unknown, version: DataVersionKey): DataVersioned<State> {
461
470
  // Step 1: call the recover function to get data at the recover point
462
- let currentData: unknown;
463
- try {
464
- currentData = this.recoverFn(version, data);
465
- } catch (error) {
466
- if (isDataUnrecoverableError(error)) {
467
- return { ...this.getDefaultData(), warning: error.message };
468
- }
469
- const errorMessage = error instanceof Error ? error.message : String(error);
470
- return {
471
- ...this.getDefaultData(),
472
- warning: `Recover failed for version '${version}': ${errorMessage}`,
473
- };
474
- }
471
+ // Let errors (including DataUnrecoverableError) propagate to the caller.
472
+ let currentData: unknown = this.recoverFn(version, data);
475
473
 
476
474
  // Step 2: run any migrations that were added after recover() in the chain
477
475
  for (let i = this.recoverFromIndex; i < this.steps.length; i++) {
478
476
  const step = this.steps[i];
479
- try {
480
- currentData = step.migrate(currentData);
481
- } catch (error) {
482
- const errorMessage = error instanceof Error ? error.message : String(error);
483
- return {
484
- ...this.getDefaultData(),
485
- warning: `Migration ${step.fromVersion}→${step.toVersion} failed: ${errorMessage}`,
486
- };
487
- }
477
+ currentData = step.migrate(currentData);
488
478
  }
489
479
 
490
480
  return { version: this.latestVersion, data: currentData as State };
@@ -494,13 +484,14 @@ export class DataModel<State> {
494
484
  * Migrate versioned data from any version to the latest.
495
485
  *
496
486
  * - If version is in chain, applies needed migrations (O(1) lookup)
497
- * - If version is unknown, calls recover function then runs remaining migrations
498
- * - If migration/recovery fails, returns default data with warning
487
+ * - If version is unknown, attempts recovery; falls back to initial data
488
+ * - If a migration step fails, throws so the caller can preserve original data
499
489
  *
500
490
  * @param versioned - Data with version tag
501
- * @returns Migration result with data at latest version
491
+ * @returns Migrated data at the latest version
492
+ * @throws If a migration step from a known version fails
502
493
  */
503
- migrate(versioned: DataVersioned<unknown>): DataMigrationResult<State> {
494
+ migrate(versioned: DataVersioned<unknown>): DataVersioned<State> {
504
495
  const { version: fromVersion, data } = versioned;
505
496
 
506
497
  if (fromVersion === this.latestVersion) {
@@ -509,35 +500,20 @@ export class DataModel<State> {
509
500
 
510
501
  const startIndex = this.stepsByFromVersion.get(fromVersion);
511
502
  if (startIndex === undefined) {
512
- return this.recoverFrom(data, fromVersion);
513
- }
514
-
515
- let currentData: unknown = data;
516
-
517
- // Legacy V1 upgrade: detect and transform { args, uiState } at the initial version
518
- if (startIndex === 0 && this.upgradeLegacyFn) {
519
503
  try {
520
- currentData = this.upgradeLegacyFn(currentData);
521
- } catch (error) {
522
- const errorMessage = error instanceof Error ? error.message : String(error);
523
- return {
524
- ...this.getDefaultData(),
525
- warning: `Legacy upgrade failed: ${errorMessage}`,
526
- };
504
+ return this.recoverFrom(data, fromVersion);
505
+ } catch {
506
+ // Recovery failed (unknown version, recover fn threw, or post-recover
507
+ // migration failed) — reset to initial data rather than blocking the update.
508
+ return this.getDefaultData();
527
509
  }
528
510
  }
529
511
 
512
+ let currentData: unknown = data;
513
+
530
514
  for (let i = startIndex; i < this.steps.length; i++) {
531
515
  const step = this.steps[i];
532
- try {
533
- currentData = step.migrate(currentData);
534
- } catch (error) {
535
- const errorMessage = error instanceof Error ? error.message : String(error);
536
- return {
537
- ...this.getDefaultData(),
538
- warning: `Migration ${step.fromVersion}→${step.toVersion} failed: ${errorMessage}`,
539
- };
540
- }
516
+ currentData = step.migrate(currentData);
541
517
  }
542
518
 
543
519
  return { version: this.latestVersion, data: currentData as State };
@@ -114,7 +114,7 @@ export class BlockModelV3<
114
114
  *
115
115
  * @example
116
116
  * const dataModel = new DataModelBuilder()
117
- * .from<BlockData>(DATA_MODEL_DEFAULT_VERSION)
117
+ * .from<BlockData>("v1")
118
118
  * .init(() => ({ numbers: [], labels: [] }));
119
119
  *
120
120
  * BlockModelV3.create(dataModel)
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
2
2
  import {
3
3
  BLOCK_STORAGE_KEY,
4
4
  BLOCK_STORAGE_SCHEMA_VERSION,
5
- DATA_MODEL_DEFAULT_VERSION,
5
+ DATA_MODEL_LEGACY_VERSION,
6
6
  createBlockStorage,
7
7
  getPluginData,
8
8
  getStorageData,
@@ -72,7 +72,7 @@ describe("BlockStorage", () => {
72
72
  it("should create storage with discriminator key and default values", () => {
73
73
  const storage = createBlockStorage();
74
74
  expect(storage[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
75
- expect(storage.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
75
+ expect(storage.__dataVersion).toBe(DATA_MODEL_LEGACY_VERSION);
76
76
  expect(storage.__data).toEqual({});
77
77
  });
78
78
 
@@ -80,7 +80,7 @@ describe("BlockStorage", () => {
80
80
  const data = { numbers: [1, 2, 3] };
81
81
  const storage = createBlockStorage(data);
82
82
  expect(storage[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
83
- expect(storage.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
83
+ expect(storage.__dataVersion).toBe(DATA_MODEL_LEGACY_VERSION);
84
84
  expect(storage.__data).toEqual(data);
85
85
  });
86
86
 
@@ -136,7 +136,7 @@ describe("BlockStorage", () => {
136
136
  const legacyData = { numbers: [1, 2, 3], name: "test" };
137
137
  const normalized = normalizeBlockStorage(legacyData);
138
138
  expect(normalized[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
139
- expect(normalized.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
139
+ expect(normalized.__dataVersion).toBe(DATA_MODEL_LEGACY_VERSION);
140
140
  expect(normalized.__data).toEqual(legacyData);
141
141
  expect(normalized.__pluginRegistry).toEqual({});
142
142
  expect(normalized.__plugins).toEqual({});
@@ -145,14 +145,14 @@ describe("BlockStorage", () => {
145
145
  it("should wrap primitive legacy data", () => {
146
146
  const normalized = normalizeBlockStorage("simple string");
147
147
  expect(normalized[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
148
- expect(normalized.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
148
+ expect(normalized.__dataVersion).toBe(DATA_MODEL_LEGACY_VERSION);
149
149
  expect(normalized.__data).toBe("simple string");
150
150
  });
151
151
 
152
152
  it("should wrap null legacy data", () => {
153
153
  const normalized = normalizeBlockStorage(null);
154
154
  expect(normalized[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
155
- expect(normalized.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
155
+ expect(normalized.__dataVersion).toBe(DATA_MODEL_LEGACY_VERSION);
156
156
  expect(normalized.__data).toBeNull();
157
157
  });
158
158
  });
@@ -188,7 +188,7 @@ describe("BlockStorage", () => {
188
188
  });
189
189
  expect(storage.__plugins).toEqual({
190
190
  table1: {
191
- __dataVersion: DATA_MODEL_DEFAULT_VERSION,
191
+ __dataVersion: DATA_MODEL_LEGACY_VERSION,
192
192
  __data: { columns: ["a", "b"] },
193
193
  },
194
194
  });
@@ -254,7 +254,7 @@ describe("BlockStorage", () => {
254
254
  value: { foo: "bar" },
255
255
  });
256
256
  expect(updated.__plugins?.plugin1).toEqual({
257
- __dataVersion: DATA_MODEL_DEFAULT_VERSION,
257
+ __dataVersion: DATA_MODEL_LEGACY_VERSION,
258
258
  __data: { foo: "bar" },
259
259
  });
260
260
  });
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { Branded } from "@milaboratories/pl-model-common";
13
- import type { DataMigrationResult, DataVersioned } from "./block_migrations";
13
+ import type { DataVersioned } from "./block_migrations";
14
14
  import type { PluginHandle, PluginFactoryLike, InferFactoryData } from "./plugin_handle";
15
15
 
16
16
  // =============================================================================
@@ -33,7 +33,7 @@ export const BLOCK_STORAGE_SCHEMA_VERSION = "v1";
33
33
  * Default data version for new blocks without migrations.
34
34
  * Unique identifier ensures blocks are created via DataModel API.
35
35
  */
36
- export const DATA_MODEL_DEFAULT_VERSION = "__pl_v1_d4e8f2a1__";
36
+ export const DATA_MODEL_LEGACY_VERSION = "__pl_v1_d4e8f2a1__";
37
37
 
38
38
  /**
39
39
  * Type for valid schema versions
@@ -100,12 +100,12 @@ export function isBlockStorage(value: unknown): value is BlockStorage {
100
100
  * Creates a BlockStorage with the given initial data
101
101
  *
102
102
  * @param initialData - The initial data value (defaults to empty object)
103
- * @param version - The initial data version key (defaults to DATA_MODEL_DEFAULT_VERSION)
103
+ * @param version - The initial data version key (defaults to DATA_MODEL_LEGACY_VERSION)
104
104
  * @returns A new BlockStorage instance with discriminator key
105
105
  */
106
106
  export function createBlockStorage<TState = unknown>(
107
107
  initialData: TState = {} as TState,
108
- version: string = DATA_MODEL_DEFAULT_VERSION,
108
+ version: string = DATA_MODEL_LEGACY_VERSION,
109
109
  ): BlockStorage<TState> {
110
110
  return {
111
111
  [BLOCK_STORAGE_KEY]: BLOCK_STORAGE_SCHEMA_VERSION,
@@ -132,7 +132,7 @@ export function normalizeBlockStorage<TState = unknown>(raw: unknown): BlockStor
132
132
  // Fix for early released version where __dataVersion was a number
133
133
  __dataVersion:
134
134
  typeof storage.__dataVersion === "number"
135
- ? DATA_MODEL_DEFAULT_VERSION
135
+ ? DATA_MODEL_LEGACY_VERSION
136
136
  : storage.__dataVersion,
137
137
  // Ensure plugin fields have defaults
138
138
  __pluginRegistry: storage.__pluginRegistry ?? {},
@@ -197,7 +197,7 @@ export function updateStorageData<TValue = unknown>(
197
197
  const { pluginId, value } = payload;
198
198
  const currentPlugins = storage.__plugins ?? {};
199
199
  const existingEntry = currentPlugins[pluginId];
200
- const version = existingEntry?.__dataVersion ?? DATA_MODEL_DEFAULT_VERSION;
200
+ const version = existingEntry?.__dataVersion ?? DATA_MODEL_LEGACY_VERSION;
201
201
  return {
202
202
  ...storage,
203
203
  __plugins: {
@@ -258,13 +258,13 @@ export type MigrationResult<TState> = MigrationSuccess<TState> | MigrationFailur
258
258
  * Conversion to internal VersionedData format is handled by migrateBlockStorage().
259
259
  */
260
260
  export interface MigrateBlockStorageConfig {
261
- /** Migrate block data from any version to latest */
262
- migrateBlockData: (versioned: DataVersioned<unknown>) => DataMigrationResult<unknown>;
263
- /** Migrate each plugin's data. Return undefined to remove the plugin. */
261
+ /** Migrate block data from any version to latest. Throws on failure. */
262
+ migrateBlockData: (versioned: DataVersioned<unknown>) => DataVersioned<unknown>;
263
+ /** Migrate each plugin's data. Return undefined to remove the plugin. Throws on failure. */
264
264
  migratePluginData: (
265
265
  handle: PluginHandle,
266
266
  versioned: DataVersioned<unknown>,
267
- ) => DataMigrationResult<unknown> | undefined;
267
+ ) => DataVersioned<unknown> | undefined;
268
268
  /** The new plugin registry after migration (pluginId -> pluginName) */
269
269
  newPluginRegistry: PluginRegistry;
270
270
  /** Factory to create initial data for new plugins */
@@ -27,7 +27,7 @@ import {
27
27
  import type { PluginHandle } from "./plugin_handle";
28
28
 
29
29
  import { stringifyJson, type StringifiedJson } from "@milaboratories/pl-model-common";
30
- import type { DataMigrationResult, DataVersioned } from "./block_migrations";
30
+ import type { DataVersioned } from "./block_migrations";
31
31
 
32
32
  // =============================================================================
33
33
  // Hook interfaces for dependency injection
@@ -35,12 +35,12 @@ import type { DataMigrationResult, DataVersioned } from "./block_migrations";
35
35
 
36
36
  /** Dependencies for storage migration */
37
37
  export interface MigrationHooks {
38
- migrateBlockData: (versioned: DataVersioned<unknown>) => DataMigrationResult<unknown>;
38
+ migrateBlockData: (versioned: DataVersioned<unknown>) => DataVersioned<unknown>;
39
39
  getPluginRegistry: () => PluginRegistry;
40
40
  migratePluginData: (
41
41
  handle: PluginHandle,
42
42
  versioned: DataVersioned<unknown>,
43
- ) => DataMigrationResult<unknown> | undefined;
43
+ ) => DataVersioned<unknown> | undefined;
44
44
  createPluginData: (handle: PluginHandle) => DataVersioned<unknown>;
45
45
  }
46
46
 
@@ -229,6 +229,7 @@ export function migrateStorage(
229
229
  *
230
230
  * @param hooks - Dependencies for creating initial block and plugin data
231
231
  * @returns Initial storage as branded JSON string
232
+ * @throws If initialDataFn or createPluginData throws
232
233
  */
233
234
  export function createInitialStorage(hooks: InitialStorageHooks): StringifiedJson<BlockStorage> {
234
235
  const blockDefault = hooks.getDefaultBlockData();
package/src/index.ts CHANGED
@@ -1,7 +1,27 @@
1
1
  export * from "./block_state_patch";
2
2
  export * from "./block_state_util";
3
3
  export * from "./plugin_handle";
4
- export * from "./block_storage";
4
+ export {
5
+ type BlockStorageSchemaVersion,
6
+ type PluginName,
7
+ type PluginRegistry,
8
+ type VersionedData,
9
+ type BlockStorage,
10
+ isBlockStorage,
11
+ createBlockStorage,
12
+ normalizeBlockStorage,
13
+ getStorageData,
14
+ deriveDataFromStorage,
15
+ type MutateStoragePayload,
16
+ updateStorageData,
17
+ type StorageDebugView,
18
+ type MigrationSuccess,
19
+ type MigrationFailure,
20
+ type MigrationResult,
21
+ type MigrateBlockStorageConfig,
22
+ migrateBlockStorage,
23
+ getPluginData,
24
+ } from "./block_storage";
5
25
  export * from "./block_storage_facade";
6
26
  export * from "./block_model_legacy";
7
27
  export { BlockModelV3 } from "./block_model";
@@ -9,6 +29,7 @@ export type { PluginInstance, ParamsInput } from "./block_model";
9
29
  export {
10
30
  DataModel,
11
31
  DataModelBuilder,
32
+ DataMigrationError,
12
33
  DataUnrecoverableError,
13
34
  isDataUnrecoverableError,
14
35
  defaultRecover,
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
2
2
  import { PluginModel } from "./plugin_model";
3
3
  import type { PluginRenderCtx } from "./plugin_model";
4
4
  import { DataModelBuilder } from "./block_migrations";
5
- import { DATA_MODEL_DEFAULT_VERSION, type PluginName } from "./block_storage";
5
+ import { type PluginName } from "./block_storage";
6
6
  import type { ResultPool } from "./render";
7
7
 
8
8
  // =============================================================================
@@ -11,7 +11,7 @@ import type { ResultPool } from "./render";
11
11
 
12
12
  type Data = { count: number; label: string };
13
13
 
14
- const dataModelChain = new DataModelBuilder().from<Data>(DATA_MODEL_DEFAULT_VERSION);
14
+ const dataModelChain = new DataModelBuilder().from<Data>("v1");
15
15
 
16
16
  // Mock ResultPool for testing
17
17
  const mockResultPool = {} as ResultPool;
@@ -83,7 +83,7 @@ export class PluginModel<
83
83
  * @returns PluginModelBuilder for chaining output definitions
84
84
  *
85
85
  * @example
86
- * const dataModelChain = new DataModelBuilder().from<MyData>(DATA_MODEL_DEFAULT_VERSION);
86
+ * const dataModelChain = new DataModelBuilder().from<MyData>("v1");
87
87
  *
88
88
  * const myPlugin = PluginModel.define({
89
89
  * name: 'myPlugin' as PluginName,
@@ -169,7 +169,7 @@ class PluginModelFactory<
169
169
  * @typeParam Outputs - Accumulated output types
170
170
  *
171
171
  * @example
172
- * const dataModelChain = new DataModelBuilder().from<TableData>(DATA_MODEL_DEFAULT_VERSION);
172
+ * const dataModelChain = new DataModelBuilder().from<TableData>("v1");
173
173
  *
174
174
  * const dataTable = PluginModel.define<TableData, TableParams, TableConfig>({
175
175
  * name: 'dataTable' as PluginName,