@platforma-sdk/model 1.54.13 → 1.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bconfig/normalization.cjs +8 -1
- package/dist/bconfig/normalization.cjs.map +1 -1
- package/dist/bconfig/normalization.d.ts.map +1 -1
- package/dist/bconfig/normalization.js +8 -1
- package/dist/bconfig/normalization.js.map +1 -1
- package/dist/block_api_v3.d.ts +2 -2
- package/dist/block_api_v3.d.ts.map +1 -1
- package/dist/block_migrations.cjs +246 -214
- package/dist/block_migrations.cjs.map +1 -1
- package/dist/block_migrations.d.ts +180 -158
- package/dist/block_migrations.d.ts.map +1 -1
- package/dist/block_migrations.js +247 -214
- package/dist/block_migrations.js.map +1 -1
- package/dist/block_model.cjs +85 -35
- package/dist/block_model.cjs.map +1 -1
- package/dist/block_model.d.ts +66 -38
- package/dist/block_model.d.ts.map +1 -1
- package/dist/block_model.js +86 -36
- package/dist/block_model.js.map +1 -1
- package/dist/{builder.cjs → block_model_legacy.cjs} +2 -2
- package/dist/block_model_legacy.cjs.map +1 -0
- package/dist/{builder.d.ts → block_model_legacy.d.ts} +1 -1
- package/dist/block_model_legacy.d.ts.map +1 -0
- package/dist/{builder.js → block_model_legacy.js} +2 -2
- package/dist/block_model_legacy.js.map +1 -0
- package/dist/block_state_patch.d.ts +11 -1
- package/dist/block_state_patch.d.ts.map +1 -1
- package/dist/block_storage.cjs +126 -109
- package/dist/block_storage.cjs.map +1 -1
- package/dist/block_storage.d.ts +109 -112
- package/dist/block_storage.d.ts.map +1 -1
- package/dist/block_storage.js +126 -101
- package/dist/block_storage.js.map +1 -1
- package/dist/block_storage_callbacks.cjs +227 -0
- package/dist/block_storage_callbacks.cjs.map +1 -0
- package/dist/block_storage_callbacks.d.ts +113 -0
- package/dist/block_storage_callbacks.d.ts.map +1 -0
- package/dist/block_storage_callbacks.js +218 -0
- package/dist/block_storage_callbacks.js.map +1 -0
- package/dist/block_storage_facade.cjs +104 -0
- package/dist/block_storage_facade.cjs.map +1 -0
- package/dist/block_storage_facade.d.ts +168 -0
- package/dist/block_storage_facade.d.ts.map +1 -0
- package/dist/block_storage_facade.js +99 -0
- package/dist/block_storage_facade.js.map +1 -0
- package/dist/index.cjs +13 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/package.json.cjs +1 -1
- package/dist/package.json.js +1 -1
- package/dist/platforma.d.ts +11 -4
- package/dist/platforma.d.ts.map +1 -1
- package/dist/plugin_model.cjs +171 -0
- package/dist/plugin_model.cjs.map +1 -0
- package/dist/plugin_model.d.ts +162 -0
- package/dist/plugin_model.d.ts.map +1 -0
- package/dist/plugin_model.js +169 -0
- package/dist/plugin_model.js.map +1 -0
- package/dist/render/api.cjs +20 -21
- package/dist/render/api.cjs.map +1 -1
- package/dist/render/api.d.ts +8 -8
- package/dist/render/api.d.ts.map +1 -1
- package/dist/render/api.js +20 -21
- package/dist/render/api.js.map +1 -1
- package/dist/render/internal.cjs.map +1 -1
- package/dist/render/internal.d.ts +1 -1
- package/dist/render/internal.d.ts.map +1 -1
- package/dist/render/internal.js.map +1 -1
- package/dist/version.cjs +4 -0
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.ts +4 -0
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +4 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -5
- package/src/bconfig/normalization.ts +8 -1
- package/src/block_api_v3.ts +2 -2
- package/src/block_migrations.test.ts +141 -171
- package/src/block_migrations.ts +300 -285
- package/src/block_model.ts +205 -95
- package/src/{builder.ts → block_model_legacy.ts} +1 -1
- package/src/block_state_patch.ts +13 -1
- package/src/block_storage.test.ts +283 -95
- package/src/block_storage.ts +199 -188
- package/src/block_storage_callbacks.ts +326 -0
- package/src/block_storage_facade.ts +199 -0
- package/src/index.ts +7 -3
- package/src/platforma.ts +26 -7
- package/src/plugin_model.test.ts +168 -0
- package/src/plugin_model.ts +242 -0
- package/src/render/api.ts +26 -24
- package/src/render/internal.ts +1 -1
- package/src/typing.test.ts +1 -1
- package/src/version.ts +8 -0
- package/dist/block_storage_vm.cjs +0 -262
- package/dist/block_storage_vm.cjs.map +0 -1
- package/dist/block_storage_vm.d.ts +0 -59
- package/dist/block_storage_vm.d.ts.map +0 -1
- package/dist/block_storage_vm.js +0 -258
- package/dist/block_storage_vm.js.map +0 -1
- package/dist/branding.d.ts +0 -7
- package/dist/branding.d.ts.map +0 -1
- package/dist/builder.cjs.map +0 -1
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js.map +0 -1
- package/dist/sdk_info.cjs +0 -10
- package/dist/sdk_info.cjs.map +0 -1
- package/dist/sdk_info.d.ts +0 -5
- package/dist/sdk_info.d.ts.map +0 -1
- package/dist/sdk_info.js +0 -8
- package/dist/sdk_info.js.map +0 -1
- package/dist/unionize.d.ts +0 -12
- package/dist/unionize.d.ts.map +0 -1
- package/src/block_storage_vm.ts +0 -346
- package/src/branding.ts +0 -4
- package/src/sdk_info.ts +0 -9
- package/src/unionize.ts +0 -12
package/src/block_storage.ts
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* @module block_storage
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import type { Branded } from "@milaboratories/pl-model-common";
|
|
13
|
+
import type { DataMigrationResult, DataVersioned } from "./block_migrations";
|
|
14
|
+
|
|
12
15
|
// =============================================================================
|
|
13
16
|
// Core Types
|
|
14
17
|
// =============================================================================
|
|
@@ -37,28 +40,44 @@ export const DATA_MODEL_DEFAULT_VERSION = "__pl_v1_d4e8f2a1__";
|
|
|
37
40
|
export type BlockStorageSchemaVersion = "v1"; // Add 'v2', 'v3', etc. as schema evolves
|
|
38
41
|
|
|
39
42
|
/**
|
|
40
|
-
*
|
|
43
|
+
* Branded type for plugin names - globally unique plugin type identifiers.
|
|
44
|
+
* Using a branded type enforces explicit casting (`as PluginName`) which makes
|
|
45
|
+
* it easy to find all plugin name definitions in the codebase and verify uniqueness.
|
|
46
|
+
*/
|
|
47
|
+
export type PluginName = Branded<string, "PluginName">;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Plugin registry - maps pluginId (unique within a block) to pluginName (globally unique plugin type).
|
|
51
|
+
* Using a Record highlights that pluginIds must be unique within a block.
|
|
52
|
+
*/
|
|
53
|
+
export type PluginRegistry = Record<string, PluginName>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Versioned data - used for both block data and plugin data
|
|
41
57
|
*/
|
|
42
|
-
export
|
|
58
|
+
export interface VersionedData<TData = unknown> {
|
|
59
|
+
/** Version of the data, used for migrations */
|
|
60
|
+
__dataVersion: string;
|
|
61
|
+
/** The persistent data */
|
|
62
|
+
__data: TData;
|
|
63
|
+
}
|
|
43
64
|
|
|
44
65
|
/**
|
|
45
66
|
* Core BlockStorage type that holds:
|
|
46
67
|
* - __pl_a7f3e2b9__: Schema version (discriminator key identifies BlockStorage format)
|
|
47
68
|
* - __dataVersion: Version key for block data migrations
|
|
48
69
|
* - __data: The block's user-facing data (state)
|
|
49
|
-
* -
|
|
70
|
+
* - __pluginRegistry: Map from pluginId to pluginName (optional)
|
|
71
|
+
* - __plugins: Plugin-specific data keyed by pluginId (optional)
|
|
50
72
|
*/
|
|
51
73
|
export type BlockStorage<TState = unknown> = {
|
|
52
74
|
/** Schema version - the key itself is the discriminator */
|
|
53
75
|
readonly [BLOCK_STORAGE_KEY]: BlockStorageSchemaVersion;
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
} &
|
|
59
|
-
/** Plugin-specific data, keyed by `@plugin/<pluginName>` */
|
|
60
|
-
[K in PluginKey]?: unknown;
|
|
61
|
-
};
|
|
76
|
+
/** Registry of plugins: pluginId -> pluginName */
|
|
77
|
+
__pluginRegistry?: PluginRegistry;
|
|
78
|
+
/** Plugin-specific data, keyed by pluginId */
|
|
79
|
+
__plugins?: Record<string, VersionedData<unknown>>;
|
|
80
|
+
} & VersionedData<TState>;
|
|
62
81
|
|
|
63
82
|
/**
|
|
64
83
|
* Type guard to check if a value is a valid BlockStorage object.
|
|
@@ -91,12 +110,14 @@ export function createBlockStorage<TState = unknown>(
|
|
|
91
110
|
[BLOCK_STORAGE_KEY]: BLOCK_STORAGE_SCHEMA_VERSION,
|
|
92
111
|
__dataVersion: version,
|
|
93
112
|
__data: initialData,
|
|
113
|
+
__pluginRegistry: {},
|
|
114
|
+
__plugins: {},
|
|
94
115
|
};
|
|
95
116
|
}
|
|
96
117
|
|
|
97
118
|
/**
|
|
98
119
|
* Normalizes raw storage data to BlockStorage format.
|
|
99
|
-
* If the input is already a BlockStorage, returns it as-is.
|
|
120
|
+
* If the input is already a BlockStorage, returns it as-is (with defaults for missing fields).
|
|
100
121
|
* If the input is legacy format (raw state), wraps it in BlockStorage structure.
|
|
101
122
|
*
|
|
102
123
|
* @param raw - Raw storage data (may be legacy format or BlockStorage)
|
|
@@ -112,6 +133,9 @@ export function normalizeBlockStorage<TState = unknown>(raw: unknown): BlockStor
|
|
|
112
133
|
typeof storage.__dataVersion === "number"
|
|
113
134
|
? DATA_MODEL_DEFAULT_VERSION
|
|
114
135
|
: storage.__dataVersion,
|
|
136
|
+
// Ensure plugin fields have defaults
|
|
137
|
+
__pluginRegistry: storage.__pluginRegistry ?? {},
|
|
138
|
+
__plugins: storage.__plugins ?? {},
|
|
115
139
|
};
|
|
116
140
|
}
|
|
117
141
|
// Legacy format: raw is the state directly
|
|
@@ -150,7 +174,9 @@ export function deriveDataFromStorage<TData = unknown>(rawStorage: unknown): TDa
|
|
|
150
174
|
}
|
|
151
175
|
|
|
152
176
|
/** Payload for storage mutation operations. SDK defines specific operations. */
|
|
153
|
-
export type MutateStoragePayload<T = unknown> =
|
|
177
|
+
export type MutateStoragePayload<T = unknown> =
|
|
178
|
+
| { operation: "update-block-data"; value: T }
|
|
179
|
+
| { operation: "update-plugin-data"; pluginId: string; value: unknown };
|
|
154
180
|
|
|
155
181
|
/**
|
|
156
182
|
* Updates the data in BlockStorage (immutable)
|
|
@@ -164,37 +190,29 @@ export function updateStorageData<TValue = unknown>(
|
|
|
164
190
|
payload: MutateStoragePayload<TValue>,
|
|
165
191
|
): BlockStorage<TValue> {
|
|
166
192
|
switch (payload.operation) {
|
|
167
|
-
case "update-data":
|
|
193
|
+
case "update-block-data":
|
|
168
194
|
return { ...storage, __data: payload.value };
|
|
195
|
+
case "update-plugin-data": {
|
|
196
|
+
const { pluginId, value } = payload;
|
|
197
|
+
const currentPlugins = storage.__plugins ?? {};
|
|
198
|
+
const existingEntry = currentPlugins[pluginId];
|
|
199
|
+
const version = existingEntry?.__dataVersion ?? DATA_MODEL_DEFAULT_VERSION;
|
|
200
|
+
return {
|
|
201
|
+
...storage,
|
|
202
|
+
__plugins: {
|
|
203
|
+
...currentPlugins,
|
|
204
|
+
[pluginId]: {
|
|
205
|
+
__dataVersion: version,
|
|
206
|
+
__data: value,
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
169
211
|
default:
|
|
170
212
|
throw new Error(`Unknown storage operation: ${(payload as { operation: string }).operation}`);
|
|
171
213
|
}
|
|
172
214
|
}
|
|
173
215
|
|
|
174
|
-
/**
|
|
175
|
-
* Gets the data version from BlockStorage
|
|
176
|
-
*
|
|
177
|
-
* @param storage - The BlockStorage instance
|
|
178
|
-
* @returns The data version key
|
|
179
|
-
*/
|
|
180
|
-
export function getStorageDataVersion(storage: BlockStorage): string {
|
|
181
|
-
return storage.__dataVersion;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Updates the data version in BlockStorage (immutable)
|
|
186
|
-
*
|
|
187
|
-
* @param storage - The current BlockStorage
|
|
188
|
-
* @param version - The new version key
|
|
189
|
-
* @returns A new BlockStorage with updated version
|
|
190
|
-
*/
|
|
191
|
-
export function updateStorageDataVersion<TState>(
|
|
192
|
-
storage: BlockStorage<TState>,
|
|
193
|
-
version: string,
|
|
194
|
-
): BlockStorage<TState> {
|
|
195
|
-
return { ...storage, __dataVersion: version };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
216
|
/**
|
|
199
217
|
* Storage debug view returned by __pl_storage_debugView callback.
|
|
200
218
|
* Used by developer tools to display block storage info.
|
|
@@ -207,180 +225,173 @@ export interface StorageDebugView {
|
|
|
207
225
|
}
|
|
208
226
|
|
|
209
227
|
// =============================================================================
|
|
210
|
-
//
|
|
228
|
+
// Atomic Migration
|
|
211
229
|
// =============================================================================
|
|
212
230
|
|
|
213
231
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* @param storage - The BlockStorage instance
|
|
217
|
-
* @param pluginName - The plugin name (without `@plugin/` prefix)
|
|
218
|
-
* @returns The plugin data or undefined if not set
|
|
232
|
+
* Result of a successful atomic migration.
|
|
219
233
|
*/
|
|
220
|
-
export
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const key: PluginKey = `@plugin/${pluginName}`;
|
|
225
|
-
return storage[key] as TData | undefined;
|
|
234
|
+
export interface MigrationSuccess<TState> {
|
|
235
|
+
success: true;
|
|
236
|
+
/** The fully migrated storage - commit this to persist */
|
|
237
|
+
storage: BlockStorage<TState>;
|
|
226
238
|
}
|
|
227
239
|
|
|
228
240
|
/**
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* @param storage - The current BlockStorage
|
|
232
|
-
* @param pluginName - The plugin name (without `@plugin/` prefix)
|
|
233
|
-
* @param data - The plugin data to store
|
|
234
|
-
* @returns A new BlockStorage with updated plugin data
|
|
241
|
+
* Result of a failed atomic migration.
|
|
242
|
+
* The original storage is untouched - user must choose to abort or reset.
|
|
235
243
|
*/
|
|
236
|
-
export
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return { ...storage, [key]: data };
|
|
244
|
+
export interface MigrationFailure {
|
|
245
|
+
success: false;
|
|
246
|
+
/** Description of what failed */
|
|
247
|
+
error: string;
|
|
248
|
+
/** Which step failed: 'block' or pluginId */
|
|
249
|
+
failedAt: string;
|
|
243
250
|
}
|
|
244
251
|
|
|
245
|
-
|
|
246
|
-
* Removes plugin-specific data from BlockStorage (immutable)
|
|
247
|
-
*
|
|
248
|
-
* @param storage - The current BlockStorage
|
|
249
|
-
* @param pluginName - The plugin name (without `@plugin/` prefix)
|
|
250
|
-
* @returns A new BlockStorage with the plugin data removed
|
|
251
|
-
*/
|
|
252
|
-
export function removePluginData<TState>(
|
|
253
|
-
storage: BlockStorage<TState>,
|
|
254
|
-
pluginName: string,
|
|
255
|
-
): BlockStorage<TState> {
|
|
256
|
-
const key: PluginKey = `@plugin/${pluginName}`;
|
|
257
|
-
const { [key]: _, ...rest } = storage;
|
|
258
|
-
return rest as BlockStorage<TState>;
|
|
259
|
-
}
|
|
252
|
+
export type MigrationResult<TState> = MigrationSuccess<TState> | MigrationFailure;
|
|
260
253
|
|
|
261
254
|
/**
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
* @returns Array of plugin names (without `@plugin/` prefix)
|
|
255
|
+
* Configuration for atomic block storage migration.
|
|
256
|
+
* Callbacks use DataVersioned format (the DataModel API format).
|
|
257
|
+
* Conversion to internal VersionedData format is handled by migrateBlockStorage().
|
|
266
258
|
*/
|
|
267
|
-
export
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
259
|
+
export interface MigrateBlockStorageConfig {
|
|
260
|
+
/** Migrate block data from any version to latest */
|
|
261
|
+
migrateBlockData: (versioned: DataVersioned<unknown>) => DataMigrationResult<unknown>;
|
|
262
|
+
/** Migrate each plugin's data. Return undefined to remove the plugin. */
|
|
263
|
+
migratePluginData: (
|
|
264
|
+
pluginId: string,
|
|
265
|
+
versioned: DataVersioned<unknown>,
|
|
266
|
+
) => DataMigrationResult<unknown> | undefined;
|
|
267
|
+
/** The new plugin registry after migration (pluginId -> pluginName) */
|
|
268
|
+
newPluginRegistry: PluginRegistry;
|
|
269
|
+
/** Factory to create initial data for new plugins */
|
|
270
|
+
createPluginData: (pluginId: string) => DataVersioned<unknown>;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
// =============================================================================
|
|
274
|
-
// Generic Storage Access
|
|
275
|
-
// =============================================================================
|
|
276
|
-
|
|
277
273
|
/**
|
|
278
|
-
*
|
|
274
|
+
* Performs atomic migration of block storage including block data and all plugins.
|
|
279
275
|
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
* @returns The value at the given key
|
|
283
|
-
*/
|
|
284
|
-
export function getFromStorage<TState, K extends keyof BlockStorage<TState>>(
|
|
285
|
-
storage: BlockStorage<TState>,
|
|
286
|
-
key: K,
|
|
287
|
-
): BlockStorage<TState>[K] {
|
|
288
|
-
return storage[key];
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* Updates a value in BlockStorage by key (immutable)
|
|
276
|
+
* Migration is atomic: either everything succeeds and a new storage is returned,
|
|
277
|
+
* or an error is returned and the original storage is completely untouched.
|
|
293
278
|
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
279
|
+
* Migration steps:
|
|
280
|
+
* 1. Migrate block data
|
|
281
|
+
* 2. For each plugin in newPluginRegistry:
|
|
282
|
+
* - If plugin exists with same name: migrate its data
|
|
283
|
+
* - Otherwise (new or type changed): create with initial data
|
|
284
|
+
* Plugins not in newPluginRegistry are dropped.
|
|
285
|
+
*
|
|
286
|
+
* If any step throws, migration fails and original storage is preserved.
|
|
287
|
+
* User can then choose to:
|
|
288
|
+
* - Abort: keep original storage, don't update block
|
|
289
|
+
* - Reset: call createBlockStorage() to start fresh
|
|
290
|
+
*
|
|
291
|
+
* @param storage - The original storage (will not be modified)
|
|
292
|
+
* @param config - Migration configuration
|
|
293
|
+
* @returns Migration result - either success with new storage, or failure with error info
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* const result = migrateBlockStorage(storage, {
|
|
297
|
+
* migrateBlockData: (versioned) => blockDataModel.migrate(versioned),
|
|
298
|
+
* migratePluginData: (pluginId, versioned) => getPluginModel(pluginId).migrate(versioned),
|
|
299
|
+
* newPluginRegistry: { table1: 'dataTable' as PluginName },
|
|
300
|
+
* createPluginData: (pluginId) => getPluginModel(pluginId).getDefaultData(),
|
|
301
|
+
* });
|
|
302
|
+
*
|
|
303
|
+
* if (result.success) {
|
|
304
|
+
* commitStorage(result.storage);
|
|
305
|
+
* } else {
|
|
306
|
+
* const userChoice = await askUser(`Migration failed: ${result.error}. Reset data?`);
|
|
307
|
+
* if (userChoice === 'reset') {
|
|
308
|
+
* commitStorage(createBlockStorage(initialData, currentVersion));
|
|
309
|
+
* }
|
|
310
|
+
* // else: abort, keep original
|
|
311
|
+
* }
|
|
298
312
|
*/
|
|
299
|
-
export function
|
|
300
|
-
storage: BlockStorage<
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
313
|
+
export function migrateBlockStorage(
|
|
314
|
+
storage: BlockStorage<unknown>,
|
|
315
|
+
config: MigrateBlockStorageConfig,
|
|
316
|
+
): MigrationResult<unknown> {
|
|
317
|
+
const { migrateBlockData, migratePluginData, newPluginRegistry, createPluginData } = config;
|
|
318
|
+
|
|
319
|
+
// Step 1: Migrate block data
|
|
320
|
+
let migratedData: unknown;
|
|
321
|
+
let newVersion: string;
|
|
322
|
+
try {
|
|
323
|
+
const result = migrateBlockData({ version: storage.__dataVersion, data: storage.__data });
|
|
324
|
+
migratedData = result.data;
|
|
325
|
+
newVersion = result.version;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
return {
|
|
328
|
+
success: false,
|
|
329
|
+
error: error instanceof Error ? error.message : String(error),
|
|
330
|
+
failedAt: "block",
|
|
331
|
+
};
|
|
332
|
+
}
|
|
306
333
|
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
334
|
+
// Step 2: Migrate plugins
|
|
335
|
+
const oldPlugins = storage.__plugins ?? {};
|
|
336
|
+
const oldRegistry = storage.__pluginRegistry ?? {};
|
|
337
|
+
const newPlugins: Record<string, VersionedData<unknown>> = {};
|
|
338
|
+
|
|
339
|
+
for (const [pluginId, pluginName] of Object.entries(newPluginRegistry)) {
|
|
340
|
+
const existingEntry = oldPlugins[pluginId];
|
|
341
|
+
const existingName = oldRegistry[pluginId];
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
if (existingEntry && existingName === pluginName) {
|
|
345
|
+
// Plugin exists with same type - migrate its data
|
|
346
|
+
const migrated = migratePluginData(pluginId, {
|
|
347
|
+
version: existingEntry.__dataVersion,
|
|
348
|
+
data: existingEntry.__data,
|
|
349
|
+
});
|
|
350
|
+
if (migrated) {
|
|
351
|
+
newPlugins[pluginId] = { __dataVersion: migrated.version, __data: migrated.data };
|
|
352
|
+
}
|
|
353
|
+
// If undefined returned, plugin is intentionally removed
|
|
354
|
+
} else {
|
|
355
|
+
// New plugin or type changed - create with initial data
|
|
356
|
+
const initial = createPluginData(pluginId);
|
|
357
|
+
newPlugins[pluginId] = { __dataVersion: initial.version, __data: initial.data };
|
|
358
|
+
}
|
|
359
|
+
} catch (error) {
|
|
360
|
+
return {
|
|
361
|
+
success: false,
|
|
362
|
+
error: error instanceof Error ? error.message : String(error),
|
|
363
|
+
failedAt: pluginId,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
}
|
|
310
367
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
*
|
|
320
|
-
* @param currentStorage - The current BlockStorage
|
|
321
|
-
* @param newState - The new state being set
|
|
322
|
-
* @returns The updated BlockStorage
|
|
323
|
-
*/
|
|
324
|
-
transformStateForStorage?: (
|
|
325
|
-
currentStorage: BlockStorage<TState>,
|
|
326
|
-
newState: TState,
|
|
327
|
-
) => BlockStorage<TState>;
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Called when reading state for args derivation.
|
|
331
|
-
* Default behavior: returns the state directly.
|
|
332
|
-
*
|
|
333
|
-
* @param storage - The current BlockStorage
|
|
334
|
-
* @returns The state to use for args derivation
|
|
335
|
-
*/
|
|
336
|
-
deriveStateForArgs?: (storage: BlockStorage<TState>) => TState;
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Called during storage schema migration.
|
|
340
|
-
* Default behavior: updates stateVersion only.
|
|
341
|
-
*
|
|
342
|
-
* @param oldStorage - The storage before migration
|
|
343
|
-
* @param fromVersion - The version migrating from
|
|
344
|
-
* @param toVersion - The version migrating to
|
|
345
|
-
* @returns The migrated BlockStorage
|
|
346
|
-
*/
|
|
347
|
-
migrateStorage?: (
|
|
348
|
-
oldStorage: BlockStorage<TState>,
|
|
349
|
-
fromVersion: string,
|
|
350
|
-
toVersion: string,
|
|
351
|
-
) => BlockStorage<TState>;
|
|
352
|
-
}
|
|
368
|
+
// Step 3: Build final storage atomically
|
|
369
|
+
const migratedStorage: BlockStorage = {
|
|
370
|
+
[BLOCK_STORAGE_KEY]: BLOCK_STORAGE_SCHEMA_VERSION,
|
|
371
|
+
__dataVersion: newVersion,
|
|
372
|
+
__data: migratedData,
|
|
373
|
+
__pluginRegistry: newPluginRegistry,
|
|
374
|
+
__plugins: newPlugins,
|
|
375
|
+
};
|
|
353
376
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
storage: BlockStorage<TState>,
|
|
360
|
-
newState: TState,
|
|
361
|
-
): BlockStorage<TState> =>
|
|
362
|
-
updateStorageData(storage, { operation: "update-data", value: newState }),
|
|
363
|
-
|
|
364
|
-
deriveStateForArgs: <TState>(storage: BlockStorage<TState>): TState => getStorageData(storage),
|
|
365
|
-
|
|
366
|
-
migrateStorage: <TState>(
|
|
367
|
-
storage: BlockStorage<TState>,
|
|
368
|
-
_fromVersion: string,
|
|
369
|
-
toVersion: string,
|
|
370
|
-
): BlockStorage<TState> => updateStorageDataVersion(storage, toVersion),
|
|
371
|
-
};
|
|
377
|
+
return {
|
|
378
|
+
success: true,
|
|
379
|
+
storage: migratedStorage,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
372
382
|
|
|
373
383
|
/**
|
|
374
|
-
*
|
|
384
|
+
* Gets plugin-specific data from BlockStorage (for UI)
|
|
375
385
|
*
|
|
376
|
-
* @param
|
|
377
|
-
* @
|
|
386
|
+
* @param storage - The BlockStorage instance
|
|
387
|
+
* @param pluginId - The plugin instance id
|
|
388
|
+
* @returns The plugin data or undefined if not set
|
|
378
389
|
*/
|
|
379
|
-
export function
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
390
|
+
export function getPluginData<TData = unknown>(
|
|
391
|
+
storage: BlockStorage,
|
|
392
|
+
pluginId: string,
|
|
393
|
+
): TData | undefined {
|
|
394
|
+
const pluginEntry = storage.__plugins?.[pluginId];
|
|
395
|
+
if (!pluginEntry) return undefined;
|
|
396
|
+
return pluginEntry.__data as TData;
|
|
386
397
|
}
|