@platforma-sdk/model 1.53.0 → 1.53.2

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.
@@ -2,6 +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
6
  createBlockStorage,
6
7
  defaultBlockStorageHandlers,
7
8
  getFromStorage,
@@ -57,11 +58,11 @@ describe('BlockStorage', () => {
57
58
  });
58
59
 
59
60
  it('should return false for objects without discriminator', () => {
60
- expect(isBlockStorage({ __dataVersion: 1, __data: {} })).toBe(false);
61
+ expect(isBlockStorage({ __dataVersion: 'v1', __data: {} })).toBe(false);
61
62
  });
62
63
 
63
64
  it('should return false for objects with wrong discriminator value', () => {
64
- expect(isBlockStorage({ [BLOCK_STORAGE_KEY]: 'wrong', __dataVersion: 1, __data: {} })).toBe(false);
65
+ expect(isBlockStorage({ [BLOCK_STORAGE_KEY]: 'wrong', __dataVersion: 'v1', __data: {} })).toBe(false);
65
66
  });
66
67
  });
67
68
 
@@ -69,7 +70,7 @@ describe('BlockStorage', () => {
69
70
  it('should create storage with discriminator key and default values', () => {
70
71
  const storage = createBlockStorage();
71
72
  expect(storage[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
72
- expect(storage.__dataVersion).toBe(1);
73
+ expect(storage.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
73
74
  expect(storage.__data).toEqual({});
74
75
  });
75
76
 
@@ -77,21 +78,21 @@ describe('BlockStorage', () => {
77
78
  const data = { numbers: [1, 2, 3] };
78
79
  const storage = createBlockStorage(data);
79
80
  expect(storage[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
80
- expect(storage.__dataVersion).toBe(1);
81
+ expect(storage.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
81
82
  expect(storage.__data).toEqual(data);
82
83
  });
83
84
 
84
85
  it('should create storage with custom version', () => {
85
- const storage = createBlockStorage({ foo: 'bar' }, 5);
86
+ const storage = createBlockStorage({ foo: 'bar' }, 'v5');
86
87
  expect(storage[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
87
- expect(storage.__dataVersion).toBe(5);
88
+ expect(storage.__dataVersion).toBe('v5');
88
89
  expect(storage.__data).toEqual({ foo: 'bar' });
89
90
  });
90
91
  });
91
92
 
92
93
  describe('normalizeBlockStorage', () => {
93
94
  it('should return BlockStorage as-is', () => {
94
- const storage = createBlockStorage({ data: 'test' }, 2);
95
+ const storage = createBlockStorage({ data: 'test' }, 'v2');
95
96
  const normalized = normalizeBlockStorage(storage);
96
97
  expect(normalized).toEqual(storage);
97
98
  });
@@ -100,52 +101,52 @@ describe('BlockStorage', () => {
100
101
  const legacyData = { numbers: [1, 2, 3], name: 'test' };
101
102
  const normalized = normalizeBlockStorage(legacyData);
102
103
  expect(normalized[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
103
- expect(normalized.__dataVersion).toBe(1);
104
+ expect(normalized.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
104
105
  expect(normalized.__data).toEqual(legacyData);
105
106
  });
106
107
 
107
108
  it('should wrap primitive legacy data', () => {
108
109
  const normalized = normalizeBlockStorage('simple string');
109
110
  expect(normalized[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
110
- expect(normalized.__dataVersion).toBe(1);
111
+ expect(normalized.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
111
112
  expect(normalized.__data).toBe('simple string');
112
113
  });
113
114
 
114
115
  it('should wrap null legacy data', () => {
115
116
  const normalized = normalizeBlockStorage(null);
116
117
  expect(normalized[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
117
- expect(normalized.__dataVersion).toBe(1);
118
+ expect(normalized.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
118
119
  expect(normalized.__data).toBeNull();
119
120
  });
120
121
  });
121
122
 
122
123
  describe('Data access functions', () => {
123
- const storage = createBlockStorage({ count: 42 }, 3);
124
+ const storage = createBlockStorage({ count: 42 }, 'v3');
124
125
 
125
126
  it('getStorageData should return the data', () => {
126
127
  expect(getStorageData(storage)).toEqual({ count: 42 });
127
128
  });
128
129
 
129
130
  it('getStorageDataVersion should return the version', () => {
130
- expect(getStorageDataVersion(storage)).toBe(3);
131
+ expect(getStorageDataVersion(storage)).toBe('v3');
131
132
  });
132
133
 
133
134
  it('updateStorageData should return new storage with updated data', () => {
134
135
  const newStorage = updateStorageData(storage, { operation: 'update-data', value: { count: 100 } });
135
136
  expect(newStorage.__data).toEqual({ count: 100 });
136
- expect(newStorage.__dataVersion).toBe(3);
137
+ expect(newStorage.__dataVersion).toBe('v3');
137
138
  expect(newStorage[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
138
139
  // Original should be unchanged
139
140
  expect(storage.__data).toEqual({ count: 42 });
140
141
  });
141
142
 
142
143
  it('updateStorageDataVersion should return new storage with updated version', () => {
143
- const newStorage = updateStorageDataVersion(storage, 5);
144
- expect(newStorage.__dataVersion).toBe(5);
144
+ const newStorage = updateStorageDataVersion(storage, 'v5');
145
+ expect(newStorage.__dataVersion).toBe('v5');
145
146
  expect(newStorage.__data).toEqual({ count: 42 });
146
147
  expect(newStorage[BLOCK_STORAGE_KEY]).toBe(BLOCK_STORAGE_SCHEMA_VERSION);
147
148
  // Original should be unchanged
148
- expect(storage.__dataVersion).toBe(3);
149
+ expect(storage.__dataVersion).toBe('v3');
149
150
  });
150
151
  });
151
152
 
@@ -189,14 +190,14 @@ describe('BlockStorage', () => {
189
190
  });
190
191
 
191
192
  describe('Generic storage access', () => {
192
- const storage = createBlockStorage('hello', 2);
193
+ const storage = createBlockStorage('hello', 'v2');
193
194
 
194
195
  it('getFromStorage should get __data', () => {
195
196
  expect(getFromStorage(storage, '__data')).toBe('hello');
196
197
  });
197
198
 
198
199
  it('getFromStorage should get __dataVersion', () => {
199
- expect(getFromStorage(storage, '__dataVersion')).toBe(2);
200
+ expect(getFromStorage(storage, '__dataVersion')).toBe('v2');
200
201
  });
201
202
 
202
203
  it('updateStorage should update any key', () => {
@@ -212,7 +213,7 @@ describe('BlockStorage', () => {
212
213
  const storage = createBlockStorage('old');
213
214
  const result = defaultBlockStorageHandlers.transformStateForStorage(storage, 'new');
214
215
  expect(result.__data).toBe('new');
215
- expect(result.__dataVersion).toBe(1);
216
+ expect(result.__dataVersion).toBe(DATA_MODEL_DEFAULT_VERSION);
216
217
  });
217
218
 
218
219
  it('deriveStateForArgs should return data directly', () => {
@@ -222,8 +223,8 @@ describe('BlockStorage', () => {
222
223
 
223
224
  it('migrateStorage should update version only', () => {
224
225
  const storage = createBlockStorage({ data: 'test' });
225
- const result = defaultBlockStorageHandlers.migrateStorage(storage, 1, 3);
226
- expect(result.__dataVersion).toBe(3);
226
+ const result = defaultBlockStorageHandlers.migrateStorage(storage, 'v1', 'v3');
227
+ expect(result.__dataVersion).toBe('v3');
227
228
  expect(result.__data).toEqual({ data: 'test' });
228
229
  });
229
230
  });
@@ -242,7 +243,7 @@ describe('BlockStorage', () => {
242
243
  const customTransform = <T>(storage: ReturnType<typeof createBlockStorage<T>>, data: T) => ({
243
244
  ...storage,
244
245
  __data: data,
245
- __dataVersion: storage.__dataVersion + 1,
246
+ __dataVersion: `${storage.__dataVersion}-next`,
246
247
  });
247
248
 
248
249
  const handlers = mergeBlockStorageHandlers({
@@ -25,6 +25,12 @@ export const BLOCK_STORAGE_KEY = '__pl_a7f3e2b9__';
25
25
  */
26
26
  export const BLOCK_STORAGE_SCHEMA_VERSION = 'v1';
27
27
 
28
+ /**
29
+ * Default data version for new blocks without migrations.
30
+ * Unique identifier ensures blocks are created via DataModel API.
31
+ */
32
+ export const DATA_MODEL_DEFAULT_VERSION = '__pl_v1_d4e8f2a1__';
33
+
28
34
  /**
29
35
  * Type for valid schema versions
30
36
  */
@@ -38,7 +44,7 @@ export type PluginKey = `@plugin/${string}`;
38
44
  /**
39
45
  * Core BlockStorage type that holds:
40
46
  * - __pl_a7f3e2b9__: Schema version (discriminator key identifies BlockStorage format)
41
- * - __dataVersion: Version number for block data migrations
47
+ * - __dataVersion: Version key for block data migrations
42
48
  * - __data: The block's user-facing data (state)
43
49
  * - @plugin/*: Optional plugin-specific data
44
50
  */
@@ -46,7 +52,7 @@ export type BlockStorage<TState = unknown> = {
46
52
  /** Schema version - the key itself is the discriminator */
47
53
  readonly [BLOCK_STORAGE_KEY]: BlockStorageSchemaVersion;
48
54
  /** Version of the block data, used for migrations */
49
- __dataVersion: number;
55
+ __dataVersion: string;
50
56
  /** The block's user-facing data (state) */
51
57
  __data: TState;
52
58
  } & {
@@ -74,12 +80,12 @@ export function isBlockStorage(value: unknown): value is BlockStorage {
74
80
  * Creates a BlockStorage with the given initial data
75
81
  *
76
82
  * @param initialData - The initial data value (defaults to empty object)
77
- * @param version - The initial data version (defaults to 1)
83
+ * @param version - The initial data version key (defaults to DATA_MODEL_DEFAULT_VERSION)
78
84
  * @returns A new BlockStorage instance with discriminator key
79
85
  */
80
86
  export function createBlockStorage<TState = unknown>(
81
87
  initialData: TState = {} as TState,
82
- version: number = 1,
88
+ version: string = DATA_MODEL_DEFAULT_VERSION,
83
89
  ): BlockStorage<TState> {
84
90
  return {
85
91
  [BLOCK_STORAGE_KEY]: BLOCK_STORAGE_SCHEMA_VERSION,
@@ -98,10 +104,17 @@ export function createBlockStorage<TState = unknown>(
98
104
  */
99
105
  export function normalizeBlockStorage<TState = unknown>(raw: unknown): BlockStorage<TState> {
100
106
  if (isBlockStorage(raw)) {
101
- return raw as BlockStorage<TState>;
107
+ const storage = raw as BlockStorage<TState>;
108
+ return {
109
+ ...storage,
110
+ // Fix for early released version where __dataVersion was a number
111
+ __dataVersion: typeof storage.__dataVersion === 'number'
112
+ ? DATA_MODEL_DEFAULT_VERSION
113
+ : storage.__dataVersion,
114
+ };
102
115
  }
103
116
  // Legacy format: raw is the state directly
104
- return createBlockStorage(raw as TState, 1);
117
+ return createBlockStorage(raw as TState);
105
118
  }
106
119
 
107
120
  // =============================================================================
@@ -163,9 +176,9 @@ export function updateStorageData<TValue = unknown>(
163
176
  * Gets the data version from BlockStorage
164
177
  *
165
178
  * @param storage - The BlockStorage instance
166
- * @returns The data version number
179
+ * @returns The data version key
167
180
  */
168
- export function getStorageDataVersion(storage: BlockStorage): number {
181
+ export function getStorageDataVersion(storage: BlockStorage): string {
169
182
  return storage.__dataVersion;
170
183
  }
171
184
 
@@ -173,12 +186,12 @@ export function getStorageDataVersion(storage: BlockStorage): number {
173
186
  * Updates the data version in BlockStorage (immutable)
174
187
  *
175
188
  * @param storage - The current BlockStorage
176
- * @param version - The new version number
189
+ * @param version - The new version key
177
190
  * @returns A new BlockStorage with updated version
178
191
  */
179
192
  export function updateStorageDataVersion<TState>(
180
193
  storage: BlockStorage<TState>,
181
- version: number,
194
+ version: string,
182
195
  ): BlockStorage<TState> {
183
196
  return { ...storage, __dataVersion: version };
184
197
  }
@@ -188,8 +201,8 @@ export function updateStorageDataVersion<TState>(
188
201
  * Used by developer tools to display block storage info.
189
202
  */
190
203
  export interface StorageDebugView {
191
- /** Current data version (1-based, starts at 1) */
192
- dataVersion: number;
204
+ /** Current data version key */
205
+ dataVersion: string;
193
206
  /** Raw data payload stored in BlockStorage */
194
207
  data: unknown;
195
208
  }
@@ -337,8 +350,8 @@ export interface BlockStorageHandlers<TState = unknown> {
337
350
  */
338
351
  migrateStorage?: (
339
352
  oldStorage: BlockStorage<TState>,
340
- fromVersion: number,
341
- toVersion: number,
353
+ fromVersion: string,
354
+ toVersion: string,
342
355
  ) => BlockStorage<TState>;
343
356
  }
344
357
 
@@ -355,8 +368,8 @@ export const defaultBlockStorageHandlers: Required<BlockStorageHandlers<unknown>
355
368
 
356
369
  migrateStorage: <TState>(
357
370
  storage: BlockStorage<TState>,
358
- _fromVersion: number,
359
- toVersion: number,
371
+ _fromVersion: string,
372
+ toVersion: string,
360
373
  ): BlockStorage<TState> => updateStorageDataVersion(storage, toVersion),
361
374
  };
362
375
 
@@ -14,7 +14,7 @@
14
14
  *
15
15
  * Callbacks registered by DataModel.registerCallbacks():
16
16
  * - `__pl_data_initial`: () => initial data
17
- * - `__pl_data_upgrade`: (versioned) => UpgradeResult
17
+ * - `__pl_data_upgrade`: (versioned) => DataMigrationResult
18
18
  * - `__pl_storage_initial`: () => initial BlockStorage as JSON string
19
19
  *
20
20
  * @module block_storage_vm
@@ -30,6 +30,7 @@ import {
30
30
  createBlockStorage,
31
31
  getStorageData,
32
32
  isBlockStorage,
33
+ normalizeBlockStorage,
33
34
  updateStorageData,
34
35
  } from './block_storage';
35
36
  import { stringifyJson, type StringifiedJson } from '@milaboratories/pl-model-common';
@@ -76,7 +77,8 @@ function normalizeStorage(rawStorage: unknown): NormalizeStorageResult {
76
77
 
77
78
  // Check for BlockStorage format (has discriminator)
78
79
  if (isBlockStorage(parsed)) {
79
- return { storage: parsed, data: getStorageData(parsed) };
80
+ const storage = normalizeBlockStorage(parsed);
81
+ return { storage, data: getStorageData(storage) };
80
82
  }
81
83
 
82
84
  // Check for legacy V1/V2 format: { args, uiState }
@@ -165,15 +167,15 @@ export type MigrationResult =
165
167
  | { error: string }
166
168
  | { error?: undefined; newStorageJson: string; info: string; warn?: string };
167
169
 
168
- /** Result from Migrator.upgrade() */
169
- interface UpgradeResult {
170
- version: number;
170
+ /** Result from DataModel.migrate() */
171
+ interface DataMigrationResult {
172
+ version: string;
171
173
  data: unknown;
172
174
  warning?: string;
173
175
  }
174
176
 
175
177
  /**
176
- * Runs storage migration using the DataModel's upgrade callback.
178
+ * Runs storage migration using the DataModel's migrate callback.
177
179
  * This is the main entry point for the middle layer to trigger migrations.
178
180
  *
179
181
  * Uses the '__pl_data_upgrade' callback registered by DataModel.registerCallbacks() which:
@@ -195,7 +197,7 @@ function migrateStorage(currentStorageJson: string | undefined): MigrationResult
195
197
  const currentVersion = currentStorage.__dataVersion;
196
198
 
197
199
  // Helper to create storage with given data and version
198
- const createStorageJson = (data: unknown, version: number): string => {
200
+ const createStorageJson = (data: unknown, version: string): string => {
199
201
  return JSON.stringify({
200
202
  ...currentStorage,
201
203
  __dataVersion: version,
@@ -203,27 +205,27 @@ function migrateStorage(currentStorageJson: string | undefined): MigrationResult
203
205
  });
204
206
  };
205
207
 
206
- // Get the upgrade callback (registered by DataModel.registerCallbacks())
207
- const upgradeCallback = ctx.callbackRegistry['__pl_data_upgrade'] as ((v: { version: number; data: unknown }) => UpgradeResult) | undefined;
208
- if (typeof upgradeCallback !== 'function') {
208
+ // Get the migrate callback (registered by DataModel.registerCallbacks())
209
+ const migrateCallback = ctx.callbackRegistry['__pl_data_upgrade'] as ((v: { version: string; data: unknown }) => DataMigrationResult) | undefined;
210
+ if (typeof migrateCallback !== 'function') {
209
211
  return { error: '__pl_data_upgrade callback not found (DataModel not registered)' };
210
212
  }
211
213
 
212
- // Call the migrator's upgrade function
213
- let result: UpgradeResult;
214
+ // Call the migrator's migrate function
215
+ let result: DataMigrationResult;
214
216
  try {
215
- result = upgradeCallback({ version: currentVersion, data: currentData });
217
+ result = migrateCallback({ version: currentVersion, data: currentData });
216
218
  } catch (e) {
217
219
  const errorMsg = e instanceof Error ? e.message : String(e);
218
- return { error: `upgrade() threw: ${errorMsg}` };
220
+ return { error: `migrate() threw: ${errorMsg}` };
219
221
  }
220
222
 
221
223
  // Build info message
222
224
  const info = result.version === currentVersion
223
- ? `No migration needed (v${currentVersion})`
225
+ ? `No migration needed (${currentVersion})`
224
226
  : result.warning
225
- ? `Reset to initial data (v${result.version})`
226
- : `Migrated v${currentVersion}→v${result.version}`;
227
+ ? `Reset to initial data (${result.version})`
228
+ : `Migrated ${currentVersion}→${result.version}`;
227
229
 
228
230
  return {
229
231
  newStorageJson: createStorageJson(result.data, result.version),
package/src/index.ts CHANGED
@@ -3,7 +3,15 @@ export * from './block_state_util';
3
3
  export * from './block_storage';
4
4
  export * from './builder';
5
5
  export { BlockModelV3 } from './block_model';
6
- export { DataModel } from './block_migrations';
6
+ export {
7
+ DataModel,
8
+ DataModelBuilder,
9
+ DataUnrecoverableError,
10
+ isDataUnrecoverableError,
11
+ defineDataVersions,
12
+ defaultRecover,
13
+ makeDataVersioned,
14
+ } from './block_migrations';
7
15
  export * from './bconfig';
8
16
  export * from './components';
9
17
  export * from './config';